JSON исключил бесконечность и NaN; Статус JSON в ECMAScript?


180

Есть идеи, почему JSON не учел NaN и +/- Infinity? Это ставит Javascript в странную ситуацию, когда объекты, которые в противном случае были бы сериализуемыми, отсутствуют, если они содержат значения NaN или +/- бесконечность.

Похоже, что это было отлито из камня: см. RFC4627 и ECMA-262 (раздел 24.5.2, JSON.stringify, ПРИМЕЧАНИЕ 4, стр. 683 из документа ECMA-262 pdf при последнем редактировании):

Конечные числа выстраиваются как будто по телефону ToString(number). NaN и Бесконечность независимо от знака представлены в виде строки null.


Я не могу найти эту цитату ни в одном документе.
— wingedsubmariner

1
исправил это, похоже, что была устаревшая ссылка / устаревшее редактирование как-то.
— Джейсон С

Ответы:


90

Infinityи NaNне являются ключевыми словами или чем-то особенным, они являются просто свойствами глобального объекта (как есть undefined) и, как таковые, могут быть изменены. По этой причине JSON не включает их в спецификацию - по сути, любая истинная строка JSON должна иметь такой же результат в EcmaScript, если вы это делаете eval(jsonString)или JSON.parse(jsonString).

Если бы это было разрешено, то кто-то мог бы ввести код, похожий на

NaN={valueOf:function(){ do evil }};
Infinity={valueOf:function(){ do evil }};

в форум (или что-то еще), и тогда любое использование json на этом сайте может быть скомпрометировано.


29
Если вы оцените 1/0, вы получите Infinity, если вы оцените -1/0, вы получите -Infinity, если вы оцените 0/0, вы получите NaN.
— Джейсон С.

9
Но термины NaNи Infinityявляются именами свойств, поэтому пока String (1/0) выдает строку, "Infinity"которая является просто строковым представлением значения бесконечности. Это не возможно представить ни NaNили Infinityкак буквенные значения является ES - вы должны либо использовать выражение (например , 1/0, 0/0 и т.д.) . Или поиск свойства ( имеется в виду Infinityили NaN). Так как они требуют выполнения кода, они не могут быть включены в JSON.
— olliej

16
Что касается безопасности / защиты, то все, что нужно сделать приличному парсеру JSON при преобразовании NaN, - это получить значение 0/0 (вместо вычисления символа NaN), которое будет возвращать «реальный» NaN независимо от того, что символ NaN переопределяется как.
— Джейсон С

33
@olliej: вы утверждаете, что NaN не является литералом, я недостаточно знаю Javascript, чтобы судить о семантике javascript. Но для формата файла, в котором хранятся числа с плавающей запятой двойной точности, должен быть способ определения чисел с плавающей запятой IEEE, то есть с помощью литералов NaN / Infinity / NegInfinity. Это состояния 64-битных двойников, и поэтому они должны быть представимы. Есть люди, которые зависят от них (по причинам). Они, вероятно, были забыты, потому что JSON / Javascript возникли в веб-разработке, а не в научных вычислениях.
— wirrbel

35
Это 100%, абсолютно НЕПРАВИЛЬНО для JSON, чтобы произвольно опускать совершенно допустимые и стандартные состояния чисел с плавающей точкой NaN, Infinity и -Infinity. По сути, JSON решил поддержать произвольное подмножество значений с плавающей запятой IEEE, игнорируя три конкретных значения, потому что они жесткие или что-то в этом роде. Нет. Eval-способность даже не оправдание, потому что такие числа могли быть закодированы как литералы 1/0, -1/0 и 0/0. Это были бы действительные числа с добавлением «/ 0», которое не только просто обнаружить, но и фактически оценить как ES одновременно. Нет оправдания.
— Трийнко

56

