如何正确跳过 JsonConverter 中的未知属性<T>。Read() system .Text.Json 的方法?



使用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。

相关内容

最新更新