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.