GKa*_*kyi 7 c# json .net-core-3.1 system.text.json
此问题适用System.Text.Json于 .Net Core 3.1 中的自定义反序列化类。
我试图理解为什么自定义反序列化类需要读取到 JSON 流的末尾,即使它已经生成了所需的数据,否则反序列化失败并JsonException以“读取太多或不够”结束。
我通读了System.Text.Json([ 1 ], [ 2 ]) 的Microsoft 文档,但无法弄清楚。
这是文档的示例:
{
"Response": {
"Result": [
{
"Code": "CLF",
"Id": 49,
"Type": "H"
},
{
"Code": "CLF",
"Id": 42,
"Type": "C"
}
]
}
}
Run Code Online (Sandbox Code Playgroud)
DTO 类和反序列化方法定义如下:
public class EntityDto
{
public string Code { get; set; }
public int Id { get; set; }
public string Type { get; set; }
}
// This method is a part of class EntityDtoIEnumerableConverter : JsonConverter<IEnumerable<EntityDto>>
public override IEnumerable<EntityDto> Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options)
{
if (reader.TokenType != JsonTokenType.StartObject)
{
throw new JsonException("JSON payload expected to start with StartObject token.");
}
while ((reader.TokenType != JsonTokenType.StartArray) && reader.Read()) { }
var eodPostions = JsonSerializer.Deserialize<EntityDto[]>(ref reader, options);
// This loop is required to not get JsonException
while (reader.Read()) { }
return new List<EntityDto>(eodPostions);
}
Run Code Online (Sandbox Code Playgroud)
下面是反序列化类的调用方式。
var serializerOptions = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
};
serializerOptions.Converters.Add(new EntityDtoIEnumerableConverter());
HttpResponseMessage message = await httpClient.GetAsync(requestUrl);
message.EnsureSuccessStatusCode();
var contentStream = await msg.Content.ReadAsStreamAsync();
var result = await JsonSerializer.DeserializeAsync<IEnumerable<EntityDto>>(contentStream, serializerOptions);
Run Code Online (Sandbox Code Playgroud)
当while (reader.Read()) { }反序列化方法中的最后一个循环不存在或被注释掉时,最后一次调用将await JsonSerializer.DeserializeAsync<...失败JsonException,并以 结尾read too much or not enough。谁能解释为什么?或者有没有更好的方法来编写这种反序列化?
更新了第二个代码块以使用EntityDtoIEnumerableConverter.
小智 8
只是提醒任何使用扩展方法或任何外部调用的人,以确保您通过Utf8JsonReader引用传递,否则即使您似乎正确地推进了阅读器,您也可能会遇到一些意外的错误。使用:
public static IReadOnlyDictionary<string, string> ReadObjectDictionary(ref this Utf8JsonReader reader)
Run Code Online (Sandbox Code Playgroud)
...并不是...
public static IReadOnlyDictionary<string, string> ReadObjectDictionary(this Utf8JsonReader reader)
Run Code Online (Sandbox Code Playgroud)
读取对象时,JsonConverter<T>.Read()必须将Utf8JsonReader定位在对象的EndObject标记上留下它最初定位的位置。(对于数组,则EndArray是原始数组的 。)在编写Read()解析 JSON 的多个级别的方法时,可以通过CurrentDepth在进入时记住读取器的 ,然后读取直到EndObject在相同深度找到an来完成。
由于您的EntityDtoIEnumerableConverter.Read()方法似乎试图降低 JSON 令牌层次结构,直到遇到数组,然后将数组反序列化为一个EntityDto[](基本上剥离"Response"和"Result"包装器属性),您的代码可以重写如下:
public override IEnumerable<EntityDto> Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options)
{
if (reader.TokenType != JsonTokenType.StartObject)
{
throw new JsonException("JSON payload expected to start with StartObject token.");
}
List<EntityDto> list = null;
var startDepth = reader.CurrentDepth;
while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndObject && reader.CurrentDepth == startDepth)
return list;
if (reader.TokenType == JsonTokenType.StartArray)
{
if (list != null)
throw new JsonException("Multiple lists encountered.");
var eodPostions = JsonSerializer.Deserialize<EntityDto[]>(ref reader, options);
(list = new List<EntityDto>(eodPostions.Length)).AddRange(eodPostions);
}
}
throw new JsonException(); // Truncated file or internal error
}
Run Code Online (Sandbox Code Playgroud)
笔记:
在您的原始代码中,您在数组反序列化后立即返回。由于JsonSerializer.Deserialize<EntityDto[]>(ref reader, options)仅将读取器推进到嵌套数组的末尾,因此您从未将读取器推进到所需的对象末尾。这导致了您看到的异常。(当当前对象是根对象时,前进到 JSON 流的末尾似乎也有效,但不适用于嵌套对象。)
文档文章中当前没有显示任何转换器如何在您链接的.NET 中编写用于 JSON 序列化(编组)的自定义转换器尝试将多个级别的 JSON 平展为单个 .Net 对象,因此需要跟踪当前的深度似乎没有在实践中出现。
演示小提琴在这里。
| 归档时间: |
|
| 查看次数: |
1742 次 |
| 最近记录: |