使用System.Text.Json
,我正在编写一个自定义的JsonConverter<T>.Read()
反序列化方法来反序列化JSON对象。该方法从JSON中读取每个属性名称和值,并将结果手动分配到反序列化对象中,如Microsoft文档中显示的示例所示。NET:支持多态反序列化。然而,在我的情况下,JSON对象有时会包含我想要忽略的未知属性。当这种情况发生时,文档中的示例代码将引发异常:
JsonException: The converter 'PersonConverter' read too much or not enough.
如何正确跳过自定义对象反序列化程序中的未知属性?
下面是一个最小的例子。假设我有以下数据模型:
public class Person
{
public string Name { get; set; }
}
和以下转换器:
public class PersonConverter : JsonConverter<Person>
{
public override bool CanConvert(Type typeToConvert) =>
typeof(Person).IsAssignableFrom(typeToConvert);
public override Person Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.Null)
return null;
if (reader.TokenType != JsonTokenType.StartObject)
throw new JsonException();
var person = new Person();
while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndObject)
return person;
if (reader.TokenType == JsonTokenType.PropertyName)
{
var propertyName = reader.GetString();
reader.Read();
switch (propertyName)
{
case "Name":
person.Name = reader.GetString();
break;
}
}
}
throw new JsonException();
}
public override void Write(Utf8JsonWriter writer, Person person, JsonSerializerOptions options) => throw new NotImplementedException();
}
尝试反序列化以下JSON:时
{"Name":"my name", "ExtraData" : {"Value" : "extra value"} }
通过:
var json = @"{""Name"":""my name"", ""ExtraData"" : {""Value"" : ""extra value""} }";
var options = new JsonSerializerOptions
{
Converters = { new PersonConverter() },
};
var person = JsonSerializer.Deserialize<Person>(json, options);
引发以下异常:
System.Text.Json.JsonException: The converter 'PersonConverter' read too much or not enough. Path: $ | LineNumber: 0 | BytePositionInLine: 58. at System.Text.Json.ThrowHelper.ThrowJsonException_SerializationConverterRead(JsonConverter converter) at System.Text.Json.Serialization.JsonConverter`1.VerifyRead(JsonTokenType tokenType, Int32 depth, Int64 bytesConsumed, Boolean isValueConverter, Utf8JsonReader& reader) at System.Text.Json.Serialization.JsonConverter`1.TryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value) at System.Text.Json.Serialization.JsonConverter`1.ReadCore(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state) at System.Text.Json.JsonSerializer.ReadCore[TValue](JsonConverter jsonConverter, Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state) at System.Text.Json.JsonSerializer.ReadCore[TValue](Utf8JsonReader& reader, Type returnType, JsonSerializerOptions options) at System.Text.Json.JsonSerializer.Deserialize[TValue](String json, Type returnType, JsonSerializerOptions options) at System.Text.Json.JsonSerializer.Deserialize[TValue](String json, JsonSerializerOptions options)
如何更正转换器的逻辑以避免此异常?
一个简化的演示小提琴在这里:小提琴#1。
文档中的转换器演示抛出了相同的异常:fiddle#2。
当在JsonConverter<T>.Read()
内部遇到未知属性时,必须调用Utf8JsonReader.Skip()
以根据需要推进读取器,从而忽略未知值及其子级。这种方法
跳过当前JSON令牌的子级。
对Skip()
的调用应该作为默认情况添加到属性名称切换语句中,如下所示:
public override Person Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.Null)
return null;
else if (reader.TokenType != JsonTokenType.StartObject)
throw new JsonException();
var person = new Person();
while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndObject)
return person;
else if (reader.TokenType != JsonTokenType.PropertyName)
throw new JsonException();
var propertyName = reader.GetString();
reader.Read(); // Advance the reader to the property value.
switch (propertyName)
{
case "Name":
person.Name = reader.GetString();
break;
default:
reader.Skip(); // Advance the reader as required to skip the unknown value
break;
}
}
throw new JsonException(); // Malformed truncated file
}
注:
与其检查当前令牌是否是
Read()
循环内的属性名,不如在不是的情况下抛出异常。格式良好的JSON对象被定义为由名称/值对组成,如果令牌不是属性名,则可能表明读取器中存在可能导致数据丢失的错误。在实践中,当未知属性的值是基元(数字、字符串、布尔值或null(时,文档中的代码会成功跳过这些属性,而当它们的值是对象或数组时,则会失败。
可以在这里找到固定简化转换器的演示:fix#1。
文档转换器的固定版本可以在这里找到:fix#2。