28

我有一堂课

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可以让您更详细地控制序列化?

4

4 回答 4

20

到目前为止,我对实现这一目标的所有技术并不完全满意。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()测试。

于 2014-09-23T15:26:46.503 回答
10

为了将来参考,这可以通过创建自定义在 Json.net 中非常优雅地实现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 文档

于 2014-06-12T08:51:19.727 回答
8

我刚刚遇到了同样的麻烦,因为我用 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);
}
于 2013-02-06T12:47:05.110 回答
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% 确定。

于 2012-09-05T14:36:04.447 回答