Json.Net 没有以相同的方式序列化小数两次

2024-02-27

我正在测试我正在处理的购物车的 Json.NET 序列化,并注意到当我序列化 -> 反序列化 -> 再次序列化时,我发现某些文件的尾随零格式有所不同decimal字段。这是序列化代码:

private static void TestRoundTripCartSerialization(Cart cart)
{
    string cartJson = JsonConvert.SerializeObject(cart, Formatting.Indented);

    Console.WriteLine(cartJson);

    Cart cartClone = JsonConvert.DeserializeObject<Cart>(cartJson);

    string cloneJson = JsonConvert.SerializeObject(cartClone, Formatting.Indented);

    Console.WriteLine(cloneJson);

    Console.WriteLine("\r\n Serialized carts are " + (cartJson == cloneJson ? "" : "not") + " identical");
}

The Cart实施IEnumerable<T>并有一个JsonObjectAttribute允许它序列化为对象,包括其属性及其内部列表。这decimal的属性Cart不改变,但有些decimal内部列表/数组中的对象及其内部对象的属性的作用如上面代码输出的摘录所示:

第一次连载:

      ...
      "Total": 27.0000,
      "PaymentPlan": {
        "TaxRate": 8.00000,
        "ManualDiscountApplied": 0.0,
        "AdditionalCashDiscountApplied": 0.0,
        "PreTaxDeposit": 25.0000,
        "PreTaxBalance": 0.0,
        "DepositTax": 2.00,
        "BalanceTax": 0.0,
        "SNPFee": 25.0000,
        "cartItemPaymentPlanTypeID": "SNP",
        "unitPreTaxTotal": 25.0000,
        "unitTax": 2.00
      }
    }
  ],
 }

第二次连载:

      ...
      "Total": 27.0,
      "PaymentPlan": {
        "TaxRate": 8.0,
        "ManualDiscountApplied": 0.0,
        "AdditionalCashDiscountApplied": 0.0,
        "PreTaxDeposit": 25.0,
        "PreTaxBalance": 0.0,
        "DepositTax": 2.0,
        "BalanceTax": 0.0,
        "SNPFee": 25.0,
        "cartItemPaymentPlanTypeID": "SNP",
        "unitPreTaxTotal": 25.0,
        "unitTax": 2.0
      }
    }
  ],
 }

注意Total, TaxRate,其他一些已从四个尾随零更改为单个尾随零。我确实曾经在源代码中发现了一些关于处理尾随零的更改的内容,但我没有足够理解的内容可以与此放在一起。我无法在这里分享完整的 Cart 实现,但我构建了它的基本模型,但无法重现结果。最明显的区别是我的基本版本丢失了抽象基类和接口的一些额外继承/实现以及这些类和接口的一些泛型类型用法(其中泛型类型参数定义了一些嵌套子对象的类型)。

所以我希望没有人仍然可以回答:知道为什么尾随零会改变吗?反序列化任一 JSON 字符串后,这些对象似乎与原始对象相同,但我想确保 Json.NET 中没有某些内容会导致精度或舍入损失,从而可能在多次序列化后逐渐改变这些小数之一旅行。


Updated

这是一个可重现的示例。我以为我已经排除了JsonConverter但错了。因为我的内心_itemslist 是在接口上键入的,我必须告诉 Json.NET 反序列化回哪个具体类型。我不想要实际的TypeJSON 中的名称 so 而不是使用TypeNameHandling.Auto,我给了这些项目一个唯一的字符串标识符属性。这JsonConverter用它来选择要创建的具体类型,但我猜JObject已经解析了我的decimals to double是?这可能是我第二次实施JsonConverter我并不完全了解它们是如何工作的,因为查找文档很困难。所以我可能有ReadJson都错了。

[JsonObject]
public class Test : IEnumerable<IItem>
{
    [JsonProperty(ItemConverterType = typeof(TestItemJsonConverter))]
    protected List<IItem> _items;

    public Test() { }

    [JsonConstructor]
    public Test(IEnumerable<IItem> o)
    {
        _items = o == null ? new List<IItem>() : new List<IItem>(o);
    }

