C# Json.Net不以相同的方式序列化小数两次

C# Json.Net不以相同的方式序列化小数两次,c#,json.net,decimal,system.text.json,C#,Json.net,Decimal,System.text.json,我正在测试我正在使用的购物车的Json.NET序列化,并注意到当我再次序列化->反序列化->序列化时,一些decimal字段的尾随零格式有所不同。以下是序列化代码: private static void TestRoundTripCartSerialization(Cart cart) { string cartJson = JsonConvert.SerializeObject(cart, Formatting.Indented); Console.WriteLine(ca

我正在测试我正在使用的购物车的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");
}
第二次序列化:

      ...
      "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
,以及其他一些已从四个尾随零更改为一个尾随零。我确实在源代码中发现了一些关于处理尾随零的变化的东西,但我对这些东西的理解还不够透彻,无法与之结合起来。我不能在这里共享完整的购物车实现,但我构建了一个简单的模型,无法重现结果。最明显的区别是我的基本版本丢失了抽象基类和接口的一些额外继承/实现,以及这些基类和接口上的一些泛型类型用法(其中泛型类型参数定义了一些嵌套子对象的类型)

所以我希望没有人能回答:知道尾随的零为什么会改变吗?在反序列化JSON字符串后,对象看起来与原始对象完全相同,但我想确保JSON.NET中没有导致精度损失或舍入的内容,在多次序列化往返之后,这些小数中的一个可能会逐渐改变


已更新

这里有一个可复制的例子。我以为我已经排除了JsonConverter的可能性,但我错了。因为我的内部
\u items
列表是在接口上键入的,所以我必须告诉Json.NET要反序列化回哪个具体类型。我不想在JSON中使用实际的
类型
名称,因此我给了这些项一个唯一的字符串标识符属性,而不是使用
typenameholling.Auto
JsonConverter
使用它来选择要创建的具体类型,但是我猜
JObject
已经将我的
decimal
s解析为
double
s了?这可能是我第二次实现
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();
    }
}

如果多态模型包含
decimal
属性,为了不丢失精度,在将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;
    }
公共类TestItemJsonConverter:JsonConverter
{
公共重写对象ReadJson(JsonReader阅读器,类型objectType,对象existingValue,JsonSerializer序列化程序)
{
对象结果=空;
var old=reader.FloatParseHandling;
尝试
{
reader.FloatParseHandling=FloatParseHandling.Decimal;
JObject jObj=JObject.Load(读卡器);
字符串itemTypeID=jObj[“ItemName”].Value();
//注意:我真正的实现在这里没有硬编码的字符串或类型。
//请参阅下面的代码块了解实际实现。
如果(itemTypeID==“Item1”)
结果=jObj.ToObject(typeof(Item1),序列化程序);
}
最后
{
reader.FloatParseHandling=old;
}
返回结果;
}
演示小提琴

为什么有必要这样做?事实证明,您在Json.NET中遇到了一个不幸的设计决策。当遇到浮点值时,它会将其解析为上述
FloatParseHandling
设置定义的
decimal
double
。一旦做出选择,Json值将被解析为into目标类型和存储在中,并且基础字符序列被丢弃。因此,如果浮点类型选择不当,以后很难纠正错误

因此,理想情况下,我们希望选择“最通用”的浮点类型作为默认浮点类型,该类型可以在不丢失信息的情况下转换为所有其他类型。不幸的是,在.Net中不存在此类类型。其可能性总结如下:

如您所见,
double
支持更大的范围,而
decimal
支持更大的精度。因此,为了最大限度地减少数据丢失,有时需要选择
decimal
,有时需要选择
double
。同样不幸的是,
JsonReader
中没有内置此类逻辑;没有
FloatParseHandling.Auto
选项选择最合适的表示形式

如果没有此类选项或无法将原始浮点值作为字符串加载并在以后重新解析,则在预加载
JToken
层次结构时,需要根据数据模型使用适当的
FloatParseHandling
设置对转换器进行硬编码

在数据模型同时包含
double
decimal
成员的情况下,使用
FloatParseHandling.decimal
预加载可能会满足您的需要,因为Json.NET在尝试将太大的值反序列化为
decimal
时会抛出
JsonReaderException
(演示小提琴)但是,当尝试将过于精确的值反序列化为
double
时,它会自动舍入该值。实际上,在同一多态数据模型中,浮点值不太可能大于
10^28
,且精度超过15位+尾随零sing
FloatParseHandling.Decimal
您将得到一个解释问题的显式异常

注:

  • 我不知道为什么选择了
    double
    而不是
    decimal
    作为“默认”浮点格式。Json.NET最初是在年发布的;我的回忆
    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;
        }