По первоначальному вопросу: я согласен с пользователем "cbare" в том, что это неудачное упущение в JSON. IEEE754 определяет их как три специальных значения числа с плавающей запятой. Поэтому JSON не может полностью представлять числа с плавающей точкой IEEE754. На самом деле это даже хуже, поскольку JSON, как определено в ECMA262 5.1, даже не определяет, основаны ли его числа на IEEE754. Поскольку в процессе проектирования, описанном для функции stringify () в ECMA262, упоминаются три специальных значения IEEE, можно предположить, что на самом деле предполагалось поддерживать числа с плавающей запятой IEEE754.

В качестве еще одной точки данных, не связанной с вопросом: типы данных XML xs: float и xs: double do заявляют, что они основаны на числах с плавающей запятой IEEE754, и они поддерживают представление этих трех специальных значений (см. W3C XSD 1.0 Part 2 , Типы данных).


5
Я согласен, что это все неудачно. Но, возможно, хорошо, что числа JSON не указывают точный формат с плавающей запятой. Даже IEEE754 определяет много форматов - разные размеры и различие между десятичным и двоичным показателями. JSON особенно хорошо подходит для десятичной дроби, поэтому было бы жаль, если какой-то стандарт связал бы его с двоичным.
— Адриан Ратнапала

5
@AdrianRatnapala +1 Действительно: числа JSON имеют потенциально бесконечную точность, поэтому они намного лучше, чем спецификации IEEE, поскольку у них нет ограничения по размеру, ограничения по точности и эффекта округления (если сериализатор может с этим справиться).
— Арно Бушез

2
@ArnaudBouchez. Тем не менее, JSON должен по-прежнему поддерживать строки, представляющие NaN и + -Infinity. Даже если JSON не следует прикреплять к какому-либо формату IEEE, люди, определяющие числовой формат, должны хотя бы взглянуть на страницу википедии IEEE754 и немного подумать.
— Адриан Ратнапала


Это не неудачно. Смотрите ответ @CervEd. Это не связано с IEE754, что хорошо (даже если большинство языков программирования используют IEEE754 и, следовательно, требует дополнительной обработки в случае NaN и т. Д.).
— Людовик Куты

16

Не могли бы вы адаптировать шаблон нулевого объекта и в своем JSON представить такие значения как

"myNum" : {
   "isNaN" :false,
   "isInfinity" :true
}

Затем при проверке вы можете проверить тип

if (typeof(myObj.myNum) == 'number') {/* do this */}
else if (myObj.myNum.isNaN) {/* do that*/}
else if (myObj.myNum.isInfinity) {/* Do another thing */}

Я знаю, что в Java вы можете переопределить методы сериализации, чтобы реализовать такую ​​вещь. Не знаю, откуда твоя сериализация, поэтому я не могу подробно рассказать о том, как реализовать ее в методах сериализации.


1
хм ... это ответ на обходной путь; Я действительно не просил обойти, а скорее, почему эти значения исключали. Но +1 в любом случае.
— Джейсон С.

2
@Zoidberg: undefinedэто не ключевое слово, это свойство глобального объекта
— olliej

2
@Zoidberg: undefined - это свойство глобального объекта - это не ключевое слово, поэтому "undefined" in thisвозвращает значение true в глобальной области видимости. Это также означает, что вы можете сделать undefined = 42и if (myVar == undefined)становится (по существу) myVar == 42. Это возвращает нас к ранним дням экваскрипта, где undefinedне существовало по умолчанию, так что люди просто делали это var undefinedв глобальном масштабе. Следовательно, undefinedнельзя было сделать ключевое слово, не нарушая существующие сайты, и поэтому мы были обречены на все времена, чтобы undefined был обычным свойством.
— olliej

2
@olliej: я понятия не имею, почему вы думаете, что undefined - это свойство глобального объекта. По умолчанию поиск undefined является встроенным значением undefined. Если вы переопределите его с помощью «undefined = 42», то при доступе к undefined в качестве поиска переменной вы получите переопределенное значение. Но попробуйте выполнить "zz = undefined; undefined = 42; x = {}; 'undefined old =' + (xa === zz) + ', undefined new =' + (xa === undefined)". Вы никогда не сможете переопределить внутренние значения null, undefined, NaN или Infinity, даже если вы можете переопределить их поиск символов.
— Джейсон С.