    public decimal Total { get; set; }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return _items.GetEnumerator();
    }

    IEnumerator<IItem> IEnumerable<IItem>.GetEnumerator()
    {
        return _items.GetEnumerator();
    }
}

public interface IItem
{
    string ItemName { get; }
}

public class Item1 : IItem
{
    public Item1() { }
    public Item1(decimal fee) { Fee = fee; }

    public string ItemName { get { return "Item1"; } }

    public virtual decimal Fee { get; set; }
}

public class TestItemJsonConverter : JsonConverter
{
    public override bool CanConvert(Type objectType) { return (objectType == typeof(IItem)); }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        object result = null;

        JObject jObj = JObject.Load(reader);

        string itemTypeID = jObj["ItemName"].Value<string>();

        //NOTE: My real implementation doesn't have hard coded strings or types here.
        //See the code block below for actual implementation.
        if (itemTypeID == "Item1")
            result = jObj.ToObject(typeof(Item1), serializer);

        return result;
    }

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

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { throw new NotImplementedException(); }
}

class Program
{
    static void Main(string[] args)
    {
        Test test1 = new Test(new List<Item1> { new Item1(9.00m), new Item1(24.0000m) })
        {
            Total = 33.0000m
        };

        string json = JsonConvert.SerializeObject(test1, Formatting.Indented);
        Console.WriteLine(json);
        Console.WriteLine();

        Test test1Clone = JsonConvert.DeserializeObject<Test>(json);
        string json2 = JsonConvert.SerializeObject(test1Clone, Formatting.Indented);
        Console.WriteLine(json2);

        Console.ReadLine();
    }
}

我的实际转换器的片段:

if (CartItemTypes.TypeMaps.ContainsKey(itemTypeID))
    result = jObj.ToObject(CartItemTypes.TypeMaps[itemTypeID], serializer);

如果您的多态模型包含decimal属性,为了不丢失精度,必须临时设置JsonReader.FloatParseHandling https://www.newtonsoft.com/json/help/html/P_Newtonsoft_Json_JsonReader_FloatParseHandling.htm to be FloatParseHandling.Decimal https://www.newtonsoft.com/json/help/html/T_Newtonsoft_Json_FloatParseHandling.htm当将 JSON 预加载到JToken层次结构,像这样:

public class TestItemJsonConverter : JsonConverter
{
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        object result = null;

        var old = reader.FloatParseHandling;
        try
        {
            reader.FloatParseHandling = FloatParseHandling.Decimal;

            JObject jObj = JObject.Load(reader);
            string itemTypeID = jObj["ItemName"].Value<string>();

            //NOTE: My real implementation doesn't have hard coded strings or types here.
            //See the code block below for actual implementation.
            if (itemTypeID == "Item1")
                result = jObj.ToObject(typeof(Item1), serializer);
        }
        finally
        {
            reader.FloatParseHandling = old;
        }

        return result;
    }

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

为什么这是必要的?事实证明,您在 Json.NET 中遇到了一个不幸的设计决策。什么时候JsonTextReader https://www.newtonsoft.com/json/help/html/T_Newtonsoft_Json_JsonTextReader.htm遇到浮点值,它将其解析为decimal or double按照上述定义FloatParseHandling环境。一旦做出选择,JSON 值就会被解析为目标类型并存储在JsonReader.Value https://www.newtonsoft.com/json/help/html/P_Newtonsoft_Json_JsonReader_Value.htm,并且底层字符序列被丢弃。因此,如果浮点类型选择不当,以后就很难纠正错误。

因此,理想情况下,我们希望选择“最通用”的浮点类型作为默认浮点类型,该类型可以转换为所有其他浮点类型而不会丢失信息。不幸的是,在.Net不存在这样的类型。可能性总结为浮点类型的特征 https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/floating-point-numeric-types#characteristics-of-the-floating-point-types:

如你看到的,double支持更大的范围,同时decimal支持更大的精度。因此,为了最大限度地减少数据丢失,有时decimal需要选择,有时double。而且,再次不幸的是,没有这样的逻辑内置于JsonReader;没有FloatParseHandling.Auto选择最合适的表示的选项。

如果没有这样的选项或无法将原始浮点值作为字符串加载并稍后重新解析,您将需要使用适当的方法对转换器进行硬编码FloatParseHandling当您预加载数据时,根据您的数据模型进行设置JToken等级制度。

