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

2023-12-24

我正在尝试将此 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());
    }
}

您可以创建一个适用于所有float, double and decimal值,以及相同的可空值,通过创建JsonConverter<object>并压倒一切JsonConverter<object>.CanConvert(Type) https://learn.microsoft.com/en-us/dotnet/api/system.text.json.serialization.jsonconverter-1.canconvert?view=net-6.0仅对六种相关类型返回 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);
    }           
}

Notes:

  • System.Text.Json 没有与 Newtonsoft 等效的系统JsonConverter.CanRead所以你必须实施Read()Write().

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

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

    但是,如果您通过属性应用转换器,Microsoft 不允许传递转换器参数(请参阅here https://github.com/dotnet/runtime/issues/54187#issuecomment-869598871以供确认),因此您需要为每个所需的位数创建特定的转换器类型,例如

    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) https://learn.microsoft.com/en-us/dotnet/api/system.nullable.getunderlyingtype可用于获取可空类型的基础类型,例如decimal? or double?.

  • JsonConverter<T>.Write()从未被要求null可空值除非JsonConverter<T>.HandleNull https://learn.microsoft.com/en-us/dotnet/api/system.text.json.serialization.jsonconverter-1.handlenull被覆盖返回true.

演示小提琴here https://dotnetfiddle.net/XnOPWk.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

将 JsonConverter 转换为 System.Text.Json 以支持多种基元类型和可为 null 的相关文章

  • STL 迭代器:前缀增量更快? [复制]

    这个问题在这里已经有答案了 可能的重复 C 中的预增量比后增量快 正确吗 如果是 为什么呢 https stackoverflow com questions 2020184 preincrement faster than postinc
  • 没有特殊字符的密码验证器

    我是 RegEx 的新手 已经进行了大量搜索 但没有找到任何具体内容 我正在编写一个验证密码字符串的正则表达式 可接受的字符串必须至少具有 4 种字符类型中的 3 种 数字 小写字母 大写字母 特殊字符 我对包含有一个想法 也就是说 如果这
  • 根据属性的类型使用文本框或复选框

    如果我有这样的结构 public class Parent public string Name get set public List
  • 通过引用传递 [C++]、[Qt]

    我写了这样的东西 class Storage public Storage QString key const int value const void add item QString int private QMap
  • std::list 线程push_back、front、pop_front

    std list 线程安全吗 我假设不是这样 所以我添加了自己的同步机制 我认为我有正确的术语 但我仍然遇到问题 每个函数都由单独的线程调用 Thread1 不能等待 它必须尽可能快 std list
  • 从经典 ASP 调用 .Net C# DLL 方法

    我正在开发一个经典的 asp 项目 该项目需要将字符串发送到 DLL DLL 会将其序列化并发送到 Zebra 热敏打印机 我已经构建了我的 DLL 并使用它注册了regasm其次是 代码库这使得 IIS 能够识别它 虽然我可以设置我的对象
  • WPF 数据绑定到复合类模式?

    我是第一次尝试 WPF 并且正在努力解决如何将控件绑定到使用其他对象的组合构建的类 例如 如果我有一个由两个单独的类组成的类 Comp 为了清楚起见 请注意省略的各种元素 class One int first int second cla
  • C# 列表通用扩展方法与非通用扩展方法

    这是一个简单的问题 我希望 集合类中有通用和非通用方法 例如List
  • 两个静态变量同名(两个不同的文件),并在任何其他文件中 extern 其中一个

    在一个文件中将变量声明为 static 并在另一个文件中进行 extern 声明 我认为这会在链接时出现错误 因为 extern 变量不会在任何对象中看到 因为在其他文件中声明的变量带有限定符 static 但不知何故 链接器 瑞萨 没有显
  • 结构体的内存大小不同?

    为什么第一种情况不是12 测试环境 最新版本的 gcc 和 clang 64 位 Linux struct desc int parts int nr sizeof desc Output 16 struct desc int parts
  • x:将 ViewModel 方法绑定到 DataTemplate 内的事件

    我基本上问同样的问题这个人 https stackoverflow com questions 10752448 binding to viewmodels property from a template 但在较新的背景下x Bind V
  • 在 MySQL 中使用 COUNT 时如何返回 0 而不是 null

    我使用此查询返回存储在 sTable 中的歌曲列表以及存储在 sTable2 中的总项目数 SQL queries Get data to display sQuery SELECT SQL CALC FOUND ROWS str repl
  • 两个类可以使用 C++ 互相查看吗?

    所以我有一个 A 类 我想在其中调用一些 B 类函数 所以我包括 b h 但是 在 B 类中 我想调用 A 类函数 如果我包含 a h 它最终会陷入无限循环 对吗 我能做什么呢 仅将成员函数声明放在头文件 h 中 并将成员函数定义放在实现文
  • 为什么使用小于 32 位的整数?

    我总是喜欢使用最小尺寸的变量 这样效果就很好 但是如果我使用短字节整数而不是整数 并且内存是 32 位字可寻址 这真的会给我带来好处吗 编译器是否会做一些事情来增强内存使用 对于局部变量 它可能没有多大意义 但是在具有数千甚至数百万项的结构
  • 有没有办法让 doxygen 自动处理未记录的 C 代码?

    通常它会忽略未记录的 C 文件 但我想测试 Callgraph 功能 例如 您知道在不更改 C 文件的情况下解决此问题的方法吗 设置变量EXTRACT ALL YES在你的 Doxyfile 中
  • 相当于Linux中的导入库

    在 Windows C 中 当您想要链接 DLL 时 您必须提供导入库 但是在 GNU 构建系统中 当您想要链接 so 文件 相当于 dll 时 您就不需要链接 为什么是这样 是否有等效的 Windows 导入库 注意 我不会谈论在 Win
  • 当文件流没有新数据时如何防止fgets阻塞

    我有一个popen 执行的函数tail f sometextfile 只要文件流中有数据显然我就可以通过fgets 现在 如果没有新数据来自尾部 fgets 挂起 我试过ferror and feof 无济于事 我怎样才能确定fgets 当
  • C++ 中的参考文献

    我偶尔会在 StackOverflow 上看到代码 询问一些涉及函数的重载歧义 例如 void foo int param 我的问题是 为什么会出现这种情况 或者更确切地说 你什么时候会有 对参考的参考 这与普通的旧参考有何不同 我从未在现
  • C# 使用“?” if else 语句设置值这叫什么

    嘿 我刚刚看到以下声明 return name null name NA 我只是想知道这在 NET 中叫什么 是吗 代表即然后执行此操作 这是一个俗称的 条件运算符 三元运算符 http en wikipedia org wiki Tern
  • 在OpenGL中,我可以在坐标(5, 5)处精确地绘制一个像素吗?

    我所说的 5 5 正是指第五行第五列 我发现使用屏幕坐标来绘制东西非常困难 OpenGL 中的所有坐标都是相对的 通常范围从 1 0 到 1 0 为什么阻止程序员使用屏幕坐标 窗口坐标如此严重 最简单的方法可能是通过以下方式设置投影以匹配渲

随机推荐