Skip to content

[API Proposal]: Add support for nested data streaming in JsonSerializer.DeserializeAsyncEnumerable. #114265

Open

Description

@Harpush

Background and motivation

Currently IAsyncEnumerable is only supported for top level (root) arrays. The problem is many APIs return json data structured with a wrapper object. For example: { "data": [/* Huge array here */] }. This kind of jsons eliminate the ability to use IAsyncEnumerable without manual parsing.
The expected support is parsing the array inside the data property as if it was a root array.

API Proposal

There are actually two cases for the API - one for simple cases and one for more control albeit more low level.

For the high level extend deserialize of IAsyncEnumerable to accept a root json path or even a property name.

JsonSerializer.DeserializeAsyncEnumerable(stream, "data");

This should also apply to http client methods regarding IAsyncEnumerable.

For the low level - the idea is giving an easier json token parser that allows control on how to deserialize.
For example given the json { "data1": [/* Huge array here */], "data2": [/* Huge array here */]} and the requirement to create an IAsyncEnumerable from data1 concatenated with data2 the api could allow:

using var jsonStreamReader = new JsonStreamReader(stream, bufferSize /* Maybe? */);

await jsonStreamReader.ReadAsync(); // skip object start
await jsonStreamReader.ReadAsync(); // skip data1

var data1AsyncEnumerable = jsonStreamReader.DeserializeAsyncEnumerable<DataItem>(options);

await foreach (var item in data1AsyncEnumerable)
{
    yield return item;
}

await jsonStreamReader.ReadAsync(); // skip data2

var data2AsyncEnumerable = jsonStreamReader.DeserializeAsyncEnumerable<DataItem>(options);

await foreach (var item in data2AsyncEnumerable)
{
    yield return item;
}

Here JsonStreamReader is the new API that allows much more control.

API Usage

Mentioned above in API proposal

Alternative Designs

Currently the only way is writing my own parser with low level APIs which isn't trivial at all

Risks

None (probably?)

Activity

  1. added
    api-suggestionEarly API idea and discussion, it is NOT ready for implementation
    on Apr 4, 2025
  2. dotnet-policy-service commented on Apr 4, 2025

    @dotnet-policy-service
    Contributor

    Tagging subscribers to this area: @dotnet/area-system-text-json, @gregsdennis
    See info in area-owners.md if you want to be subscribed.

  3. eiriktsarpalis commented on Apr 4, 2025

    @eiriktsarpalis
    Member

    It's unlikely we would add a "JsonStreamReader" just to enable this feature. The more likely outcome is introducing an overload to the existing JsonSerializer.DeserializeAsyncEnumerable methods that takes a JSON pointer/JSON path parameter pinpointing the nested array that requires streaming.

  4. added this to the Future milestone on Apr 4, 2025
  5. removed
    untriagedNew issue has not been triaged by the area owner
    on Apr 4, 2025
  6. changed the title [-][API Proposal]: Support non root IAsyncEnumerable parsing[/-] [+][API Proposal]: Add support for nested data streaming in `JsonSerializer.DeserializeAsyncEnumerable`.[/+] on Apr 4, 2025
  7. Harpush commented on Apr 4, 2025

    @Harpush
    Author

    That's fine too. The idea of the reader is to allow any combination of read tokens and deserialize. I would expect a regular deserialize (non IAsyncEnumerable) method too

  8. brantburnett commented on Apr 4, 2025

    @brantburnett
    Contributor

    I have a nested array streaming use case in the Couchbase SDK which requires maintaining the other context. It's implemented using a custom wrapper around Stream using Utf8JsonReader and JsonReaderState. Unfortunately, the example is complicated a bit because it's abstracted to also support Newtonsoft. I share it in case it gives insight into other use cases.

    https://github.com/couchbase/couchbase-net-client/blob/master/src/Couchbase/Query/StreamingQueryResult.cs
    https://github.com/couchbase/couchbase-net-client/blob/master/src/Couchbase/Core/IO/Serializers/SystemTextJson/SystemTextJsonStreamReader.cs

  9. Harpush commented on Apr 5, 2025

    @Harpush
    Author

    Just to add some more examples to why JsonStreamReader is a good idea:

    1. Can allow custom json processing (for example json: {metadata: {...}, data: [/*huge json*/]}):
    await jsonReader.SkipAsync(JsonTokenType.StartObject);
    await jsonReader.SkipAsync(JsonTokenType.PropertyName);
    
    Metadata metadata = await jsonReader.DeserializeAsync<Metadata>();
    
    await jsonReader.SkipAsync(JsonTokenType.PropertyName);
    
    await foreach (ArrayItem arrItem in jsonReader.DeserializeAsyncEnumerable<ArrayItem>())
    {
        // Use it
    }
    1. Can allow the path based deserialize mentioned in the post
    2. Can allow IAsyncEnumerable of dictionary ({a: {...}, b: {...}, c: {...}}) with KeyValuePair:
    await jsonReader.SkipAsync(JsonTokenType.StartObject);
    
    while (true)
    {
        JsonTokenType token = await jsonReader.PeekAsync();
    
        if (token == JsonTokenType.EndObject)
        {
            yield break;
        }
    
        string prop = await jsonReader.ReadPropertyNameAsync(); // Or similar API
        Item item = await jsonReader.DeserializeAsync<Item>();
    
        var pair = new KeyValuePair<string, Item>(prop, item);
    
        yield return pair;
    }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions