将小数序列化为 JSON,如何四舍五入?
Posted
技术标签:
【中文标题】将小数序列化为 JSON,如何四舍五入?【英文标题】:Serializing a decimal to JSON, how to round off? 【发布时间】:2012-08-30 06:27:45 【问题描述】:我有课
public class Money
public string Currency get; set;
public decimal Amount get; set;
并希望将其序列化为 JSON。如果我使用javascriptSerializer
我会得到
"Currency":"USD","Amount":100.31000
由于 API 我必须符合需要最多两位小数的 JSON 数量,我觉得应该可以以某种方式改变 JavaScriptSerializer
序列化小数字段的方式,但我不知道如何。 SimpleTypeResolver 您可以在构造函数中传递,但据我所知,它仅适用于类型。您可以通过 RegisterConverters(...) 添加的 JavaScriptConverter 似乎是为 Dictionary
制作的。
我想得到
"Currency":"USD","Amount":100.31
在我序列化之后。此外,更改为 double 是不可能的。我可能需要做一些四舍五入(100.311 应该变成 100.31)。
有人知道怎么做吗?是否有替代 JavaScriptSerializer
的方法可以让您更详细地控制序列化?
【问题讨论】:
序列化时是否只想取整金额?您可以向该类添加另一个属性并序列化该属性而不是 Amount 属性,并将原始 Amount 属性标记为不序列化。 @MarkSherretta 是的,我只想在序列化为 JSON 时进行舍入。我可以不把它变成一个字符串(“Amount”:“100.31”)吗?用于序列化的舍入双字段? 我希望更改只在一个地方。在我的情况下,可以对所有被序列化的小数执行此操作(不仅仅是在此对象中)。 【参考方案1】:到目前为止,我对实现这一目标的所有技术并不完全满意。 JsonConverterAttribute 似乎是最有前途的,但我无法忍受硬编码的参数和针对每种选项组合的转换器类的激增。
所以,我提交了一个PR,它增加了向 JsonConverter 和 JsonProperty 传递各种参数的能力。它已被上游接受,我预计将在下一个版本中发布(无论是 6.0.5 之后的下一个版本)
然后你可以这样做:
public class Measurements
[JsonProperty(ItemConverterType = typeof(RoundingJsonConverter))]
public List<double> Positions get; set;
[JsonProperty(ItemConverterType = typeof(RoundingJsonConverter), ItemConverterParameters = new object[] 0, MidpointRounding.ToEven )]
public List<double> Loads get; set;
[JsonConverter(typeof(RoundingJsonConverter), 4)]
public double Gain get; set;
有关示例,请参阅CustomDoubleRounding() 测试。
【讨论】:
我无法在版本 8 中完成这项工作。是否有完整的示例说明完成这项工作所需的条件?代码运行正常,但没有四舍五入。 哦,刚刚意识到它在反序列化时不起作用 - 只有在创建序列化到新 JSON 时才起作用:( 尝试改变CanRead返回true,然后实现一个合适的ReadJson。我没有理由认为这不能轻易实现双向。 谢谢 - 我会试一试。我不确定是什么导致它使用 ReadJSON 函数,但现在你已经说过 - 我想我应该发现它:) 这是我找到的最佳答案,PR 已被接受,所以这是在 Newtonsoft.Json 项目中,请参阅上面链接的 CustomDoubleRounding 测试示例代码【参考方案2】:为了将来参考,这可以通过创建自定义 JsonConverter
在 Json.net 中非常优雅地实现
public class DecimalFormatJsonConverter : JsonConverter
private readonly int _numberOfDecimals;
public DecimalFormatJsonConverter(int numberOfDecimals)
_numberOfDecimals = numberOfDecimals;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
var d = (decimal) value;
var rounded = Math.Round(d, _numberOfDecimals);
writer.WriteValue((decimal)rounded);
public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
JsonSerializer serializer)
throw new NotImplementedException("Unnecessary because CanRead is false. The type will skip the converter.");
public override bool CanRead
get return false;
public override bool CanConvert(Type objectType)
return objectType == typeof(decimal);
如果您在代码中显式使用构造函数创建序列化程序,这将正常工作,但我认为用JsonConverterAttribute
装饰相关属性会更好,在这种情况下,类必须有一个公共的、无参数的构造函数。我通过创建一个特定于我想要的格式的子类来解决这个问题。
public class SomePropertyDecimalFormatConverter : DecimalFormatJsonConverter
public SomePropertyDecimalFormatConverter() : base(3)
public class Poco
[JsonConverter(typeof(SomePropertyDecimalFormatConverter))]
public decimal SomeProperty get;set;
自定义转换器源自Json.NET documentation。
【讨论】:
您还需要做些什么来激活它吗?我已经添加了类和属性,但它从未进入WriteJson函数。 @NickG 它不应该。我刚刚确认了新的控制台应用程序和最新的 Json.NET。我添加了此处定义的 2 个转换器类和 Poco,JsonConvert.SerializeObject(new Poco SomeProperty = 1.23456789m );
导致序列化值为 "SomeProperty":1.235
我误解了这应该如何工作 - 它似乎只在创建 JSON 时才有效 - 而不是在解析它时......我希望它将供应商 JSON 中的奇怪值转换为更明智的值。例如 57.400000000000000001 等...但是我现在已经在 biz 逻辑代码中完成了。
@NickG 解析也应该相对容易支持,将 CanRead
更改为返回 true 并在 ReadJson
方法中根据需要实现解析。不过我还没有测试过。
Math.Round(d, _numberOfDecimals)
应该指定MidpointRounding.AwayFromZero
否则你可能会得到你不期望的四舍五入...参考这个:***.com/questions/977796/…【参考方案3】:
我刚刚遇到了同样的麻烦,因为我有一些用 1.00 和一些用 1.0000 序列化的小数。 这是我的改变:
创建一个可以将值四舍五入到小数点后的 JsonTextWriter。然后每个小数将四舍五入为 4 位小数:1.0 变为 1.0000,1.0000000 也变为 1.0000
private class JsonTextWriterOptimized : JsonTextWriter
public JsonTextWriterOptimized(TextWriter textWriter)
: base(textWriter)
public override void WriteValue(decimal value)
// we really really really want the value to be serialized as "0.0000" not "0.00" or "0.0000"!
value = Math.Round(value, 4);
// divide first to force the appearance of 4 decimals
value = Math.Round((((value+0.00001M)/10000)*10000)-0.00001M, 4);
base.WriteValue(value);
使用您自己的作家而不是标准作家:
var jsonSerializer = Newtonsoft.Json.JsonSerializer.Create();
var sb = new StringBuilder(256);
var sw = new StringWriter(sb, CultureInfo.InvariantCulture);
using (var jsonWriter = new JsonTextWriterOptimized(sw))
jsonWriter.Formatting = Formatting.None;
jsonSerializer.Serialize(jsonWriter, instance);
【讨论】:
我测试了这个解决方案,它也可以工作。我必须将var jsonSerializer = Newtonsoft.Json.JsonSerializer.Create();
更改为 var jsonSerializer = Newtonsoft.Json.JsonSerializer.Create(new JsonSerializerSettings());
才能编译。可能是因为我在 .Net framework 4.5 上?【参考方案4】:
在第一种情况下,000
没有害处,该值仍然是相同的,并且将被反序列化为完全相同的值。
在第二种情况下,JavascriptSerializer 不会帮助您。 JavacriptSerializer
不应该更改数据,因为它将数据序列化为众所周知的格式,它不提供成员级别的数据转换(但它提供自定义对象转换器)。您想要的是转换+序列化,这是一个两阶段的任务。
两个建议:
1) 使用DataContractJsonSerializer
:添加另一个对值进行四舍五入的属性:
public class Money
public string Currency get; set;
[IgnoreDataMember]
public decimal Amount get; set;
[DataMember(Name = "Amount")]
public decimal RoundedAmount get return Math.Round(Amount, 2);
2) 克隆取整值的对象:
public class Money
public string Currency get; set;
public decimal Amount get; set;
public Money CloneRounding()
var obj = (Money)this.MemberwiseClone();
obj.Amount = Math.Round(obj.Amount, 2);
return obj;
var roundMoney = money.CloneRounding();
我猜json.net 也不能这样做,但我不能 100% 确定。
【讨论】:
在我的具体情况下,000
会造成伤害,因为接收部分(我没有任何控制权)会在小数点后两位以上引发验证错误。除此之外,我一定会考虑您的解决方案。
感谢您的回答 :) 即使我没有检查克隆建议(我选择了DataContractJsonSerializer
-建议,它工作正常),我也会将其设置为更正。以上是关于将小数序列化为 JSON,如何四舍五入?的主要内容,如果未能解决你的问题,请参考以下文章
前后的交互js数字精度丢失解决,金额保留两位小数四舍五入统一解决,自定义Json序列化处理方法,@JsonSerialize使用