我正在尝试反序列化一些JSON,该JSON包含的值有时是数组,有时是单个项目。如何使用System.Text.Json
和JsonSerializer
执行此操作? (此问题的灵感来自this question的Robert McLaws对Json.NET的启发。)
我已收到以下JSON:
[
{
"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; }
}
使用以下代码:
var options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
};
var list = JsonSerializer.Deserialize<List<Item>>(json, options);
但是,当我这样做时,会出现以下异常:
System.Text.Json.JsonException: The JSON value could not be converted to System.String. Path: > $[1].category | LineNumber: 13 | BytePositionInLine: 25. at System.Text.Json.ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(Type propertyType) at System.Text.Json.JsonPropertyInfo.Read(JsonTokenType tokenType, ReadStack& state, Utf8JsonReader& reader) at System.Text.Json.JsonSerializer.ReadCore(JsonSerializerOptions options, Utf8JsonReader& reader, ReadStack& readStack) at System.Text.Json.JsonSerializer.ReadCore(Type returnType, JsonSerializerOptions options, Utf8JsonReader& reader) at System.Text.Json.JsonSerializer.Deserialize(String json, Type returnType, JsonSerializerOptions options) at System.Text.Json.JsonSerializer.Deserialize[TValue](String json, JsonSerializerOptions options)
发生异常是因为"category"
的值有时是单个字符串,有时是字符串数组。如何使用System.Text.Json
反序列化这样的属性?
答案 0 :(得分:0)
受this answer,Brian Rogers和other answers启发, How to handle both a single item and an array for the same property using JSON.net ,您可以创建一个通用JsonConverter<List<T>>
来检查是否传入的JSON值是一个数组,如果不是,则反序列化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>>
)实现。数组和只读集合也没有实现它。
演示小提琴here。
答案 1 :(得分:0)
最简单的方法是使用“对象”类型。参见下面的示例
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;
}
}
}