JsonSerializer - 使用“N2”格式化序列化小数位

23

使用Newtonsoft.Json.JsonSerializer序列化十进制数。

如何设置只使用1位小数的十进制数字来序列化,以在末尾使用0。

例如:3.5 序列化为 "3.50"?

3个回答

44

您需要编写自己的自定义JsonConverter,使用它来拦截decimal类型,以便可以更改它的序列化方式。以下是一个示例:

public class DecimalFormatConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return (objectType == typeof(decimal));
    }

    public override void WriteJson(JsonWriter writer, object value, 
                                   JsonSerializer serializer)
    {
        writer.WriteValue(string.Format("{0:N2}", value));
    }

    public override bool CanRead
    {
        get { return false; }
    }

    public override object ReadJson(JsonReader reader, Type objectType,
                                 object existingValue, JsonSerializer serializer)
    {
        throw new NotImplementedException();    
    }
}   

要使用它,只需将这个定制转换器的新实例传递给SerializeObject方法:

var json = JsonConvert.SerializeObject(yourObject, new DecimalFormatConverter());

2
请注意,在 string.Format 中必须指定文化。否则可能会出现奇怪的“错误”。 - David De Sloovere
13
可能对某些人有用:如果你不想在值周围输出双引号,可以使用WriteRawValue代替。 - Răzvan Flavius Panda
1
{0:N2} 将包含逗号。如果您使用 WriteRawValue 而逗号是字符串的一部分,那么您将收到错误信息。 - Karson
1
请注意使用CultureInfo.InvariantCulture格式化字符串:writer.WriteValue(string.Format(CultureInfo.InvariantCulture, "{0:N2}", value)); - belchev

32
已被采纳的答案是正确的,但是根据已被采纳答案的评论可扩展:

如果你想要在JSON中小数为数字而非字符串,你需要使用WriteRawValue并且使用:0.00代替:N2来进行字符串格式化(因为N2包括千分位分隔符和其他与地区文化相关的数字格式,这会破坏你的JSON)。

public class DecimalFormatConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(decimal);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        writer.WriteRawValue($"{value:0.00}");
    }

    public override bool CanRead => false;

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

这是与被接受答案相比输出的不同之处。

# writer.WriteRawValue($"{value:0.00}");
{
    "MyDecimal": 3.50,
    "MyBiggerDecimal": 12345.50
}

# writer.WriteValue($"{value:N2}");
{
    "MyDecimal": "3.50",
    "MyBiggerDecimal": "12,345.50"
}

注意 - 接受的答案对于提问者的具体问题即将3.5序列化为"3.50"是正确的,但我想要把3.5序列化为3.50(不带字符串引号)。


2
那个答案使用了 number.ToString(CultureInfo.InvariantCulture)。这是正确的方法吗? - Denis535
3
是的,在.NET中,如果您的输出面向机器通信(这正是JSON的本质),则应始终明确指定InvariantCulture。如果不这样做,结果将根据代码运行的任何文化格式化。例如,在荷兰服务器上,接受的答案将返回“3,50”,而不是“3.50”。 - Martin Watts
1
请注意使用不变文化:writer.WriteRawValue(FormattableString.Invariant($"{value:0.00}")); - belchev
2
此外,如果您的模型具有可为空(nullable)类型的 decimal? 属性,则需要更新 CanConvert 方法为 return objectType == typeof(decimal) || objectType == typeof(decimal?);,否则它将无法处理可为空的属性。 - Andrew Basarab
使用System.Text,而非Newtonsoft时,是否有可能? - Bassie

1

嗨! 第二个答案也是正确的,但不反映文化。 如果你想要真正的0.00(带有小数点),你必须使用:

public class DecimalFormatConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(decimal);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        FormattableString formattableString = $"{value:0.00}";
        writer.WriteRawValue(formattableString.ToString(CultureInfo.InvariantCulture));
    }

    public override bool CanRead => false;

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

使用此选项,将保证小数点为十进制分隔符。

区别在于:

    FormattableString formattableString = $"{value:0.00}";
    writer.WriteRawValue(formattableString.ToString(CultureInfo.InvariantCulture));

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接