首页 > 解决方案 > 在自定义 JsonConverter 中,如何确定 Utf8JsonReader 数字标记是小数还是长整数?

问题描述

我有这个jsonconverter,它需要将给定的属性值转换为小数或长整数,具体取决于值 - 但我似乎无法确定属性值何时是小数或长整数,因为 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;
}

标签: c#jsonsystem.text.jsonjsonconverter

解决方案


您可以使用Utf8JsonReader.TryGetInt64(out long value)andUtf8JsonReader.TryGetDecimal(out decimal value)来测试以查看当前值是否可以成功解析为 along或 a decimal

因此,您修改后的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 上引发异常,但转换器有责任处理任何类型的有效值标记,并在任何不受支持的值类型上引发异常。

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

  • 由于您似乎启用了可空引用类型检查propertyValue,因此我修改了您的代码以在为空时抛出异常。如果您想允许 null 属性值,您应该将您的转换器声明为JsonConverter<IDictionary<string, object?>>.

  • 您的转换器仅处理原始值。如果您想将其扩展为将嵌套对象反序列化为嵌套Dictionary<string, object>值,并将嵌套数组反序列化为嵌套List<object>值,您可以查看C# - Deserializing nested json to nested Dictionary<string, object>ObjectAsPrimitiveConverter这个答案

演示小提琴在这里


推荐阅读