在自定义 JsonConverter 中,如何确定 Utf8JsonReader 数字标记是小数还是长整数?

In a custom JsonConverter, how can I determine whether a Utf8JsonReader numeric token is a decimal or a long?

我有这个 jsonconverter 需要将给定的 属性 值转换为小数或长整数,具体取决于值 - 但我似乎无法确定 属性value 是 decimal 或 long,因为 tokentype 只能检测数字...我该如何解决这个问题?

public override IDictionary<string, object> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
    IDictionary<string, object> output = new Dictionary<string, object>();

    while (reader.Read() && reader.TokenType != JsonTokenType.EndObject)
    {
        string propertyName = reader.GetString();
        reader.Read();
        object? propertyValue = null;

        switch (reader.TokenType)
        {
            case JsonTokenType.Number:
                propertyValue = reader.GetInt64();  // or could be a decimal for where I should reader.GetDecimal()
                break;
            case JsonTokenType.String:
                if (reader.TryGetDateTime(out DateTime value))
                {
                    propertyValue = value;
                }
                else
                {
                    propertyValue = reader.GetString();
                }

                break;
            case JsonTokenType.True:
            case JsonTokenType.False:
                propertyValue = reader.GetBoolean();
                break;
        }

        output.Add(propertyName, propertyValue);
    }

    return output;
}

您可以使用Utf8JsonReader.TryGetInt64(out long value) and Utf8JsonReader.TryGetDecimal(out decimal value)来测试当前值是否可以成功解析为longdecimal

因此,您修改后的 Read() 方法应如下所示:

public override IDictionary<string, object> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
    // Assert we are currently at the beginning of an object
    if (reader.TokenType != JsonTokenType.StartObject)
        throw new JsonException(string.Format("Unexpected token {0}", reader.TokenType));

    IDictionary<string, object> output = new Dictionary<string, object>();

    while (reader.Read() && reader.TokenType != JsonTokenType.EndObject)
    {
        string propertyName = reader.GetString()!;
        reader.Read();
        object? propertyValue;

        switch (reader.TokenType)
        {
            case JsonTokenType.Number:
                if (reader.TryGetInt64(out var l))
                    propertyValue = l;
                else if (reader.TryGetDecimal(out var d))
                    propertyValue = d;
                else
                {
                    // Either store the value as a string, or throw an exception.
                    using var doc = JsonDocument.ParseValue(ref reader);
                    propertyValue = doc.RootElement.ToString();
                    throw new JsonException(string.Format("Cannot parse number: {0}", propertyValue));
                }
                break;
            case JsonTokenType.String:
                if (reader.TryGetDateTime(out var dt))
                    propertyValue = dt;
                else
                    propertyValue = reader.GetString();
                break;
            case JsonTokenType.True:
            case JsonTokenType.False:
                propertyValue = reader.GetBoolean();
                break;
            case JsonTokenType.Null:
                propertyValue = null;
                break;
            default:
                // An unexpected token type such as an object or array.
                // You must either skip it or throw an exception.
                reader.Skip();
                propertyValue = null;
                throw new JsonException(string.Format("Unexpected token {0}", reader.TokenType));
                //break;
        }
        // Since your converter is of type IDictionary<string, object> I assume you don't want to allow null values.
        // If you do want to allow null values you should declare it as IDictionary<string, object?>
        if (propertyValue == null)
            throw new JsonException("null value");
        output.Add(propertyName, propertyValue);
    }

    return output;
}

备注:

  • 虽然 Utf8JsonReader 将在格式错误的 JSON 上抛出异常,但转换器有责任处理任何类型的有效值标记,并在任何类型上抛出异常不支持的值类型。

    我修改了转换器以根据需要为意外值类型抛出异常。

  • 由于您似乎启用了 nullable reference type checking,我修改了您的代码以在 propertyValue 为 null 时抛出异常。如果你想允许 null 属性 值,你应该将你的转换器声明为 JsonConverter<IDictionary<string, object?>>.

  • 您的转换器仅处理原始值。如果您想扩展它以将嵌套对象反序列化为嵌套 Dictionary<string, object> 值,并将嵌套数组反序列化为嵌套 List<object> 值,您可以从 to [=48= 查看 ObjectAsPrimitiveConverter ].

演示 fiddle here.