Can System.Text.Json.JsonSerializer serialize collections on a read-only property?(System.Text.Json.JsonSerializer 可以在只读属性上序列化集合吗?)
问题描述
我无法让新的 System.Text.Json
反序列化存储在只读属性上的集合.
考虑这些类:
public class SomeItem {公共字符串标签 { 获取;放;}}公共类 SomeObjectWithItems {公共字符串标签 { 获取;放;}//注意这个属性是只读的,但是它指向的集合是读/写的public ObservableCollection<SomeItem>项目{得到;}= 新的 ObservableCollection<SomeItem>();}
这是 JSON:
{标签":第一组",项目": [{标签":项目 1"},{标签":项目 2"},{标签":项目 3"},{标签":项目 4"}]}
这是我正在运行的代码...
var json = ...;var obj = JsonSerializer.deserialize<SomeObjectWithItems>(json);Debug.WriteLine($"'{obj.label}' 的项目计数:{obj.Items.Count}");
以上输出如下:
第一组"的项目数:0
如果我将 Items
更改为读/写,那么它可以工作,但是我们的许多模型都具有包含可变集合的只读属性,所以我想知道我们是否甚至可以使用它.
注意:Json.NET 正确处理了这个问题,在内部调用现有集合的添加"方法而不是创建一个新集合,但我不知道如何在为我们所有的类编写自定义转换器之外实现这一点已经定义了.
这是为没有 setter 的集合设计的.避免添加到预填充集合的问题(序列化程序不实例化)反序列化器使用替换";语义要求集合有一个 setter.
来源:https://github.com/dotnet/corefx/issues/41433一个>
如果没有 setter 支持添加到集合
https://github.com/dotnet/corefx/issues/39477
我的建议是在这种情况下继续使用 Json.NET
,除非您想编写自定义转换器.
https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-converters-how-to?pivots=dotnet-5-0p>
来自 GitHub 的自定义转换器,我自己没有测试过:
类 MagicConverter : JsonConverterFactory{public override bool CanConvert(Type typeToConvert) =>!typeToConvert.IsAbstract &&typeToConvert.GetConstructor(Type.EmptyTypes) != null &&类型转换.GetProperties().Where(x => !x.CanWrite).Where(x => x.PropertyType.IsGenericType).Select(x => 新{属性 = x,CollectionInterface = x.PropertyType.GetGenericInterfaces(typeof(ICollection<>)).FirstOrDefault()}).Where(x => x.CollectionInterface != null).任何();公共覆盖JsonConverter CreateConverter(类型typeToConvert,JsonSerializerOptions选项)=>(JsonConverter)Activator.CreateInstance(typeof(SuperMagicConverter<>).MakeGenericType(typeToConvert))!;类 SuperMagicConverter<T>: JsonConverter<T>其中T:新(){readonly Dictionary<string, (Type PropertyType, Action<T, object>?Setter, Action<T, object>?Adder)>属性处理程序;公共 SuperMagicConverter(){PropertyHandlers = typeof(T).GetProperties().Select(x => 新{属性 = x,CollectionInterface = !x.CanWrite &&x.PropertyType.IsGenericType ?x.PropertyType.GetGenericInterfaces(typeof(ICollection<>)).FirstOrDefault() : null}).选择(x =>;{var tParam = Expression.Parameter(typeof(T));var objParam = Expression.Parameter(typeof(object));动作<T,对象>?设置器=空;动作<T,对象>?加法器 = 空;类型?属性类型 = 空;如果(x.Property.CanWrite){propertyType = x.Property.PropertyType;setter = Expression.Lambda<Action<T, object>>(表达式.赋值(Expression.Property(tParam, x.Property),Expression.Convert(objParam, propertyType)),参数,对象参数).编译();}别的{if (x.CollectionInterface != null){propertyType = x.CollectionInterface.GetGenericArguments()[0];adder = Expression.Lambda>(表达式.调用(Expression.Property(tParam, x.Property),x.CollectionInterface.GetMethod(添加"),Expression.Convert(objParam, propertyType)),参数,对象参数).编译();}}返回新的{x.Property.Name,二传手,加法器,财产种类};}).Where(x => x.propertyType != null).ToDictionary(x => x.Name, x => (x.propertyType!, x.setter, x.adder));}public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) =>抛出新的 NotImplementedException();公共覆盖 T 读取(参考 Utf8JsonReader 阅读器,类型 typeToConvert,JsonSerializerOptions 选项){var item = new T();而(读者.读()){if (reader.TokenType == JsonTokenType.EndObject){休息;}if (reader.TokenType == JsonTokenType.PropertyName){if (PropertyHandlers.TryGetValue(reader.GetString(), out var handler)){if (!reader.Read()){throw new JsonException($"Bad JSON");}if (handler.Setter != null){handler.Setter(item, JsonSerializer.Deserialize(ref reader, handler.PropertyType, options));}别的{if (reader.TokenType == JsonTokenType.StartArray){而(真){if (!reader.Read()){throw new JsonException($"Bad JSON");}if (reader.TokenType == JsonTokenType.EndArray){休息;}handler.Adder!(item, JsonSerializer.Deserialize(ref reader, handler.PropertyType, options));}}别的{reader.Skip();}}}别的{reader.Skip();}}}归还物品;}}}
用法:
var options = new JsonSerializerOptions { Converters = { new MagicConverter() } };var adsfsdf = System.Text.Json.JsonSerializer.Deserialize<Grrrr>("{"Meow":[1,2,3]}", 选项);var adsfsdf2 = System.Text.Json.JsonSerializer.Deserialize<Grrrr>("{"Meow":null}", options);var adsfsdf3 = System.Text.Json.JsonSerializer.Deserialize<Grrrr>("{"Meow":[1,2,3],"Rawr":"asdf";}", 选项);var adsfsdf4 = System.Text.Json.JsonSerializer.Deserialize<Grrrr>("{"Meow":[1,2,3],"Rawr":null}", 选项);var adsfsdf5 = System.Text.Json.JsonSerializer.Deserialize("{"Meow":[1,2,3],"Rawr":"asdf";,"SubGrr":{"Meow":[1,2,3],"Rawr":"asdf"}}", 选项);
来源:
https://github.com/dotnet/runtime/issues/30258#issuecomment-564847072
I'm having trouble getting the new System.Text.Json
to deserialize collections stored on read-only properties.
Consider these classes:
public class SomeItem {
public string Label { get; set; }
}
public class SomeObjectWithItems {
public string Label { get; set; }
// Note this property is read-only but the collection it points to is read/write
public ObservableCollection<SomeItem> Items { get; }
= new ObservableCollection<SomeItem>();
}
Here's the JSON:
{
"Label": "First Set",
"Items": [
{
"Label": "Item 1"
},
{
"Label": "Item 2"
},
{
"Label": "Item 3"
},
{
"Label": "Item 4"
}
]
}
Here's the code I'm running...
var json = ...;
var obj = JsonSerializer.deserialize<SomeObjectWithItems>(json);
Debug.WriteLine($"Item Count for '{obj.label}': {obj.Items.Count}");
The above outputs the following:
Item Count for 'First Set': 0
If I change Items
to be read/write, then it works, but so many of our models have read-only properties that hold mutable collections so I'm wondering if we can even use this.
Note: Json.NET handles this correctly, internally calling the 'Add' method on the existing collection rather than creating a new one, but I don't know how to achieve that outside of writing custom converters for all the classes we have defined.
This is by design for collections that don't have a setter. To avoid issues with adding to pre-populated collections (that the serializer doesn't instantiate) the deserializer uses "replace" semantics which requires the collection to have a setter.
Source: https://github.com/dotnet/corefx/issues/41433
There is currently an open issue for Support adding to collections if no setter
https://github.com/dotnet/corefx/issues/39477
My recommendation is continue to use Json.NET
in this case unless you want to write a custom converter.
https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-converters-how-to?pivots=dotnet-5-0
Custom converter from GitHub, not tested this myself:
class MagicConverter : JsonConverterFactory
{
public override bool CanConvert(Type typeToConvert) =>
!typeToConvert.IsAbstract &&
typeToConvert.GetConstructor(Type.EmptyTypes) != null &&
typeToConvert
.GetProperties()
.Where(x => !x.CanWrite)
.Where(x => x.PropertyType.IsGenericType)
.Select(x => new
{
Property = x,
CollectionInterface = x.PropertyType.GetGenericInterfaces(typeof(ICollection<>)).FirstOrDefault()
})
.Where(x => x.CollectionInterface != null)
.Any();
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) => (JsonConverter)Activator.CreateInstance(typeof(SuperMagicConverter<>).MakeGenericType(typeToConvert))!;
class SuperMagicConverter<T> : JsonConverter<T> where T : new()
{
readonly Dictionary<string, (Type PropertyType, Action<T, object>? Setter, Action<T, object>? Adder)> PropertyHandlers;
public SuperMagicConverter()
{
PropertyHandlers = typeof(T)
.GetProperties()
.Select(x => new
{
Property = x,
CollectionInterface = !x.CanWrite && x.PropertyType.IsGenericType ? x.PropertyType.GetGenericInterfaces(typeof(ICollection<>)).FirstOrDefault() : null
})
.Select(x =>
{
var tParam = Expression.Parameter(typeof(T));
var objParam = Expression.Parameter(typeof(object));
Action<T, object>? setter = null;
Action<T, object>? adder = null;
Type? propertyType = null;
if (x.Property.CanWrite)
{
propertyType = x.Property.PropertyType;
setter = Expression.Lambda<Action<T, object>>(
Expression.Assign(
Expression.Property(tParam, x.Property),
Expression.Convert(objParam, propertyType)),
tParam,
objParam)
.Compile();
}
else
{
if (x.CollectionInterface != null)
{
propertyType = x.CollectionInterface.GetGenericArguments()[0];
adder = Expression.Lambda<Action<T, object>>(
Expression.Call(
Expression.Property(tParam, x.Property),
x.CollectionInterface.GetMethod("Add"),
Expression.Convert(objParam, propertyType)),
tParam,
objParam)
.Compile();
}
}
return new
{
x.Property.Name,
setter,
adder,
propertyType
};
})
.Where(x => x.propertyType != null)
.ToDictionary(x => x.Name, x => (x.propertyType!, x.setter, x.adder));
}
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) => throw new NotImplementedException();
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var item = new T();
while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndObject)
{
break;
}
if (reader.TokenType == JsonTokenType.PropertyName)
{
if (PropertyHandlers.TryGetValue(reader.GetString(), out var handler))
{
if (!reader.Read())
{
throw new JsonException($"Bad JSON");
}
if (handler.Setter != null)
{
handler.Setter(item, JsonSerializer.Deserialize(ref reader, handler.PropertyType, options));
}
else
{
if (reader.TokenType == JsonTokenType.StartArray)
{
while (true)
{
if (!reader.Read())
{
throw new JsonException($"Bad JSON");
}
if (reader.TokenType == JsonTokenType.EndArray)
{
break;
}
handler.Adder!(item, JsonSerializer.Deserialize(ref reader, handler.PropertyType, options));
}
}
else
{
reader.Skip();
}
}
}
else
{
reader.Skip();
}
}
}
return item;
}
}
}
Usage:
var options = new JsonSerializerOptions { Converters = { new MagicConverter() } };
var adsfsdf = System.Text.Json.JsonSerializer.Deserialize<Grrrr>("{"Meow":[1,2,3]}", options);
var adsfsdf2 = System.Text.Json.JsonSerializer.Deserialize<Grrrr>("{"Meow":null}", options);
var adsfsdf3 = System.Text.Json.JsonSerializer.Deserialize<Grrrr>("{"Meow":[1,2,3],"Rawr":"asdf"}", options);
var adsfsdf4 = System.Text.Json.JsonSerializer.Deserialize<Grrrr>("{"Meow":[1,2,3],"Rawr":null}", options);
var adsfsdf5 = System.Text.Json.JsonSerializer.Deserialize<Grrrr>("{"Meow":[1,2,3],"Rawr":"asdf","SubGrr":{"Meow":[1,2,3],"Rawr":"asdf"}}", options);
Source:
https://github.com/dotnet/runtime/issues/30258#issuecomment-564847072
这篇关于System.Text.Json.JsonSerializer 可以在只读属性上序列化集合吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!
本文标题为:System.Text.Json.JsonSerializer 可以在只读属性上序列化集合吗?
基础教程推荐
- MS Visual Studio .NET 的替代品 2022-01-01
- 如何在 IDE 中获取 Xamarin Studio C# 输出? 2022-01-01
- c# Math.Sqrt 实现 2022-01-01
- 将 XML 转换为通用列表 2022-01-01
- 如何激活MC67中的红灯 2022-01-01
- SSE 浮点算术是否可重现? 2022-01-01
- 有没有办法忽略 2GB 文件上传的 maxRequestLength 限制? 2022-01-01
- 将 Office 安装到 Windows 容器 (servercore:ltsc2019) 失败,错误代码为 17002 2022-01-01
- 为什么Flurl.Http DownloadFileAsync/Http客户端GetAsync需要 2022-09-30
- rabbitmq 的 REST API 2022-01-01