JSON залишив безмежність і NaN; Статус JSON в ECMAScript?


180

Будь-яка ідея, чому JSON залишив NaN та +/- нескінченність? Це ставить Javascript у дивну ситуацію, коли об’єкти, які б інакше були серіалізуються, не є, якщо вони містять значення NaN або +/- нескінченності.

Схоже, це було відлито з каменю: див. RFC4627 та ECMA-262 (розділ 24.5.2, JSON.stringify, ПРИМІТКА 4, стор. 683 pdf ECMA-262 останнього редагування):

Кінцеві номери піддаються розбитковій формі, ніби за викликом ToString(number). NaN і Нескінченність незалежно від знака представлені як String null.


Я не можу знайти цю цитату в жодному документі.
wingedsubmariner

1
виправлено це, схоже на те, що якимось чином існував несвіжий посилання / несвіжий редагування.
Jason S

Відповіді:


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, ви отримуєте нескінченність, якщо оцінюєте -1/0, ви отримуєте -інфініти, якщо ви оцінюєте 0/0, ви отримуєте NaN.
Jason S

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 переосмислюється як.
Джейсон S

33
@olliej: ти стверджуєш, що NaN не є буквальним, я не знаю Javascript достатньо, щоб судити про семантику javascript. Але для формату файлів, що зберігає подвійні точності числа з плаваючою точкою, повинен існувати спосіб визначення плавців IEEE, тобто буквами NaN / Infinity / NegInfinity. Це стани 64-бітного подвоєння і як такі повинні бути представними. Є люди, які залежать від них (з причин). Вони, ймовірно, були забуті, оскільки JSON / Javascript виникла в веб-розробках замість наукових обчислень.
wirrbel

35
Це 100%, абсолютно НЕБЕЗПЕЧНО для JSON, що довільно опущено ідеально дійсні та стандартні стани чисел з плаваючою комою NaN, Нескінченності та -Бескітності. По суті, JSON вирішив підтримати довільну підмножину плаваючих значень IEEE, необізнано пропустивши три конкретні значення, оскільки вони важкі чи щось таке. Ні. Здатність до оцінок навіть не є приводом, оскільки такі числа могли бути закодовані як літерали 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, частина 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 */}

Я знаю, що на Яві ви можете перекрити методи серіалізації, щоб реалізувати таке. Не впевнений, звідки ваша серіалізація, тому я не можу надати детальну інформацію про те, як її реалізувати в методах серіалізації.


1
хммм ... це відповідь на вирішення; Я насправді не просив вирішити питання, а навпаки, чому ці значення виключали. Але +1 все одно.
Джейсон S

2
@Zoidberg: undefinedце не ключове слово, це властивість у глобальному об'єкті
olliej

2
@Zoidberg: undefined є властивістю глобального об’єкта - це не ключове слово, тому "undefined" in thisповертає true у глобальній області. Це також означає, що ти можеш робити undefined = 42і if (myVar == undefined)стаєш (по суті) myVar == 42. Це повертається до раннього часу ecmascript nee javascript, де undefinedне існувало за замовчуванням, тому люди просто зробили var undefinedв глобальному масштабі. Отже, undefinedне вдалося створити ключове слово, не порушуючи існуючі сайти, і тому ми були приречені на весь час визначити, що це не нормальна властивість.
olliej

2
@olliej: Я поняття не маю, чому ви вважаєте, що невизначене - це властивість глобального об'єкта. За замовчуванням пошук undefined є вбудованим значенням undefined. Якщо ви заміните його на "undefined = 42", тоді, коли ви отримуєте доступ до undefined як пошук змінної, ви отримуєте переосмислене значення. Але спробуйте зробити "zz = undefined; undefined = 42; x = {}; 'undefined old =' + (xa === zz) + ', undefined new =' + (xa === undefined)". Ніколи не можна переосмислювати внутрішні значення null, undefined, NaN або Infinity, навіть якщо ви можете перекрити їхні пошукові символи.
Джейсон S

2
@Jason undefined- це глобальна властивість, оскільки вона вказана як така. Зверніться до 15.1.1.3 ECMAScript-262 3-го видання.
кенгакс

11

Рядки "Нескінченність", "Нескінченність" і "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" і т. Д. Те, як ви перетворюєте, схильне до введення коду.
Marco Sulla

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

Для того, щоб зробити його більш явним, ми можемо використовувати Number("Infinity"), Number("-Infinity")іNumber("NaN")
HKTonyLee

Це робота, як магія. JSON.parse("{ \"value\" : -1e99999 }")легко повернутися { value:-Infinity }в JavaScript. Тільки він просто не сумісний із
типовим

7

Якщо у вас є доступ до коду серіалізації, ви можете представляти Infinity як 1.0e + 1024. Експонент занадто великий, щоб представляти в подвійному, а при десеріалізації це представляється як Нескінченність. Працює над webkit, не впевнений у інших json-аналізаторах!


4
IEEE754 підтримує 128-бітні цифри з плаваючою комою, тому 1.0e5000 краще
Ton Plomp

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 Std 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 (рядок, зворотний виклик), щоб повернути належні, дійсні значення JavaScript. Я використовую це у виробничому коді і ніколи не виникало жодних проблем.
Шамель

Хіба це не зіпсує нескінченність всередині струн? Для багатьох випадків використання, напевно, можна припустити, що це не проблема, але рішення не є надійним.
olejorgenb

1

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

JSON сприймає цифри. У будь-якій мові програмування може бути безліч типів чисел різних ємностей та доповнень, фіксованих або плаваючих, двійкових чи десяткових. Це може ускладнити обмін між різними мовами програмування. Натомість JSON пропонує лише представлення чисел, якими користуються люди: послідовність цифр. Усі мови програмування знають, як зрозуміти послідовності цифр, навіть якщо вони не погоджуються щодо внутрішніх уявлень. Цього достатньо, щоб дозволити обмін.

Причина не в тому, як багато хто стверджує, в представленнях сценарію NaNта InfinityECMA. Простота - це основний принцип дизайну JSON.

Оскільки це так просто, не очікується, що граматика JSON колись зміниться. Це дає JSON, як основоположне позначення, величезну стабільність


-3

Якщо ви, як я, у вас немає контролю над кодом серіалізації, ви можете мати справу з значеннями NaN, замінюючи їх на нулеве або будь-яке інше значення як біт хак, як описано нижче:

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

По суті, .fail буде викликано, коли оригінальний аналізатор json виявить недійсний маркер. Тоді заміна рядка використовується для заміни недійсних маркерів. У моєму випадку серіалізатор повертає значення NaN, тому цей метод є найкращим підходом. Якщо результати зазвичай містять недійсний маркер, вам краще не використовувати $ .get, а замість цього вручну отримати результат JSON і завжди запустити заміну рядка.


21
Розумний, але не зовсім дурний. Спробуйте{ "tune": "NaNaNaNaNaNaNaNa BATMAN", "score": NaN }
JJJ

1
і ви повинні використовувати jQuery. У мене немає $ .get ().
Jason S
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.