将小数序列化为 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使用

PHP number_format():将数字四舍五入,然后格式化为货币

求助后台json串返回到前台自动四舍五入怎么解决

json串向后台传递数值自动四舍五入的问题

将json中所有字段四舍五入保留n位小数

excel分钟及秒化为小时?