我有巨大的 json 文件,所以我使用了下面的代码,实际上它有效。
using (FileStream? fileStream = new FileStream("hugefile.json", FileMode.Open))
{
IAsyncEnumerable<Person?> people = JsonSerializer.DeserializeAsyncEnumerable<Person?>(fileStream);
await foreach (Person? person in people)
{
Console.WriteLine($"Hello, my name is {person.Name}!");
}
}
我的问题出在使用 protobuf 生成的 Person 类中。它包含一个名为 TrackingDatas 的属性,并且具有 ProtoMember 属性,您可以在下面看到。但在我巨大的 json 中,属性名称是 TrackingData。我想反序列化它,但没有从 ProtoBuf 类中添加或删除任何内容。有人知道吗?
[global::ProtoBuf.ProtoMember(2, Name = @"TrackingData")]
public global::System.Collections.Generic.List<EntityTrackingActivity> TrackingDatas { get; } = new global::System.Collections.Generic.List<EntityTrackingActivity>();
我尝试了下面的代码来更改属性名称,但它对我不起作用。
public class CustomNamingPolicy : JsonNamingPolicy
{
private readonly Dictionary<string, string> NameMapping = new Dictionary<string, string>()
{
[nameof(OASISLevel2TrackingPacket.EntityTracking.TrackingDatas)] = "TrackingData"
};
public override string ConvertName(string name)
{
var a = NameMapping.GetValueOrDefault(name, name);
return a;
}
}
var options = new JsonSerializerOptions()
{
PropertyNamingPolicy = new CustomNamingPolicy()
};
using (FileStream? fileStream = new FileStream("hugefile.json", FileMode.Open))
{
IAsyncEnumerable<Person?> people = JsonSerializer.DeserializeAsyncEnumerable<Person?>(fileStream, options);
await foreach (Person? person in people)
{
Console.WriteLine($"Hello, my name is {person.Name}!");
}
}
最佳答案
这里有两个问题:
属性名称
TrackingDatas
与 JSON 名称"TrackingData"
不匹配,但是你的类型是由 Protobuf 自动生成的,所以你不能轻易修改它。您已通过添加
PropertyNamingPolicy
正确修复了此问题重新映射名为TrackingDatas
的所有属性(所有类型)至"TrackingData"
.您的收藏属性(property)
public List<EntityTrackingActivity> TrackingDatas { get; } = new ();
是只读,但 System.Text.Json 不支持在 .NET 8 之前反序列化只读集合属性。
有关确认,请参阅 Can System.Text.Json.JsonSerializer serialize collections on a read-only property? 和 What's new in .NET 8: Read-only properties 。
那么,您有什么选择来解决第二个问题?
首先,您可以反序列化为一些适当的 PersonDTO
然后将 DTO 映射到 Person
例如,使用 AutoMapper。
其次在 .NET 5 及更高版本中,如果您自动生成 Person
类被声明为 partial
,例如:
[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; }
}
您可以添加带有 List<EntityTrackingActivity> trackingDatas
的参数化构造函数参数并用 [JsonConstructor]
标记它像这样:
public partial class Person
{
public Person() { } // Add parameterless constructor if not already auto-generated by protobuf
[JsonConstructor]
public Person(List<EntityTrackingActivity> trackingDatas) => this.TrackingDatas.AddRange(trackingDatas ?? throw new ArgumentNullException(nameof(trackingDatas)));
}
现在您将能够反序列化 TrackingDatas
属性。
演示 fiddle #1 here .
第三,在 .NET 7 及更高版本中,Microsoft 添加了以编程方式自定义 serialization contract 的功能。 System.Text.Json 为每个 .NET 类型创建。使用此 API,您可以添加 typeInfo modifier将所有 JSON 属性名称映射到 ProtoMemberAttribute.Name
的值,并将合成 setter 添加到仅获取 List<T>
特性。这种方法完全避免了以任何方式修改类型的需要。
首先添加以下扩展方法:
public static partial class JsonExtensions
{
public static Action<JsonTypeInfo> InitializeProtoMemberNames(Type type) => typeInfo =>
{
if (typeInfo.Kind != JsonTypeInfoKind.Object)
return;
if (!type.IsAssignableFrom(typeInfo.Type))
return;
// Fix property name(s).
foreach (var property in typeInfo.Properties)
{
// Set the JSON property name to be the same as ProtoMemberAttribute.Name
var name = property.AttributeProvider?.GetCustomAttributes(typeof(global::ProtoBuf.ProtoMemberAttribute), true)
.OfType<global::ProtoBuf.ProtoMemberAttribute>()
.FirstOrDefault()
?.Name;
if (name != null)
property.Name = name;
}
};
public static Action<JsonTypeInfo> InitializeGetOnlyListSetters(Type type) => typeInfo =>
{
if (typeInfo.Kind != JsonTypeInfoKind.Object)
return;
if (!type.IsAssignableFrom(typeInfo.Type))
return;
// Add synthetic list setters.
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?>;
property.Set = setter;
}
}
};
static Action<Object,Object?>? CreateGetOnlyListPropertySetter<TItem>(JsonPropertyInfo property)
{
if (property.Get == null)
return null;
(var getter, var name) = (property.Get, property.Name);
return (obj, value) =>
{
var oldValue = (List<TItem>?)getter(obj);
var newValue = value as List<TItem>;
if (newValue == oldValue)
return;
else if (oldValue == null)
throw new JsonException("Cannot populate list ${name} in ${obj}.");
oldValue.Clear();
if (newValue != null)
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;
}
然后反序列化,例如如下:
var options = new JsonSerializerOptions
{
TypeInfoResolver = new DefaultJsonTypeInfoResolver
{
Modifiers = {
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))}!");
}
}
注释:
如 Asynchronous streams and disposables 中所述,
await using
编写异步代码时应使用语法来处理文件流。为了真正启用异步
FileStream
访问、通过useAsync : true
到FileStream
构造函数。请参阅docs讨论可能的性能影响。CustomNamingPolicy
不再需要这种方法。
演示 fiddle #2 here .
最后,在 .NET 8 及更高版本中,通过设置开箱即用地支持填充预分配的只读集合属性
JsonSerializerOptions.PreferredObjectCreationHandling =
JsonObjectCreationHandling.Populate;
因此InitializeGetOnlyListSetters()
不再需要,您的代码可以简化,例如如下:
var options = new JsonSerializerOptions
{
PreferredObjectCreationHandling = JsonObjectCreationHandling.Populate,
TypeInfoResolver = new DefaultJsonTypeInfoResolver()
.WithAddedModifier(JsonExtensions.InitializeProtoMemberNames(typeof(Person))),
};
await using var fileStream = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 4096, useAsync: true);
var people = JsonSerializer.DeserializeAsyncEnumerable<Person?>(fileStream, options);
await foreach (var person in people)
{
// Code to process each person
}
有关详细信息,请参阅 Populate initialized properties 。
演示 fiddle #3 here .
关于c# - 我们如何使用具有不同属性名称的 JsonSerializer.DeserializeAsyncEnumerable 从巨大的 json 反序列化为 ProtoBuf,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/76438672/