【发布时间】:2020-06-12 02:11:20
【问题描述】:
我正在测试我正在处理的购物车的 Json.NET 序列化,并注意到当我再次序列化 -> 反序列化 -> 序列化时,我发现某些 @987654327 的尾随零格式有所不同@字段。下面是序列化代码:
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");
}
Cart 实现了IEnumerable<T> 并有一个JsonObjectAttribute 允许它作为一个对象序列化,包括它的属性以及它的内部列表。 Cart 的 decimal 属性不会改变,但内部列表/数组中的对象及其内部对象的某些 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 中没有任何东西会导致精度损失或舍入可能会在多次序列化回合后逐渐改变这些小数之一旅行。
更新
这是一个可重现的示例。我以为我已经排除了JsonConverter 但错了。因为我的内部_items 列表是在接口上键入的,所以我必须告诉 Json.NET 要反序列化回哪个具体类型。我不想要 JSON 中的实际 Type 名称,所以我没有使用 TypeNameHandling.Auto,而是为项目提供了唯一的字符串标识符属性。 JsonConverter 使用它来选择要创建的具体类型,但我猜JObject 已经将我的decimals 解析为doubles?这可能是我第二次实现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);
【问题讨论】:
-
一种可能性是您在数据模型中使用的是
double而不是decimal。请参阅:JsonConvert.PopulateObject() is not handling the decimal type data properly。 -
另一种可能性是,在您的代码中的某处,您有一个
JsonConverter,它将 JSON 预加载到JToken层次结构中,此时 JSON 浮点值被反序列化为中间 @987654355 @。为避免这种情况,您需要在转换器中或更高级别设置FloatParseHandling.Decimal,请参阅Force decimal type in class definition during serialization。但我们需要看到minimal reproducible example 才能确定我认为。 -
很惊讶你有任何尾随小数,因为尾随和前导零严格来说是一个字符串,除非你需要在整数后面加上 .0 来区分小数或浮点文字和整数字面意思
-
@mlibby - 实际上
decimal保留了有关尾随零的信息,请参阅docs:缩放因子还保留十进制数中的任何尾随零。尾随零不会影响算术或比较运算中的 Decimal 数的值。但是,如果应用了适当的格式字符串,ToString 方法可能会显示尾随零。 Json.NET 将保留decimal的尾随零,参见例如dotnetfiddle.net/KqfGSR -
@dbc 完美,你明白了!我正在使用
JsonConverter,这是我怀疑问题所在,但无法重现它。结果我的转换器没有被执行,我没有在我的值上明确定义额外的 0。有时包含minimal reproducible example 并不容易,除非您知道问题出在哪里,因此感谢您的努力,而不仅仅是拒绝投票或投票结束。我已经更新了这个问题。我了解您的问题,但不确定我是否了解您的解决方案。