2
@Jason undefinedявляется глобальным свойством, потому что оно указано как таковое. Обратитесь к 15.1.1.3 из ECMAScript-262 3-е изд.
— Кангакс

11

Строки «Infinity», «-Infinity» и «NaN» все приводят к ожидаемым значениям в JS. Поэтому я бы сказал, что правильный способ представления этих значений в JSON - это строки.

> +"Infinity"
Infinity

> +"-Infinity"
-Infinity

> +"NaN"
NaN

Обидно, что JSON.stringify не делает этого по умолчанию. Но есть способ:

> JSON.stringify({ x: Infinity }, function (k,v) { return v === Infinity ? "Infinity" : v; })
"{"x":"Infinity"}"

1
0/0 и т. Д. Не являются допустимыми в формате JSON. Вы должны работать в рамках стандарта, а струны прекрасно справляются со своей задачей.
— teh_senaus

Напротив, я думаю, что это единственное практическое решение, но я сделаю функцию, которая возвращает NaN, если входное значение равно «NaN» и т. Д. Способ, которым вы выполняете преобразование, подвержен внедрению кода.
— Марко Сулла

3
Значения JSON не могут быть арифметическими выражениями ... цель отделения стандарта от синтаксиса литерала языка состоит в том, чтобы сделать JSON десериализованным без выполнения какого-либо из них как кода. Не уверен, почему мы не могли иметь NaNи Infinityдобавлять в качестве значений ключевых слов, как trueи false, хотя.
— Марк Рид

Чтобы сделать это более явным, мы можем использовать Number("Infinity"), Number("-Infinity")иNumber("NaN")
— HKTonyLee

Это работа, как магия. JSON.parse("{ \"value\" : -1e99999 }")легко вернуться { value:-Infinity }в JavaScript. Только это просто не совместимо с пользовательским типом номера, который может быть больше, чем
— Thaina

7

Если у вас есть доступ к коду сериализации, вы можете представить Infinity как 1.0e + 1024. Показатель степени слишком велик для двойного представления, и при десериализации это представляется как бесконечность. Работает над webkit, не уверен насчет других парсеров json!


4
IEEE754 поддерживает 128-битные числа с плавающей запятой, поэтому 1.0e5000 лучше
— Тон Помп

2
Тонн: 128 бит был добавлен позже. Что если они решат добавить 256 бит? Тогда вам придется добавить больше нулей, и существующий код будет вести себя по-другому. Infinityвсегда будет Infinity, так почему бы не поддержать это?
— летающая овца

1
Умная идея! Я собирался либо переключиться на другой формат, либо добавить громоздкий обходной код в мой анализатор. Не идеально для каждого случая, но в моем случае, когда бесконечность служит просто оптимизированным краем для сходящейся последовательности, она просто идеальна, и даже если будет введена большая точность, она все равно будет в основном правильной. Спасибо!
— Или Шарир

