受Brian Rogers的这个答案和如何使用 JSON.net 处理同一属性的单个项目和数组的其他答案的启发,您可以创建一个泛型来检查传入的 JSON 值是否是一个数组,如果不是,反序列化一个类型的项目并返回包装在适当列表中的项目。更好的是,您可以为序列化图中遇到的所有列表类型创建一个这样的转换器。JsonConverter<List<T>>
T
JsonConverterFactory
List<T>
首先,定义以下转换器和转换器工厂:
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();
}
}
}
然后将转换器工厂添加到JsonSerializerOptions.Converters
反序列化之前:
var options = new JsonSerializerOptions
{
Converters = { new SingleOrArrayConverterFactory() },
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
};
var list = JsonSerializer.Deserialize<List<Item>>(json, options);
或者直接使用以下方式将特定转换器添加到选项或数据模型JsonConverterAttribute
:
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; }
}
如果您的数据模型使用其他类型的集合,例如ObservableCollection<string>
,您可以应用较低级别的转换器SingleOrArrayConverter<TCollection, TItem>
,如下所示:
[JsonConverter(typeof(SingleOrArrayConverter<ObservableCollection<string>, string>))]
public ObservableCollection<string> Category { get; set; }
笔记:
如果您希望转换器仅在反序列化期间应用,请传递canWrite: false
给参数化构造函数:
Converters = { new SingleOrArrayConverterFactory(canWrite: false) }
转换器仍会被使用,但会无条件地生成默认序列化。
该转换器不适用于锯齿状2d
或nD
集合,例如List<List<string>>
. 它也不适用于数组和只读集合。
根据序列化器支持更简单的对象和集合转换器#1562,因为JsonConverter<T>
缺少异步Read()
方法,
现有 [JsonConverter] 模型的一个限制是它必须在反序列化期间“预读”以完全填充缓冲区直到当前 JSON 级别。仅当调用async+stream JsonSerializer
反序列化方法并且仅当该转换器的当前 JSON 以 StartArray 或 StartObject 令牌开头时, 才会发生这种预读。
因此,使用此转换器反序列化可能非常大的阵列可能会对性能产生负面影响。
如同一线程中所讨论的,转换器 API 可能会在 System.Text.Json - 5.0 中重新设计,以完全支持async
数组和对象转换器的反序列化,这意味着此转换器可能会受益于在.NET 5(不再标记为“核心”)最终发布。
演示小提琴在这里。