JsonConverter - WebApi - 区分大小写 - 多态

JsonConverter - WebApi - Case Sensitivity - Polymorphic

我正在使用 Json转换器来处理多态集合:

class ItemBatch
{
    List<ItemBase> Items { get; set; }
}

// For type discrimination of ItemBase
class ItemTypes
{
    public int Value { get; set; }
}

[JsonConverter(typeof(ItemConverter))]
abstract class ItemBase
{
    public abstract ItemTypes Type { get; set; }
}

class WideItem : ItemBase
{
    public override ItemTypes Type => 1;
    public decimal Width { get; set; }
}

class HighItem : ItemBase
{
    public override ItemTypes Type => 2;
    public decimal Height { get; set; }
}

class ItemConverter : JsonConverter<ItemBase>
{
    public override ItemBase? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        using (JsonDocument jsonDoc = JsonDocument.ParseValue(ref reader));

        int type = jsonDoc.RootElement.GetProperty("Type").GetProperty("Value").GetInt32();

        // deserialize depending on type.
    }
}

我正在使用 Blazor 并将 ItemBatch 存储在 IndexedDb 中,然后稍后再次检索并发送到 WebAPI。

序列化到 IndexedDb 和从 IndexedDb 反序列化工作正常。

但是,当我尝试将 ItemBatch 发送到 WebAPI 时,出现错误:

Exception thrown: 'System.Collections.Generic.KeyNotFoundException' in System.Text.Json.dll An exception of type 'System.Collections.Generic.KeyNotFoundException' occurred in System.Text.Json.dll but was not handled in user code The given key was not present in the dictionary.

通过查看各种值,我怀疑是区分大小写的问题。的确,如果我改变:

int type = jsonDoc.RootElement.GetProperty("Type").GetProperty("Value").GetInt32();

int type;

try
{
    type = jsonDoc.RootElement.GetProperty("Type").GetProperty("Value").GetInt32();
}
catch (Exception)
{
    type = jsonDoc.RootElement.GetProperty("type").GetProperty("value").GetInt32();
}

然后我克服了这个错误并调用了我的 WebAPI。

我错过了什么允许我对 IndexedDb 进行序列化/反序列化,但是 WebApi Json 转换存在区分大小写的问题。

Newtonsoft 不区分大小写。

有了 System.Text.Json 你必须再拉动一些杠杆。

https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-migrate-from-newtonsoft-how-to?pivots=dotnet-6-0#case-insensitive-deserialization

Case-insensitive deserialization During deserialization, Newtonsoft.Json does case-insensitive property name matching by default. The System.Text.Json default is case-sensitive, which gives better performance since it's doing an exact match. For information about how to do case-insensitive matching, see Case-insensitive property matching.

另见下面 URL:

https://makolyte.com/csharp-case-sensitivity-in-json-deserialization/

这里有一个可能的处理方法:

https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-character-casing

above url is ::: How to enable case-insensitive property name matching with System.Text.Json

https://docs.microsoft.com/en-us/dotnet/api/system.text.json.jsonelement.getproperty?view=net-6.0#system-text-json-jsonelement-getproperty(system-string)

Remarks
Property name matching is performed as an ordinal, case-sensitive comparison.

我不认为你可以通过“自己动手”来克服这种行为。

但也许你可以追这个:

https://docs.microsoft.com/en-us/dotnet/api/system.text.json.jsonserializeroptions.propertynamecaseinsensitive?view=net-6.0#system-text-json-jsonserializeroptions-propertynamecaseinsensitive

但这似乎没有“自己动手”。

我的结论是,使用默认序列化,JsonSerializerOptions 不会为 PropertyNamingPolicy 设置值“CamelCase”,但 HttpClient 和 WebApi 请求管道会:

PropertyNamingPolicy = JsonNamingPolicy.CamelCase;

这意味着当我序列化和反序列化到 IndexedDb 时,默认序列化将 Json 保留为 Pascal Case。

我的自定义 JsonConverter 使用传递到 Read 和 Write 方法的选项参数,因此在使用 IndexedDb 时在客户端上使用 Pascal 大小写。

但是,当 HttpClient 和 WebApi 请求管道调用同一个 JsonConverter 时,选项设置为:

PropertyNamePolicy = JsonNamingPolicy.CamelCase.

并且在尝试解析为 Json 文档时,内容现在采用驼峰式大小写,然后我使用 Pascal 大小写假设阅读 Json 文档然后失败了。

我的问题的答案是更新写入方法如下:

public override void Write(Utf8JsonWriter writer, SaleCommandBase value, JsonSerializerOptions options)
{
    JsonSerializerOptions newOptions = new JsonSerializerOptions(options) { PropertyNamingPolicy = null };

    JsonSerializer.Serialize(writer, (object)value, newOptions);
}

这会强制序列化在所有情况下都使用 Pascal 大小写,无论是客户端上的本地序列化(写入 IndexedDb 时)还是发送到 WebAPI 时在 HttpClient 中序列化。

同理,在read方法中:

    using (JsonDocument jsonDoc = JsonDocument.ParseValue(ref reader))
    {
        int type = jsonDoc.RootElement.GetProperty("Type").GetProperty("Value").GetInt32();

        newOptions = new JsonSerializerOptions(options) { PropertyNamingPolicy = null };

        return type switch
        {
            1 => jsonDoc.RootElement.Deserialize<WideItem>(newOptions),
            2 => jsonDoc.RootElement.Deserialize<HighItem>(newOptions),
            _ => throw new InvalidOperationException($"Cannot convert type '{type}'."),
        };
    }

通过复制提供的任何选项,然后覆盖命名策略以使用 Pascal 大小写 (PropertyNamingPolicy = null),然后我可以确信解析的 Json 文档将始终采用 Pascal 大小写,无论框架提供的选项如何。