2012-09-05 20 views
17

我有一个类将小数序列化为JSON,如何四舍五入?

public class Money 
{ 
    public string Currency { get; set; } 
    public decimal Amount { get; set; } 
} 

,并想将其序列化到JSON。如果我使用JavaScriptSerializer我得到

{"Currency":"USD","Amount":100.31000} 

因为我必须符合需求JSON最大两位小数量的API的,我觉得它应该有可能以某种方式改变JavaScriptSerializer连载小数场的方式,但我不知道如何。您可以在构造函数中传递SimpleTypeResolver,但只能在我能理解的类型上使用。可以通过RegisterConverters(...)添加的JavaScriptConverter似乎是为Dictionary而制作的。

我想获得

{"Currency":"USD","Amount":100.31} 

我序列之后。另外,改为加倍是不可能的。我可能需要做一些舍入(100.311应该变成100.31)。

有谁知道如何做到这一点?有没有可能替代JavaScriptSerializer,让你更详细地控制序列化?

+0

您只想在序列化时舍入金额吗?您可以添加另一个属性到类中,然后序列化它来代替Amount属性,并将原始的Amount属性标记为不被序列化。 –

+0

@MarkSherretta是的,我只想在序列化为JSON时舍入。我可以做到这一点,而不把它变成一个字符串(“金额”:“100.31”)?一个舍入的双字段序列化? – Halvard

+0

最好我想只是在一个地方的变化。在我的情况下,可以对所有要序列化的小数执行它(不只是在这个对象中)。 – Halvard

回答

3

在第一种情况下,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%确定。

+0

在我的具体情况下'000'确实会造成伤害,因为接收部分(我没有任何控制权)会在超过两位小数时抛出验证错误。除此之外,我一定会考虑你的解决方案。 – Halvard

+1

感谢您的回答:)我设置它来纠正,即使我没有检查克隆建议(我去'DataContractJsonSerializer'建议,它工作正常)。 – Halvard

7

我刚刚经历了同样的麻烦去,因为我已经被序列化与1.00和一些与1.0000一些小数。 这是我的变化:

创建JsonTextWriter能值四舍五入到4位小数。那么每一个小数将四舍五入到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); 
} 
+0

我测试过这个解决方案,它也可以工作。我不得不将'var jsonSerializer = Newtonsoft.Json.JsonSerializer.Create();'改为'var jsonSerializer = Newtonsoft.Json.JsonSerializer.Create(new JsonSerializerSettings());'让它编译。可能是因为我在.Net framework 4.5上? – Halvard

8

对于未来的参考,这样就可以实现在Json。净漂亮优雅通过创建自定义JsonConverter

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的。

+0

还有什么你必须做激活呢?我添加了类和属性,但它永远不会进入WriteJson函数。 – NickG

+0

@NickG它不应该。我刚刚确认了新的控制台应用程序和最新的Json.NET。我添加了2个转换器类和Poco,这里定义的是'JsonConvert.SerializeObject(new Poco {SomeProperty = 1.23456789m});'产生序列化的值'{“SomeProperty”:1.235}' – htuomola

+0

我误解了这是怎么回事工作 - 它似乎只在创建JSON时才工作 - 而不是在解析它时......我希望它能将供应商的JSON中的奇怪值转换为更明智的值。例如57.400000000000000001等等......但是我现在已经在biz逻辑代码中做了这个。 – NickG

12

我并不完全满意迄今为止实现这一目标的所有技术。 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()测试的一个例子。

+0

我无法在版本8中完成此项工作。是否有完成此项工作所需的完整示例?该代码运行良好,但不包含任何内容。 – NickG

+0

哦,刚刚意识到它反序列化时不工作 - 只有当创建序列化到新的JSON时: – NickG

+0

尝试更改CanRead返回true,然后实现一个正确的ReadJson。没有理由我想这不能轻易做出双向的。 – BrandonLWhite