如何在 System.Text.Json 的 JsonConverter<T>.Read() 方法中正确跳过未知的 属性?
How can I correctly skip an unknown property inside the JsonConverter<T>.Read() method of System.Text.Json?
使用System.Text.Json
, I am writing a custom JsonConverter<T>.Read()
deserialization method to deserialize a JSON object. The method reads each property name and value from the JSON and manually assigns the results into the deserialized object, along the lines of the example shown in the Microsoft documentation How to write custom converters for JSON serialization (marshalling) in .NET: Support polymorphic deserialization。但是,在我的例子中,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)
如何更正转换器的逻辑以避免此异常?
这里有一个简化的演示 fiddle:fiddle #1。
文档中的转换器演示抛出相同的异常在这里:fiddle #2。
当在 JsonConverter<T>.Read()
中遇到未知的 属性 时,必须调用 Utf8JsonReader.Skip()
以按要求推进 reader 以忽略未知值及其子值。这个方法
Skips the children of the current JSON token.
应将对 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 对象被定义为由 name/value 对组成,如果令牌不是 属性 名称,则可能表明 reader 中的错误可能导致导致数据丢失。
实际上,当未知属性的值为原始值(数字、字符串、布尔值或 null)时,文档中的代码会成功跳过它们,而当它们的值为对象或数组时会失败。
可在此处找到固定简化转换器的演示:fix #1。
可以在此处找到文档转换器的固定版本:fix #2。
使用System.Text.Json
, I am writing a custom JsonConverter<T>.Read()
deserialization method to deserialize a JSON object. The method reads each property name and value from the JSON and manually assigns the results into the deserialized object, along the lines of the example shown in the Microsoft documentation How to write custom converters for JSON serialization (marshalling) in .NET: Support polymorphic deserialization。但是,在我的例子中,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)
如何更正转换器的逻辑以避免此异常?
这里有一个简化的演示 fiddle:fiddle #1。
文档中的转换器演示抛出相同的异常在这里:fiddle #2。
当在 JsonConverter<T>.Read()
中遇到未知的 属性 时,必须调用 Utf8JsonReader.Skip()
以按要求推进 reader 以忽略未知值及其子值。这个方法
Skips the children of the current JSON token.
应将对 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 对象被定义为由 name/value 对组成,如果令牌不是 属性 名称,则可能表明 reader 中的错误可能导致导致数据丢失。实际上,当未知属性的值为原始值(数字、字符串、布尔值或 null)时,文档中的代码会成功跳过它们,而当它们的值为对象或数组时会失败。
可在此处找到固定简化转换器的演示:fix #1。
可以在此处找到文档转换器的固定版本:fix #2。