Skip to content

[API Proposal]: Add JsonSerializer.DeserializeAsyncEnumerable overloads accepting non-generic JsonTypeInfo and Type #107940

@ykvelit

Description

@ykvelit

Background and motivation

The JsonSerializer class exposes deserialization overloads that accept JsonTypeInfo<T> and JsonTypeInfo. These overloads are generally marked AOT/linker-safe and provide the only entrypoint for serializing using materialized metadata instances. We are however missing non-generic overloads for doing the same thing for DeserializeAsyncEnumerable.

In my case, firstly we call an Rest API that respond an object schema. Then call another Rest API to get data. In this moment, we receive a response as stream and we use ExpandoObject class to deserialize that response. In my study, using a typed deserialization we have an improvement in memory usage compared to the ExpandoObject. Considering that the object to be deserialized is dynamic, we are creating a type using the System.Reflection.Emit.AssemblyBuilder API using the object schema recovered by first call and try to use DeserializeAsyncEnumerable.

If we use DeserializeAsync we will have to wait for the entire body response complete to interact with data received.

API Proposal

namespace System.Text.Json;

public partial static class JsonSerializer
{
    public static IAsyncEnumerable<object?> DeserializeAsyncEnumerable(Stream utf8Json, JsonTypeInfo jsonTypeInfo, CancellationToken cancellationToken = default);
    public static IAsyncEnumerable<object?> DeserializeAsyncEnumerable(Stream utf8Json, JsonTypeInfo jsonTypeInfo, bool topLevelValues, CancellationToken cancellationToken = default);
    public static IAsyncEnumerable<object?> DeserializeAsyncEnumerable(Stream utf8Json, Type returnType, JsonSerializerOptions? options = default, CancellationToken cancellationToken = default);
    public static IAsyncEnumerable<object?> DeserializeAsyncEnumerable(Stream utf8Json, Type returnType, bool topLevelValues, JsonSerializerOptions? options = default, CancellationToken cancellationToken = default);
}

API Usage

// create any dynamic type using System.Reflection.Emit.AssemblyBuilder
Type? returnType = tb.CreateType();

var json = """
            [
                {"Number": 1},
                {"Number": 2},
                {"Number": 3}
            ]
        """;

byte[] bytes = Encoding.UTF8.GetBytes(json);
var stream = new MemoryStream(bytes);

var result = JsonSerializer.DeserializeAsyncEnumerable(stream, returnType);

await foreach (var item in result)
{
    var property = returnType.GetProperty("Number");
    var value = property.GetValue(item, null);
    Console.WriteLine(value); // 1, 2, 3
}

Alternative Designs

public static IAsyncEnumerable<object> DeserializeAsyncEnumerable(Stream utf8Json, Type returnType, JsonSerializerOptions options = null, CancellationToken cancellationToken = default)
{
    var serializer = typeof(JsonSerializer);

    var deserializeAsyncEnumerable = serializer
        .GetMethod("DeserializeAsyncEnumerable", BindingFlags.Public | BindingFlags.Static);

    var typedDeserializeAsyncEnumerable = deserializeAsyncEnumerable.MakeGenericMethod(returnType);

    var result = typedDeserializeAsyncEnumerable.Invoke(null, new object[] { utf8Json, options, cancellationToken });

    return result as IAsyncEnumerable<object>;
}

Risks

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions