Что мне нужно:
В чем проблема? Когда Json имеет свойство типа {"foobar" : 0.0000000000000000001}, оно преобразуется в число, но затем в виде строки оно представляется в научной записи как 1E-19.
Есть ли способ прочитать число из json в виде строки или позже преобразовать такое число в ту же самую строку без потери точности? Точность не фиксирована, и число в json может встречаться в любом свойстве. Использование Newtonsoft предпочтительнее (если это возможно) вместо System.Text.Json.
Я пытался
В идеале я хотел бы преобразовать строку типа {"foobar": 21.37} в объект со свойством foobar = "21.37", чтобы быть уверенным, что любая точность не будет потеряна после покрытия между числом/строкой.
Минимальный воспроизводимый пример
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
var json = """
[{
"foobar1": 21.37000,
"foobar2": 0.0000000000000000001000,
"foobar3": 1.1000E-19,
"aReallyBigInteger" : 1111199999999999999999999999999999999911111
}]
""";
var input = JsonConvert.DeserializeObject<JToken>(json);
Console.WriteLine(input);
/*
Expected result:
[
{
"foobar1": "21.37000",
"foobar2": "0.0000000000000000001000",
"foobar3": "1.1000E-19",
"aReallyBigInteger": "1111199999999999999999999999999999999911111"
}
]
Current Behavior:
[
{
"foobar1": 21.37,
"foobar2": 1E-19,
"foobar3": 1.1E-19,
"aReallyBigInteger": 1111199999999999999999999999999999999911111
}
]
*/
Проведите тест здесь: binaryconvert.com/…
Почему вас волнует, как число с плавающей запятой представлено в json? Есть ли у вас основания полагать, что он теряет точность во время цикла сериализации/десериализации?
С Json.NET это невозможно сделать. JsonTextReader
преобразует числа JSON в double
, decimal
, int
или какой-либо другой числовой тип, а затем отбрасывает исходное текстовое представление, которое никогда не становится общедоступным для вызывающих абонентов. Вы могли бы попробовать разветвить Json.NET и изменить JsonTextWriter
, чтобы сохранить символьное представление чисел, но я подозреваю, что это будет нетривиально.
Требования клиентов - в конце мне нужно показать ту же строку, что и в начале, поэтому, когда клиенты ставят 0,0001, он хочет видеть позже 0,0001, а не 1E-5 (я возвращаю строку в конце)
Однако это можно сделать с помощью System.Text.Json. JsonElement
и JsonNode
сохраняют базовые представления символов токенов JSON, поэтому при их обратной записи они останутся неизменными. Возможно, это удовлетворит ваши потребности?
@MarioVernari это ограничивает - простой тест double x = 0.000000000000000000000000000000000000000000000000000000001; строка xStr = x.ToString(); Console.WriteLine(xStr); //он вернет 1E-57 //он не вернет 0.000000000000000000000000000000000000000000000000000000001
@MatteoDelOmbra Это вообще превышает точность double
?
@dbc Я пробовал это, но в конце вернулось число, а не строка. можешь подробнее, как это сделать, может я что-то не так сделал
а) это число не может быть представлено в виде double
(оно поддерживает только 17 значащих цифр) б) 1E-57 и 0,0000000000000000000000000000000000000000000000000000001 - это буквально одно и то же числовое значение (просто разные представления, так же, как 0x10
, 0o20
и 16
— это такое же количество)
«в конце мне нужно показать ту же строку, что и была» - затем передать как строку ??
@MatteoDelOmbra - можешь ли ты тогда поделиться минимально воспроизводимым примером? Конечно, типы double
и decimal
не могут сохранять числовое форматирование, но если вы прочитаете JsonNode
, я думаю, оно запомнится.
^^ И даже если это не так: вы можете ввести тип, у которого есть свойство для числового значения и отдельное свойство для (принудительного) строкового представления. Или значение и формат?
Вот демонстрация JsonNode.Parse()
сохранения числового форматирования: dotnetfiddle.net/JHO53j.
Хм, ответ Шинго был правильным: Json.NET сохраняет числовое форматирование для свойств, объявленных как строки, см. dotnetfiddle.net/Lwrvdv. Конечно, они записываются обратно в виде строк с кавычками. Я почти уверен, что это невозможно сделать с помощью конвертера, сериализатор использует для этого частный API.
Пожалуйста, уточните вашу конкретную проблему или предоставьте дополнительную информацию, чтобы выделить именно то, что вам нужно. Поскольку сейчас написано, трудно точно сказать, о чем вы спрашиваете.
«в конце мне нужно показать ту же строку, что и была», затем отформатируйте ее таким образом. Существует разница между «числом» и его представлением. 0,00001 и 1E-5 — одно и то же число, но в разных представлениях. Если вам (или вашему клиенту) нравится представление X, вы сами должны позаботиться о том, чтобы ценность была представлена именно таким образом. Система имеет определенное поведение по умолчанию. Если вам это не нравится, вам придется это переопределить...
@derpirscher да, форматирование, это моя проблема - я указал это, и чтение числа в виде строки было лишь одной из идей, как, я думаю, это можно решить - если есть способ отформатировать конечную строку, чтобы она всегда выглядела так же, как входная строка, тогда я' я ок с этим
@dbc Я добавил воспроизводимый пример минимума. Об ответе Шинго — это своего рода результат, который я ищу, но я не знаю, какой формат данных будет предоставлен — такое число может встречаться в любом свойстве, которое мне просто нужно обработать его и вернуть такой номер без изменений
@dbc о предоставленном вами dotnetfiddle. В окончательном варианте данные json по-прежнему представляют собой числа (без кавычек). Я использую другие функции newtonsoft, которые я не смог найти в System.Text.Json (поскольку они перебирают всех потомков), поэтому использование System. Json для меня приемлем, только если я могу изменить эти числа на строки, создать из них строку Json, а затем десериализовать новую строку JSON с помощью Newtonsoft.
@dbc ох, и что касается тебя, снова поиграй на скрипке. Теоретически, я думаю, это должно сработать, если я добавлю туда NumberHandling = System.Text.Json.Serialization.JsonNumberHandling.WriteAsStr в параметры сериализатора, но он по-прежнему выдает числа (а не строку в кавычках) на выходе, поэтому я думаю, что этот параметр не подходит вообще работаю
Вам нужно десериализовать в JToken
или вы можете десериализовать в фиксированную модель данных?
@dbc Нет, я не знаю, какой будет структура данных, поэтому я не могу использовать фиксированную модель данных
Я попытался создать собственный класс Converter, подобный показанному ниже, и использовал его для десериализации строки JSON, содержащей числа, и, похоже, он возвращает числа в виде строки без потери точности. Пожалуйста, попробуйте и дайте мне знать, если это сработает.
public class NumberToStringConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(decimal) || objectType == typeof(double) || objectType == typeof(float);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
if (token.Type == JTokenType.Float || token.Type == JTokenType.Integer)
{
return token.ToString();
}
throw new JsonSerializationException($"Unexpected token type: {token.Type}");
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteValue(value.ToString());
}
}
Я использовал его для такого примера var json = @"[{ ""foo"" : 0.1, ""bar"" : 1.00, ""baz"" : 0.0000000000000000000000000000000000000000000000000000001 }]"; Но в CanConvert() он вернул false, поскольку обнаруженный тип был JToken.
@MatteoDelOmbra, вы не сможете этого добиться, если не напишете свой собственный анализатор JSON и не будете использовать библиотеку «бесконечной точности» для обработки чисел. На мой взгляд, это ограничение ваших клиентов, поскольку научная запись в JSON полностью легальна: json.org/json-en.html
@MatteoDelOmbra - JSON, который вы предоставили здесь в своем комментарии, представляет собой массив объектов, а тот, который вы упомянули в своем вопросе, представляет собой один объект, поэтому вам нужно будет соответствующим образом настроить структуру вашего класса в соответствии с требованиями вашего клиента для его анализа. . Внесите соответствующие изменения, и этот метод отлично подойдет вам.
@YashGupta Я также запускаю его с помощью var json = @"{ ""foo"" : 0.1, ""bar"" : 1.00, ""baz"" : 0.0000000000000000000000000000000000000000000000000000001 }"; но поведение такое же
это может вам помочь stackoverflow.com/questions/78339575/…
МОЕ РЕШЕНИЕ
Вонючий код, но тот, который сработал для меня.
Вероятно, ужасная эффективность, но только одна, которая соответствует моим требованиям.
не очень понятно. Ограничение точности связано с внутренним представлением числа (т. е. двойным). Строка from/to не ограничивает точность, если не используется какая-либо схема форматирования. Однако я не думаю, что кто-то из них используется.