簡體   English   中英

System.Text.Json 反序列化失敗並出現 JsonException “讀得太多或不夠”

[英]System.Text.Json deserialization fails with JsonException “read to much or not enough”

此問題適用於System.Text.Json in.Net Core 3.1 的自定義反序列化類。

我試圖理解為什么自定義反序列化 class 需要讀取到 JSON stream 的末尾,即使它已經產生了所需的數據太多或沒有以“ JsonException ”結尾

我通讀了System.Text.Json ([ 1 ], [ 2 ]) 的 Microsoft 文檔,但無法弄清楚。

以下是該文檔的示例:

{
    "Response": {
        "Result": [
            {
                "Code": "CLF",
                "Id": 49,
                "Type": "H"
            },
            {
                "Code": "CLF",
                "Id": 42,
                "Type": "C"
            }
        ]
    }
}

DTO class 和反序列化方法定義如下:

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);
}

下面是如何調用反序列化 class。

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);

當反序列化方法中的最后一個循環while (reader.Read()) { }不存在或被注釋掉時,最后一次調用await JsonSerializer.DeserializeAsync<...失敗並出現JsonException ,以read too much or not enough結束。 誰能解釋為什么? 還是有更好的方法來編寫這種反序列化?

更新了第二個代碼塊以使用EntityDtoIEnumerableConverter

讀取 object 時, JsonConverter<T>.Read()必須將Utf8JsonReader定位在 object 的EndObject令牌上,它原來的位置 (對於 arrays,原始數組的EndArray 。)當編寫一個Read()方法來解析 JSON 的多個級別時,這可以通過在進入時記住讀取器的CurrentDepth來完成,然后讀取直到找到EndObject在同一深度。

由於您的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
}

筆記:

  • 在您的原始代碼中,您在數組反序列化后立即返回。 由於JsonSerializer.Deserialize<EntityDto[]>(ref reader, options)僅將閱讀器推進到嵌套數組的末尾,因此您從未將閱讀器推進到所需的 object 末端。 這導致了您看到的異常。 (Advancing until the end of the JSON stream also seems to have worked when the current object was the root object , but would not have worked for nested objects.)

  • None of the converters currently shown in the documentation article How to write custom converters for JSON serialization (marshalling) in .NET to which you linked attempt to flatten multiple levels of JSON into a single.Net object as you are doing, so the need to track目前的深度在實踐中似乎沒有出現。

演示小提琴在這里

只是提醒任何使用擴展方法或任何外部調用的人,以確保您通過引用傳遞Utf8JsonReader ,否則即使您似乎正確地推進了閱讀器,您也可能會遇到一些意外錯誤。 利用:

public static IReadOnlyDictionary<string, string> ReadObjectDictionary(ref this Utf8JsonReader reader)

...並不是...

public static IReadOnlyDictionary<string, string> ReadObjectDictionary(this Utf8JsonReader reader)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM