using System.Collections;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using System.Text.Json.Nodes;
using OASISLevel2TrackingPacket;
namespace OASISLevel2TrackingPacket
[global::ProtoBuf.ProtoContract]
public partial class EntityTracking
[global::ProtoBuf.ProtoMember(2, Name = @"TrackingData")]
public global::System.Collections.Generic.List<EntityTrackingActivity> TrackingDatas { get; } = new global::System.Collections.Generic.List<EntityTrackingActivity>();
[global::ProtoBuf.ProtoContract]
public partial class Person : EntityTracking
[global::ProtoBuf.ProtoMember(1, Name = @"Name")]
public string? Name { get; set; }
[global::ProtoBuf.ProtoContract]
public partial class EntityTrackingActivity
[global::ProtoBuf.ProtoMember(1, Name = @"Id")]
public int Id { get; set; }
public static partial class JsonExtensions
public static Action<JsonTypeInfo> InitializeProtoMemberNames(Type type) => typeInfo =>
if (typeInfo.Kind != JsonTypeInfoKind.Object)
if (!type.IsAssignableFrom(typeInfo.Type))
foreach (var property in typeInfo.Properties)
var name = property.AttributeProvider?.GetCustomAttributes(typeof(global::ProtoBuf.ProtoMemberAttribute), true)
.OfType<global::ProtoBuf.ProtoMemberAttribute>()
public static Action<JsonTypeInfo> InitializeGetOnlyListSetters(Type type) => typeInfo =>
if (typeInfo.Kind != JsonTypeInfoKind.Object)
if (!type.IsAssignableFrom(typeInfo.Type))
foreach (var property in typeInfo.Properties)
if (property.Get != null && property.Set == null && property.PropertyType.GetListItemType() is {} itemType)
var method = typeof(JsonExtensions).GetMethod(nameof(JsonExtensions.CreateGetOnlyListPropertySetter),
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static)!;
var genericMethod = method.MakeGenericMethod(new[] { itemType });
var setter = genericMethod.Invoke(null, new object[] { property }) as Action<object, object?>;
static Action<Object,Object?>? CreateGetOnlyListPropertySetter<TItem>(JsonPropertyInfo property)
if (property.Get == null)
(var getter, var name) = (property.Get, property.Name);
var oldValue = (List<TItem>?)getter(obj);
var newValue = value as List<TItem>;
if (newValue == oldValue)
else if (oldValue == null)
throw new JsonException("Cannot populate list ${name} in ${obj}.");
oldValue.AddRange(newValue);
static MemberInfo? GetMemberInfo(this JsonPropertyInfo property) => (property.AttributeProvider as MemberInfo);
static Type? GetListItemType(this Type type) =>
type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>) ? type.GetGenericArguments()[0] : null;
public static async Task Test()
var filename = "hugefile.json";
[{"Name":"My name is 0","TrackingData":[{"Id":0},{"Id":1}]},{"Name":"My name is 1","TrackingData":[{"Id":2},{"Id":3}]},{"Name":"My name is 2","TrackingData":[{"Id":4},{"Id":5}]},{"Name":"My name is 3","TrackingData":[{"Id":6},{"Id":7}]}]
await File.WriteAllTextAsync(filename, json);
var options = new JsonSerializerOptions
TypeInfoResolver = new DefaultJsonTypeInfoResolver
JsonExtensions.InitializeProtoMemberNames(typeof(Person)),
JsonExtensions.InitializeGetOnlyListSetters(typeof(Person))
await using (FileStream fileStream = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 4096, useAsync: true))
IAsyncEnumerable<Person?> people = JsonSerializer.DeserializeAsyncEnumerable<Person?>(fileStream, options);
await foreach (Person? person in people)
Console.WriteLine($"Hello, my name is \"{person?.Name}\", my tracking data is {JsonSerializer.Serialize(person?.TrackingDatas.Select(t => t.Id))}!");
Assert.That(person?.TrackingDatas.Count > 0);
public static async Task Main(string[] args)
Console.WriteLine("Environment version: {0} ({1})", System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription , Environment.Version);
Console.WriteLine("{0} version: {1}", typeof(JsonSerializer).Assembly.GetName().Name, typeof(JsonSerializer).Assembly.FullName);
Console.WriteLine("Failed with unhandled exception: ");
public static void InitializePersonTypeInfo(JsonTypeInfo typeInfo)
foreach (var property in typeInfo.Properties)
var name = property.AttributeProvider?.GetCustomAttributes(typeof(global::ProtoBuf.ProtoMemberAttribute), true)
.OfType<global::ProtoBuf.ProtoMemberAttribute>()
if (property.GetMemberInfo()?.Name == nameof(Person.TrackingDatas) && property.Set == null && property.Get != null)
property.Set = (obj, value) =>
var newValue = value as List<EntityTrackingActivity>;
var oldValue = ((Person)obj).TrackingDatas;
if (newValue == oldValue)
throw new JsonException("Cannot set ${nameof(Person.TrackingDatas)}");
oldValue.AddRange(newValue);