Json.NET如何将浮点数/双精度数序列化为最少的小数位数,即没有多余的“.0”?

34

在将浮点数序列化为Json格式时,如果数字不包含小数部分,Json.NET会始终在末尾添加“.0”。我想知道是否有一种简单的方法可以绕过这个问题,以得到更紧凑的表示形式?当序列化包含许多数字的对象时,额外的点和零会增加数据量。

例如,运行以下代码时:

JsonConvert.SerializeObject(1.0);
我希望(并且想要)得到这个结果:

我会期待并且希望得到这个结果:

"1"

但实际上我得到的是:

"1.0"

我查看了源代码并注意到它是在提交 0319263 ("...-Fixed JsonConvert to always write a floating point number with a decimal place...") 中被故意添加的,其中运行的代码基本上看起来像:

    private static string EnsureDecimalPlace(double value, string text)
    {
        if (double.IsNaN(value) || double.IsInfinity(value) ||
            text.IndexOf('.') != -1 || text.IndexOf('E') != -1 ||
            text.IndexOf('e') != -1)
        {
            return text;
        }

        return text + ".0";
    }

因此,我想知道:

  1. 那个变化的原因可能是什么? JSON规范似乎并没有要求这样做。

  2. 是否有一种简单的方法可以绕过它?


1
Newtonsoft 已确认这是设计上的问题,详见 Decimal with no decimal point serialized as a Decimal with a decimal point #590 - dbc
2个回答

40

作为对第二个问题的替代答案(假设您不想费心编译自己的Json.NET源自定义版本),您可以创建自己的自定义JsonConverter类来处理十进制、浮点和双精度值。以下是我使用的版本:

    class DecimalJsonConverter : JsonConverter
    {
        public DecimalJsonConverter()
        {
        }

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

        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 CanConvert(Type objectType)
        {
            return (objectType == typeof(decimal) || objectType == typeof(float) || objectType == typeof(double));
        }

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            if (DecimalJsonConverter.IsWholeValue(value))
            {
                writer.WriteRawValue(JsonConvert.ToString(Convert.ToInt64(value)));
            }
            else
            {
                writer.WriteRawValue(JsonConvert.ToString(value));
            }
        }

        private static bool IsWholeValue(object value)
        {
            if (value is decimal decimalValue)
            {
                int precision = (Decimal.GetBits(decimalValue)[3] >> 16) & 0x000000FF;
                return precision == 0;
            }
            else if (value is float floatValue)
            {
                return floatValue == Math.Truncate(floatValue);
            }
            else if (value is double doubleValue)
            {
                return doubleValue == Math.Truncate(doubleValue);
            }

            return false;
        }
    }

这将保留 decimal 类型的值的精度。如果您愿意忽略 decimal 值的精度,可以使 IsWholeValue() 函数的小数部分与 float/double 部分相同:

        private static bool IsWholeValue(object value)
        {
            if (value is decimal decimalValue)
            {
                return decimalValue == Math.Truncate(decimalValue);
            }
            else if (value is float floatValue)
            {
                return floatValue == Math.Truncate(floatValue);
            }
            else if (value is double doubleValue)
            {
                return doubleValue == Math.Truncate(doubleValue);
            }

            return false;
        }

在任何一种情况下,要使用上面的代码,只需像这样调用序列化器:

string json = JsonConvert.SerializeObject(value, new DecimalJsonConverter())

3
这种方法对我来说一直不成功:CanConvert 一直返回 false,意味着自定义转换器没有起作用 - 直到我注意到我正在序列化可为空的类型。当我添加了对 decimal?float?double? 的检查后,它就开始正常工作了。谢谢! - pcdev
2
虽然这个答案在大多数情况下都是非常出色的,但我想警告你关于 Convert.ToInt64 .. 如果你尝试 Convert.ToInt64(double.MaxValue) 它会溢出并崩溃。 - Crypth
如果您使用float类型,在IsWholeValue()函数中会出现异常:无法将类型为'System.Single'的对象强制转换为类型'System.Double'。.NET 6 - Saibamen
你需要有“相同”的IF语句,但是针对浮点数(float floatValue = (float)value)。 - Saibamen
@Saibamen,哇,你说得对。我只是假设由于可以将float隐式转换为double,因此将其强制转换为double也可以工作,但我想这并不是这样。已修复。 - jkindwall

5

1. 那个变化的原因可能是什么?

虽然规格书并没有要求,但也没有禁止。

我猜测这样做是为了更好地对Json.NET进行类型检查(如果他们的某个地方有类型检查),或者它是一种“以防万一”的方法,可以区分整数和浮点数类型。

from Json.org

2. 有没有简单的方法可以绕过它?

不是很简单,但如果你真的想要,可以在将EnsureDecimalPlace()更改为简单的return text;之后重新编译自己的Json.NET版本。


1
你从哪里得到这个流程图的?它是规格说明的一部分吗?它似乎表明“000”不是一个数字。 - Steztric
1
流程图是此处显示的https://www.json.org/json-en.html的早期版本。事实上,JSON标准不允许具有前导零的数字。 - dbc

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