3
1, -1 и 0 ..... идеально допустимые / анализируемые числа становятся этими тремя специальными значениями, когда вы просто добавляете /0их в конец. Это легко разбирается, сразу видно и даже оценивается. Непростительно, что они еще не добавили его в стандарт: « {"Not A Number":0/0,"Infinity":1/0,"Negative Infinity":-1/0} Почему нет? alert(eval("\"Not A Number\"") //works alert(eval("1/0")) //also works, prints 'Infinity', Нет оправдания.
— Трийнко


1

Текущий стандарт IEEE 754-2008 включает определения для двух разных 64-битных представлений с плавающей точкой: десятичный 64-битный тип с плавающей точкой и двоичный 64-битный тип с плавающей точкой.

После округления строка .99999990000000006такая же, как .9999999в двоичном 64-битном представлении IEEE, но она НЕ совпадает .9999999с десятичным 64-битным представлением IEEE. В 64-битном IEEE десятичное число с плавающей запятой .99999990000000006округляется до значения, .9999999000000001которое не совпадает с десятичным .9999999значением.

Поскольку JSON просто обрабатывает числовые значения как числовые строки десятичных цифр, система, которая поддерживает двоичные и десятичные представления с плавающей точкой IEEE (например, IBM Power), не может определить, какое из двух возможных числовых значений с плавающей точкой IEEE является предназначена.


Какое это имеет отношение к вопросу? (что касается Бесконечности и NaN)
— Брайан

1

Потенциальный обходной путь для таких случаев, как {"key": Infinity}:

JSON.parse(theString.replace(/":(Infinity|-IsNaN)/g, '":"{{$1}}"'), function(k, v) {
   if (v === '{{Infinity}}') return Infinity;
   else if (v === '{{-Infinity}}') return -Infinity;
   else if (v === '{{NaN}}') return NaN;
   return v;
   });

Общая идея состоит в том, чтобы заменить вхождения недопустимых значений строкой, которую мы распознаем при разборе, и заменить ее соответствующим представлением JavaScript.


Я не знаю, почему это решение получило отрицательную оценку, потому что, честно говоря, если вы столкнетесь с ситуацией, когда ваша строка JSON содержит значения Infinity или IsNaN, то при попытке ее проанализировать произойдет сбой. Используя эту технику, вы сначала заменяете вхождения IsNaN или Infinity чем-то другим (чтобы изолировать их от любой допустимой строки, которая может содержать эти термины), и используете JSON.parse (string, callback) для возврата правильных, допустимых значений JavaScript. Я использую это в рабочем коде и никогда не было никаких проблем.
— Шамель

Разве это не испортило бы Бесконечность внутри струн? Для многих случаев использования, вероятно, можно предположить, что это не проблема, но решение не является полностью надежным.
— olejorgenb

1

Причина указана на странице ii в стандарте ECMA-404. Синтаксис обмена данными JSON, 1-е издание

JSON не знает числа. В любом языке программирования может быть множество типов чисел различной емкости и дополнения, фиксированное или плавающее, двоичное или десятичное. Это может затруднить обмен между различными языками программирования. Вместо этого JSON предлагает только представление чисел, которые используют люди: последовательность цифр. Все языки программирования знают, как понимать последовательности цифр, даже если они не согласны с внутренними представлениями. Этого достаточно, чтобы позволить обмен.

Причина не в том, как многие утверждали, из-за представлений NaNи Infinityсценария ECMA. Простота является основным принципом проектирования JSON.

Поскольку это так просто, не ожидается, что грамматика JSON когда-либо изменится. Это дает JSON, как основополагающему обозначению, огромную стабильность


-3

Если, как и я, у вас нет контроля над кодом сериализации, вы можете иметь дело со значениями NaN, заменив их нулевым или любым другим значением, что выглядит как хакерство следующим образом:

$.get("file.json", theCallback)
.fail(function(data) {
  theCallback(JSON.parse(data.responseText.replace(/NaN/g,'null'))); 
} );

По сути, .fail будет вызван, когда оригинальный анализатор json обнаружит недопустимый токен. Затем для замены недопустимых токенов используется строка replace. В моем случае это исключение для сериализатора, возвращающего значения NaN, поэтому этот метод является лучшим подходом. Если результаты обычно содержат недопустимый токен, лучше не использовать $ .get, а вместо этого вручную извлекать результат JSON и всегда выполнять замену строки.


21
Умный, но не совсем надежный. Попробуйте это с{ "tune": "NaNaNaNaNaNaNaNa BATMAN", "score": NaN }
— JJJ

1
и вы должны использовать JQuery. У меня нет $ .get ().
— Джейсон С
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.