将 JsonConverter 转换为 System.Text.Json 以支持多种基本类型和可为空

Convert JsonConverter to System.Text.Json to support multiple primitive types and nullable

我正在尝试将此 Newtonsoft.Json.JsonConverter 转换为 System.Text.Json。但是,我只能使用一个原始类型,比如 double,即使在那里我也无法在可空(double?)上应用转换器。如何将其转换为支持可为空和所有数字格式(浮点型、双精度型)。

Newtonsoft.Json

public class DecimalRoundingJsonConverter : JsonConverter
{
    private readonly int _numberOfDecimals;

    public DecimalRoundingJsonConverter() : this(6)
    {
    }
    public DecimalRoundingJsonConverter(int numberOfDecimals)
    {
        _numberOfDecimals = numberOfDecimals;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        double input = 0;
        if (value is decimal)
        {
            var d = (decimal)value;
            input = Convert.ToDouble(d);
        }
        else if (value is float)
        {
            var d = (float)value;
            input = Convert.ToDouble(d);
        }
        else
        {
            input = (double)value;
        }
        var rounded = Math.Round(input, _numberOfDecimals);
        writer.WriteValue((decimal)rounded);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
        JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }

    public override bool CanRead
    {
        get { return false; }
    }

    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(decimal);
    }
}

System.Text.Json(基本)

public class DecimalRoundingJsonConverter : JsonConverter<double>
{
    private readonly int _numberOfDecimals = 6;
    public override double Read(
        ref Utf8JsonReader reader,
        Type typeToConvert,
        JsonSerializerOptions options)
    {
        throw new NotImplementedException();
    }

    public override void Write(
        Utf8JsonWriter writer,
        double dvalue,
        JsonSerializerOptions options)
    {
        double input = (double)dvalue;
      
        var rounded = Math.Round(input, _numberOfDecimals);
        writer.WriteStringValue(rounded.ToString());
    }
}

您可以通过创建 JsonConverter<object> 和仅对六种相关类型将 JsonConverter<object>.CanConvert(Type) 覆盖为 return true。

执行以下操作:

public class RoundingJsonConverter : RoundingJsonConverterBase
{
    // The converter works for float, double & decimal.  Max number of decimals for double is 15, for decimal is 28, so throw an exception of numberOfDecimals > 28.
    public RoundingJsonConverter(int numberOfDecimals) => NumberOfDecimals = (numberOfDecimals < 0 || numberOfDecimals > 28 ? throw new ArgumentOutOfRangeException(nameof(numberOfDecimals)) : numberOfDecimals);
    protected override int NumberOfDecimals { get; }
}

public class RoundingTo2DigitsJsonConverter : RoundingJsonConverterBase
{
    protected override int NumberOfDecimals { get; } = 2;
}

public class RoundingTo6DigitsJsonConverter : RoundingJsonConverterBase
{
    protected override int NumberOfDecimals { get; } = 6;
}

public abstract class RoundingJsonConverterBase : JsonConverter<object>
{
    protected abstract int NumberOfDecimals { get; }

    public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        typeToConvert = Nullable.GetUnderlyingType(typeToConvert) ?? typeToConvert;
        if (typeToConvert == typeof(decimal))
            return reader.GetDecimal();
        else if (typeToConvert == typeof(double))
            return reader.GetDouble();
        else if (typeToConvert == typeof(float))
            return (float)reader.GetDouble();
        throw new NotImplementedException();
    }

    public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options)
    {
        switch (value)
        {
            case double d:
                writer.WriteNumberValue(Math.Round(d, Math.Min(15, NumberOfDecimals)));
            break;
            case decimal d:
                writer.WriteNumberValue(Math.Round(d, NumberOfDecimals));
            break;
            case float f:
                writer.WriteNumberValue((decimal)Math.Round((decimal)f, NumberOfDecimals));
            break;
            default:
                throw new NotImplementedException();
        }
    }
    
    public override bool CanConvert(Type typeToConvert)
    {
        typeToConvert = Nullable.GetUnderlyingType(typeToConvert) ?? typeToConvert;
        return typeToConvert == typeof(double) || typeToConvert == typeof(decimal) || typeToConvert == typeof(float);
    }           
}

备注:

  • System.Text.Json 没有等同于 Newtonsoft 的 JsonConverter.CanRead 所以你必须实现 Read() 以及 Write().

  • 将转换器添加到 JsonSerializerOptions.Converters 时使用 DecimalRoundingJsonConverter 并将所需的位数作为构造函数参数传递,例如:

    var options = new JsonSerializerOptions
    {
        Converters = { new RoundingJsonConverter(6) },
    };
    

    但是,如果您通过属性应用转换器,Microsoft 不允许传递转换器参数(请参阅 here 进行确认)因此您需要为每个所需的位数创建一个特定的转换器类型, 例如

    public class RoundingTo2DigitsJsonConverter : RoundingJsonConverterBase
    {
        protected override int NumberOfDecimals { get; } = 2;
    }
    
    public class RoundingTo6DigitsJsonConverter : RoundingJsonConverterBase
    {
        protected override int NumberOfDecimals { get; } = 6;
    }
    

    然后申请,例如如下:

    [JsonConverter(typeof(RoundingTo6DigitsJsonConverter))]
    public decimal? SixDecimalPlaceValue { get; set; }
    
  • Nullable.GetUnderlyingType(Type) 可用于获取可空类型的基础类型,例如 decimal?double?.

  • JsonConverter<T>.Write() 永远不会为可空值的 null 值调用,除非 JsonConverter<T>.HandleNull 被覆盖为 return true.

演示 fiddle here.