如果您的数据模型同时包含double and decimal会员,预加载使用FloatParseHandling.Decimal可能会满足您的需求,因为 Json.NET 会抛出一个JsonReaderException当尝试将太大的值反序列化为decimal(演示小提琴here https://dotnetfiddle.net/rcVxDw),但当尝试将过于精确的值反序列化为double。实际上,浮点值不太可能大于10^28在同一多态数据模型中具有超过 15 位精度 + 尾随零。万一你这样做了,通过使用FloatParseHandling.Decimal你会得到一个明确的异常来解释问题。

Notes:

  • 我不知道为什么double被选择而不是decimal作为“默认默认”浮点格式。 Json.NET 最初发布于2006 http://james.newtonking.com/archive/2006/06/26/Json.NET-Simplifying-.NET-JavaScript-communication;我的记忆是decimal当时并没有被广泛使用,所以也许这是一个从未被重新审视的遗留选择?

  • 当直接反序列化到decimal or double成员,序列化器将通过调用覆盖默认浮点类型ReadAsDouble() https://www.newtonsoft.com/json/help/html/M_Newtonsoft_Json_JsonReader_ReadAsDouble.htm or ReadAsDecimal() https://www.newtonsoft.com/json/help/html/M_Newtonsoft_Json_JsonReader_ReadAsDecimal.htm,因此直接从 JSON 字符串反序列化时不会丢失精度。仅当预加载到JToken层次结构然后进行反序列化。

  • Utf8JsonReader https://learn.microsoft.com/en-us/dotnet/api/system.text.json.utf8jsonreader?view=netcore-3.1 and JsonElement https://learn.microsoft.com/en-us/dotnet/api/system.text.json.jsonelement?view=netcore-3.1 from 系统文本.json /questions/tagged/system.text.jsonMicrosoft 在 .NET Core 3.0 中替代 Json.NET,通过始终维护浮点 JSON 值的底层字节序列来避免此问题,这是新 API 对旧 API 进行改进的一个示例。

    如果您的值实际上大于10^28在同一多态数据模型中具有超过 15 位精度 + 尾随零的情况下,切换到这个新的序列化器可能是一个有效的选择。

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

Json.Net 没有以相同的方式序列化小数两次 的相关文章

随机推荐

  • 如何删除 highcharts 系列中的所有点

    使用 HighCharts 我想删除系列中的所有点 以便我可以添加新的数据集 有没有比循环所有点并像这样将它们一一删除更好的方法 for var i 0 i lt chart series 0 points length i chart s
  • 在两个服务器之间同步缓存数据的最佳方法[关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 想要同步两台服务器之间的缓存数据 两个数据库共享同一个数据库 但为了更好地执行数据 我在启动时将数据缓存到哈希映射中 因此希望在不重启
  • 使用 OpenCV 编写稳健的(颜色和大小不变)圆检测(基于霍夫变换或其他功能)

    我编写了以下非常简单的 python 代码来查找图像中的圆圈 import cv import numpy as np WAITKEY DELAY MS 10 STOP KEY q cv NamedWindow image press q
  • 使用入口点console_script进行Python包分发

    我准备部署一个具有以下布局的 Python 包 MyPackage setup py MyPackage init py main py lib init py utils py db init py db1 py db2 py tasks
  • 使用另一个表中的随机值更新 MySQL 表的最佳方法

    对于这个 伪代码 示例 我在 MySQL 中有两个表 member id name names name 会员人数100人 姓名10人 我想使用名称中的随机名称来更新成员表 到目前为止我已经得到了这个 但是不确定是否有更好的方法来实现它 U
  • 在 Swift 中的 WebService 中传递参数

    我正在学习 Swift 但不知道如何使用 Swift 向服务器发送参数 在 Objective C 中我们可以通过使用来做到这一点 作为占位符 但是对于 Swift 应该怎么做 假设我有一个需要电子邮件和密码的登录 Web 服务 现在我想知
  • 如何在android中根据材料设计制作Cardview?

    我看到了有关材料设计指南 但它有点混乱 当我设计我的卡片时 左侧有图像 图像右侧有一些文字 但我不满意它是否符合指南 请检查并告诉 我还希望我的虚拟文本段落合理 这是我的代码
  • 是否可以在 Google 地图或 Google 地球中显示 3D 数据?

    我正在尝试找到一种有效的方法来可视化一些现实世界传感器测量不同高度风速的反馈 有谁知道在谷歌地图中显示3D数据是否可行 我想象着指示方向和风速的 3D 箭头 如果可以加载模型并将其定位到特定的 GPS 位置 高度 然后更改其方向以指向特定的
  • 如何在 Python 中将字符串转换为转义序列? [复制]

    这个问题在这里已经有答案了 如果我有一个包含四个字符的字符串 例如 xf0 我如何将其转换为转义序列 xf0 我正在使用Python 3 4 编辑 我试图将字符串转换为字符串值代表的字符 您要做的就是解释原始字符串中的转义序列 以获取相应的
  • 清晰高效的 3D 范围树实现

    我正在做这个项目 我必须在 3d 空间中搜索对象 我认为效率是一个巨大的问题范围树非常适合我想要做的事情 间隔树也可以工作 但我不会从树中删除任何内容 一旦我在 3D 空间中添加每个对象 我只会使用该结构进行搜索 以下是我将如何使用该结构
  • 在不同的 Excel 文件中使用一个宏

    我写了一个宏 我想在不同的Excel文件中使用它 这些文件具有几乎相同的表结构但不同的数据 那么是否有可能将我的宏脚本 包含 到任何 Excel 文件中 我已经读过this tip https web archive org web 201
  • PHP 和 PDO:一个连接与多个连接

    在我的 PHP 程序中 我需要针对任何给定的网页请求访问数据库 0 到 3 次 我正在使用 PDO 与 MySQL 交互 首先 我使用如下内容创建一个数据库连接 dbh new PDO mysql host hostname dbname
  • 辅助功能、字段集图例和标题标签

    我正在开发的网站的一个要求是它必须符合 508 标准 目前我们大多数的 html 视图都以 header 开头h1然后该视图中需要的任何内容 现在对于表单 建议使用fieldsets and legend除其他众多准则外 在处理可访问性时也
  • 谷歌分析是否将裸域与 www 子域结合起来?

    我为自己的域安装了谷歌分析 http mydomain com 输入的用户是否会http www mydomain com也被分析脚本计算在内吗 对我来说 这似乎是合乎逻辑的 因为裸域地址与 www 前缀的站点位于同一站点是很常见的 但分析
  • 重置被拒绝的 HTML 通知

    我有一个网络应用程序 在其中使用 HTML 通知 如果用户第一次允许它并开始使用它 它工作正常 但是如果用户第一次通过单击阻止按钮阻止通知 然后尝试通过某些用户手势再次请求权限 则浏览器不会触发 允许 阻止 弹出窗口 这是我第二次触发许可
  • 按键时的 JTable 编辑

    我正在尝试以编程方式开始编辑当前行的第三列JTable在按键上 我已经实现了一个 KeyListener 其中keyReleased 包含这段代码 if e getKeyCode KeyEvent VK ENTER myTab change
  • 在 Powershell 中优雅地停止

    How do I catch and handle Ctrl C in a PowerShell script I understand that I can do this from a cmdlet in v2 by including
  • JVM 何时抛出 OutOfMemoryError

    我们正在运行一个有时会 冻结 的 Java 应用程序 因为某些线程正在使用几乎所有堆 尽管 JVM 执行的 Full GC 持续了 60 秒以上 但应用程序从未因 OutOfMemoryError 错误而终止 我从 Java 文档中读到 如
  • amqp 与 amqplib - 哪个 Node.js amqp 客户端库更好?

    这些 amqp 客户端库之间有什么区别 哪一款最值得推荐 主要区别是什么 我会推荐amqp node https github com squaremo amqp node and bramqp https github com bakke
  • Json.Net 没有以相同的方式序列化小数两次

    我正在测试我正在处理的购物车的 Json NET 序列化 并注意到当我序列化 gt 反序列化 gt 再次序列化时 我发现某些文件的尾随零格式有所不同decimal字段 这是序列化代码 private static void TestRoun