Json.NET сериализует float/double с минимальными знаками после запятой, т.е. Нет избыточного ".0"?

При сериализации поплавков и удвоений Json.NET всегда добавляет ".0" в конце, если число не содержит дробной части. Мне было интересно, есть ли простой способ обойти это, чтобы привести к более компактному представлению? Дополнительные периоды и нули складываются при сериализации объекта, содержащего много чисел.

Например, при запуске этого кода:

JsonConvert.SerializeObject(1.0);

Я ожидал бы (и хочу) этот результат:

"1"

Но вместо этого я получаю:

"1.0"

Я посмотрел исходный код и заметил, что он был специально добавлен в commit 0319263 ( "...- Исправлен JsonConvert, чтобы всегда писать число с плавающей запятой с десятичной точкой..." ), где он запускает код, который выглядит в основном следующим образом:

    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";
    }

Следовательно, мне интересно:

  • Что может быть причиной такого изменения? Спецификация JSON, похоже, не требует этого.

  • Есть ли простой способ обойти его?

Ответ 1

1. Что может быть причиной такого изменения?

Спецификации не требуют этого, но он также не запрещает его.

Моя догадка заключается в том, что она позволяет лучше проверять тип для Json.NET(если они есть где-то), или это вещь "в любом случае", которая допускает дифференциацию между целыми и плавающими типами.

from Json.org

2. Есть ли простой способ обойти его?

Не так просто, но если вы действительно этого хотите, вы можете перекомпилировать свою собственную версию Json.NET после изменения EnsureDecimalPlace() на просто return text;

Ответ 2

В качестве альтернативного ответа на вопрос 2 (предполагая, что вы не хотите преодолевать трудности с компиляцией собственной пользовательской версии источника 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)
        {
            decimal decimalValue = (decimal)value;
            int precision = (Decimal.GetBits(decimalValue)[3] >> 16) & 0x000000FF;
            return precision == 0;
        }
        else if (value is float || value is double)
        {
            double doubleValue = (double)value;
            return doubleValue == Math.Truncate(doubleValue);
        }

        return false;
    }
}

Это сохранит точность значений типа decimal. Если вы предпочитаете игнорировать точность десятичных значений, вы можете заставить десятичную часть функции IsWholeValue() работать так же, как и float/double:

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

        return false;
    }

В любом случае, чтобы использовать приведенный выше код, просто вызовите сериализатор следующим образом:

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