C# 如何使用System.Text.Json处理同一属性的单个项和数组?
我试图反序列化一些JSON,其中包含一个值,有时是数组,有时是单个项。我怎样才能用和来做这件事?(这个问题的灵感来自于for Json.NET by。) 我已收到以下JSON:C# 如何使用System.Text.Json处理同一属性的单个项和数组?,c#,json,system.text.json,C#,Json,System.text.json,我试图反序列化一些JSON,其中包含一个值,有时是数组,有时是单个项。我怎样才能用和来做这件事?(这个问题的灵感来自于for Json.NET by。) 我已收到以下JSON: [ { "email": "john.doe@sendgrid.com", "timestamp": 1337966815, "category": [ "newuser", "transactional" ], "event": "open" },
[
{
"email": "john.doe@sendgrid.com",
"timestamp": 1337966815,
"category": [
"newuser",
"transactional"
],
"event": "open"
},
{
"email": "jane.doe@sendgrid.com",
"timestamp": 1337966815,
"category": "olduser",
"event": "open"
}
]
我想将其反序列化为以下类型的列表:
class Item
{
public string Email { get; set; }
public int Timestamp { get; set; }
public string Event { get; set; }
public List<string> Category { get; set; }
}
出现异常是因为“category”
的值有时是单个字符串,有时是字符串数组。如何使用System.Text.Json
反序列化此类属性?受和的启发,您可以创建一个泛型,检查传入的Json值是否为数组,如果不是,则反序列化类型为T
的项,并返回封装在适当列表中的项。更好的是,您可以为序列化图中遇到的所有列表类型list
创建这样的转换器
首先,定义以下转换器和转换器工厂:
public class SingleOrArrayConverter<TItem> : SingleOrArrayConverter<List<TItem>, TItem>
{
public SingleOrArrayConverter() : this(true) { }
public SingleOrArrayConverter(bool canWrite) : base(canWrite) { }
}
public class SingleOrArrayConverterFactory : JsonConverterFactory
{
public bool CanWrite { get; }
public SingleOrArrayConverterFactory() : this(true) { }
public SingleOrArrayConverterFactory(bool canWrite) => CanWrite = canWrite;
public override bool CanConvert(Type typeToConvert)
{
var itemType = GetItemType(typeToConvert);
if (itemType == null)
return false;
if (itemType != typeof(string) && typeof(IEnumerable).IsAssignableFrom(itemType))
return false;
if (typeToConvert.GetConstructor(Type.EmptyTypes) == null || typeToConvert.IsValueType)
return false;
return true;
}
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
{
var itemType = GetItemType(typeToConvert);
var converterType = typeof(SingleOrArrayConverter<,>).MakeGenericType(typeToConvert, itemType);
return (JsonConverter)Activator.CreateInstance(converterType, new object [] { CanWrite });
}
static Type GetItemType(Type type)
{
// Quick reject for performance
if (type.IsPrimitive || type.IsArray || type == typeof(string))
return null;
while (type != null)
{
if (type.IsGenericType)
{
var genType = type.GetGenericTypeDefinition();
if (genType == typeof(List<>))
return type.GetGenericArguments()[0];
// Add here other generic collection types as required, e.g. HashSet<> or ObservableCollection<> or etc.
}
type = type.BaseType;
}
return null;
}
}
public class SingleOrArrayConverter<TCollection, TItem> : JsonConverter<TCollection> where TCollection : class, ICollection<TItem>, new()
{
public SingleOrArrayConverter() : this(true) { }
public SingleOrArrayConverter(bool canWrite) => CanWrite = canWrite;
public bool CanWrite { get; }
public override TCollection Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
switch (reader.TokenType)
{
case JsonTokenType.Null:
return null;
case JsonTokenType.StartArray:
var list = new TCollection();
while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndArray)
break;
list.Add(JsonSerializer.Deserialize<TItem>(ref reader, options));
}
return list;
default:
return new TCollection { JsonSerializer.Deserialize<TItem>(ref reader, options) };
}
}
public override void Write(Utf8JsonWriter writer, TCollection value, JsonSerializerOptions options)
{
if (CanWrite && value.Count == 1)
{
JsonSerializer.Serialize(writer, value.First(), options);
}
else
{
writer.WriteStartArray();
foreach (var item in value)
JsonSerializer.Serialize(writer, item, options);
writer.WriteEndArray();
}
}
}
或直接使用以下方法将特定转换器添加到选项或数据模型:
转换器仍将被使用,但将无条件生成默认序列化
2d
或nD
集合,如List
。对于数组和只读集合,它也没有实现Read()
方法
现有[JsonConverter]模型的一个限制是,它必须在反序列化期间“预读”,以完全填充缓冲区,直至达到当前JSON级别。只有在调用了async+streamJsonSerializer
反序列化方法,并且仅当该转换器的当前JSON以StartArray或StartObject标记启动时,才会发生此预读
因此,使用此转换器反序列化可能非常大的阵列可能会对性能产生负面影响
正如在同一个线程中所讨论的,转换器API可能在System.Text.Json-5.0中重新设计,以完全支持数组和对象转换器的async
反序列化,这意味着在最终发布(不再标记为“Core”)时重写转换器可能会带来好处演示小提琴。最简单的方法是使用“对象”类型。见下面的例子
public class Example
{
public string Email { get; set; }
public int Timestamp { get; set; }
public string Event { get; set; }
[JsonPropertyName("category")]
public object CategoryObjectOrArray { get; set; }
[JsonIgnore]
public List<string> Category
{
get
{
if (CategoryObjectOrArray is JsonElement element)
{
switch (element.ValueKind)
{
case JsonValueKind.Array:
return JsonSerializer.Deserialize<List<string>>(element.GetRawText());
case JsonValueKind.String:
return new List<string> { element.GetString() };
}
}
return null;
}
}
}
公共类示例
{
公共字符串电子邮件{get;set;}
公共int时间戳{get;set;}
公共字符串事件{get;set;}
[JsonPropertyName(“类别”)]
公共对象类别对象数组{get;set;}
[JsonIgnore]
公开名单类别
{
得到
{
if(CategoryObjectArray是JsonElement元素)
{
开关(element.ValueKind)
{
案例JsonValueKind.Array:
返回JsonSerializer.Deserialize(element.GetRawText());
案例JsonValueKind.String:
返回新列表{element.GetString()};
}
}
返回null;
}
}
}
非常感谢您。这是我发现的第一个使用纯JSON.Net的有效答案。你帮我省了几个小时把我的项目转换成Newtonsoft。
var options = new JsonSerializerOptions
{
Converters = { new SingleOrArrayConverterFactory() },
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
};
var list = JsonSerializer.Deserialize<List<Item>>(json, options);
class Item
{
public string Email { get; set; }
public int Timestamp { get; set; }
public string Event { get; set; }
[JsonConverter(typeof(SingleOrArrayConverter<string>))]
public List<string> Category { get; set; }
}
[JsonConverter(typeof(SingleOrArrayConverter<ObservableCollection<string>, string>))]
public ObservableCollection<string> Category { get; set; }
Converters = { new SingleOrArrayConverterFactory(canWrite: false) }
public class Example
{
public string Email { get; set; }
public int Timestamp { get; set; }
public string Event { get; set; }
[JsonPropertyName("category")]
public object CategoryObjectOrArray { get; set; }
[JsonIgnore]
public List<string> Category
{
get
{
if (CategoryObjectOrArray is JsonElement element)
{
switch (element.ValueKind)
{
case JsonValueKind.Array:
return JsonSerializer.Deserialize<List<string>>(element.GetRawText());
case JsonValueKind.String:
return new List<string> { element.GetString() };
}
}
return null;
}
}
}