JSON pominął Infinity i NaN; Status JSON w ECMAScript?


179

Masz pojęcie, dlaczego JSON pomijał NaN i +/- Infinity? Stawia JavaScript w dziwnej sytuacji, w której obiekty, które w innym przypadku byłyby możliwe do serializacji, nie są, jeśli zawierają wartości NaN lub +/- nieskończoności.

Wygląda na to, że zostało to odlane w kamieniu: patrz RFC4627 i ECMA-262 (sekcja 24.5.2, JSON.stringify, UWAGA 4, strona 683 ECMA-262 pdf w ostatniej edycji):

Liczby skończone są taktowane jak przez wywołanie ToString(number). NaN i Infinity niezależnie od znaku są reprezentowane jako ciąg null.


Nie mogę znaleźć tego cytatu w żadnym dokumencie.
wingedsubmariner

1
naprawiono to, wygląda na to, że jakoś była nieaktualna referencja / nieaktualna edycja.
Jason S

Odpowiedzi:


90

Infinityi NaNnie są słowami kluczowymi ani niczym specjalnym, są tylko właściwościami obiektu globalnego (jak jest undefined) i jako takie można je zmienić. Z tego powodu JSON nie włącza ich do specyfikacji - w zasadzie każdy prawdziwy ciąg JSON powinien mieć taki sam wynik w EcmaScript, jeśli to zrobisz eval(jsonString)lub JSON.parse(jsonString).

Gdyby było dozwolone, ktoś mógłby wstrzyknąć kod podobny do

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

na forum (lub cokolwiek innego), a następnie jakiekolwiek użycie JSON w tej witrynie może być zagrożone.


29
Jeśli ocenisz 1/0, otrzymasz Infinity, jeśli ocenisz -1/0, otrzymasz -Infinity, jeśli ocenisz 0/0, otrzymasz NaN.
Jason S

9
Ale terminy NaNi Infinitysą nazwami własności, więc podczas gdy String (1/0) tworzy ciąg, "Infinity"który jest po prostu ciągiem reprezentującym wartość nieskończoności. Nie jest możliwe, aby reprezentować albo NaNczy Infinityjako dosłowne wartości jest ES - albo trzeba użyć wyrażenia (np 1/0, 0/0 itd.) Lub odnośnika własności (w odniesieniu do Infinityalbo NaN). Ponieważ wymagają one wykonania kodu, nie można ich włączyć do JSON.
olliej

16
Jeśli chodzi o bezpieczeństwo / ochronę, wystarczy przyzwoity parser JSON, aby przekonwertować NaN, aby uzyskać wartość 0/0 (zamiast oceny symbolu NaN), która zwróci „rzeczywisty” NaN niezależnie od tego, co symbol NaN został ponownie zdefiniowany jako.
Jason S

33
@olliej: argumentujesz, że NaN nie jest dosłowny, nie znam Javascript wystarczająco, aby ocenić semantykę javascript. Ale w przypadku formatu pliku, który przechowuje liczby zmiennoprzecinkowe podwójnej precyzji, powinien istnieć sposób zdefiniowania liczb zmiennoprzecinkowych IEEE, tzn. Literałów NaN / Infinity / NegInfinity. Są to stany 64-bitowych podwójnych i jako takie powinny być reprezentowalne. Są ludzie, którzy są od nich zależni (z przyczyn). Prawdopodobnie zostały zapomniane, ponieważ JSON / JavaScript powstało w projektowaniu stron internetowych zamiast w obliczeniach naukowych.
wirrbel

35
Jest w 100% absolutnie NIEPRAWIDŁOWE, aby JSON samowolnie pominął doskonale poprawne i standardowe stany liczb zmiennoprzecinkowych NaN, Infinity i -Infinity. Zasadniczo JSON postanowił wesprzeć dowolny podzbiór wartości zmiennoprzecinkowych IEEE, ignorując ignorując trzy konkretne wartości, ponieważ są one trudne lub coś w tym rodzaju. Nie. Zdolność ewaluacyjna nie jest nawet wymówką, ponieważ takie liczby mogłyby być zakodowane jako literały 1/0, -1/0 i 0/0. Byłyby to prawidłowe liczby z dopiskiem „/ 0”, które nie tylko są łatwe do wykrycia, ale w rzeczywistości można je jednocześnie ocenić jako ES. Bez wymówek.
Triynko,

55

Na pierwotne pytanie: zgadzam się z użytkownikiem „cbare”, ponieważ jest to niefortunne pominięcie w JSON. IEEE754 definiuje je jako trzy specjalne wartości liczby zmiennoprzecinkowej. Zatem JSON nie może w pełni reprezentować liczb zmiennoprzecinkowych IEEE754. W rzeczywistości jest jeszcze gorzej, ponieważ JSON zdefiniowany w ECMA262 5.1 nawet nie określa, czy jego liczby są oparte na IEEE754. Ponieważ przepływ projektowania opisany dla funkcji stringify () w ECMA262 wspomina o trzech specjalnych wartościach IEEE, można podejrzewać, że w rzeczywistości chodziło o obsługę liczb zmiennoprzecinkowych IEEE754.

Jako jeden inny punkt danych niezwiązany z pytaniem: typy danych XML xs: float i xs: double informują, że są oparte na liczbach zmiennoprzecinkowych IEEE754 i obsługują reprezentację tych trzech wartości specjalnych (patrz W3C XSD 1.0 część 2 , Typy danych).


5
Zgadzam się, że to wszystko jest niefortunne. Ale być może dobrze, że liczby JSON nie określają dokładnego formatu zmiennoprzecinkowego. Nawet IEEE754 określa wiele formatów - różne rozmiary i rozróżnienie wykładników dziesiętnych i binarnych. JSON jest szczególnie dobrze dostosowany do dziesiętnych, więc szkoda byłoby, gdyby jakiś standard przypiął go do binarnego.
Adrian Ratnapala

5
@AdrianRatnapala +1 Rzeczywiście: Liczby JSON mają potencjalnie nieskończoną precyzję, więc są znacznie lepsze niż specyfikacje IEEE, ponieważ nie mają limitu wielkości, limitu precyzji i żadnego efektu zaokrąglania (jeśli serializator może to obsłużyć).
Arnaud Bouchez

2
@ArnaudBouchez. To powiedziawszy, JSON powinien nadal obsługiwać łańcuchy reprezentujące NaN i + -Infinity. Nawet jeśli JSON nie powinien być przypięty do żadnego formatu IEEE, ludzie definiujący format liczb powinni przynajmniej spojrzeć na stronę wikipedii IEEE754 i chwilę się zastanowić.
Adrian Ratnapala


To nie jest niefortunne. Zobacz odpowiedź @CervEd. Nie jest związany z IEE754, co jest dobrą rzeczą (nawet jeśli większość języków programowania używa IEEE754, a zatem wymaga dodatkowego przetwarzania w przypadku NaN itp.).
Ludovic Kuty

16

Czy możesz dostosować wzorzec obiektu zerowego, aw swoim JSON reprezentować takie wartości jak

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

Następnie podczas sprawdzania możesz sprawdzić typ

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

Wiem, że w Javie możesz zastąpić metody serializacji, aby zaimplementować coś takiego. Nie jestem pewien, skąd twoja serializacja, więc nie mogę podać szczegółów, jak zaimplementować ją w metodach serializacji.


1
hmmm ... to odpowiedź na obejście; Tak naprawdę nie prosiłem o obejście, ale raczej o to, dlaczego te wartości były wykluczone. Ale i tak +1.
Jason S

2
@Zoidberg: undefinednie jest słowem kluczowym, jest własnością obiektu globalnego
olliej

2
@Zoidberg: undefined jest właściwością obiektu globalnego - nie jest słowem kluczowym, więc "undefined" in thiszwraca wartość true w zakresie globalnym. Oznacza to również, że możesz to zrobić undefined = 42i if (myVar == undefined)staje się (zasadniczo) myVar == 42. To nawiązuje do początków evascript nee javascript, w których undefineddomyślnie nie istniały, więc ludzie po prostu działali var undefinedw zakresie globalnym. W związku z tym undefinednie można uczynić słowa kluczowego bez zerwania z istniejącymi witrynami, dlatego byliśmy skazani na wieczny niezdecydowanie, że jest to normalna właściwość.
olliej

2
@olliej: Nie mam pojęcia, dlaczego uważasz, że undefined jest własnością obiektu globalnego. Domyślnie wyszukiwanie niezdefiniowanej jest wbudowaną wartością niezdefiniowanej. Jeśli zastąpisz to parametrem „undefined = 42”, to gdy uzyskasz dostęp do niezdefiniowanego jako wyszukiwanie zmiennej, otrzymasz nadpisaną wartość. Ale spróbuj zrobić „zz = niezdefiniowany; niezdefiniowany = 42; x = {}; 'niezdefiniowany stary =' + (xa === zz) + ', niezdefiniowany nowy =' + (xa === niezdefiniowany)”. Nigdy nie można przedefiniować wewnętrznych wartości null, undefined, NaN lub Infinity, nawet jeśli można przesłonić ich wyszukiwanie symboli.
Jason S

2
@Jason undefinedjest własnością globalną, ponieważ jest określona jako taka. Zobacz 15.1.1.3 ECMAScript-262 wydanie 3.
kangax

11

Ciągi „Infinity”, „-Infinity” i „NaN” wszystkie wymuszają oczekiwane wartości w JS. Więc argumentowałbym, że właściwym sposobem reprezentowania tych wartości w JSON są ciągi znaków.

> +"Infinity"
Infinity

> +"-Infinity"
-Infinity

> +"NaN"
NaN

Szkoda tylko, że JSON.stringify nie robi tego domyślnie. Ale jest sposób:

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

1
0/0 itd. Są niepoprawne JSON. Musisz pracować w granicach standardu, a łańcuchy dobrze sobie radzą.
teh_senaus

Przeciwnie, myślę, że jest to jedyne praktyczne rozwiązanie, ale wykonam funkcję, która zwraca NaN, jeśli wartość wejściowa to „NaN” itp. Sposób konwersji jest podatny na wstrzykiwanie kodu.
Marco Sulla,

3
Wartości JSON nie mogą być wyrażeniami arytmetycznymi ... celem oddzielenia standardu od literalnej składni języka jest uczynienie JSON deeserializowalnym bez wykonywania żadnego z nich jako kodu. Nie jestem pewien, dlaczego nie mogliśmy NaNi Infinitydodaliśmy jako wartości słów kluczowych takich jak truei false.
Mark Reed,

Żeby było bardziej wyraźne, możemy użyć Number("Infinity"), Number("-Infinity")iNumber("NaN")
HKTonyLee

To praca jak magia. JSON.parse("{ \"value\" : -1e99999 }")łatwo wrócić { value:-Infinity }w javascript. Tylko to po prostu nie jest kompatybilne z niestandardowym typem numeru, który może być większy
Thaina

7

Jeśli masz dostęp do kodu serializacji, możesz reprezentować Infinity jako 1.0e + 1024. Wykładnik jest zbyt duży, aby reprezentować go podwójnie, a po deserializacji jest reprezentowany jako Nieskończoność. Działa na zestawie internetowym, niepewny co do innych parserów json!


4
IEEE754 obsługuje 128-bitowe liczby zmiennoprzecinkowe, więc 1.0e5000 jest lepszy
Ton Plomp

2
Ton: 128 bitów dodano później. Co jeśli zdecydują się dodać 256 bitów? Następnie musisz dodać więcej zer, a istniejący kod będzie zachowywać się inaczej. Infinityzawsze będzie Infinity, więc dlaczego nie wesprzeć tego?
latające owce

1
Sprytny pomysł! Właśnie miałem przejść do innego formatu lub dodać kłopotliwy kod obejścia do mojego parsera. Nie jest idealny dla każdego przypadku, ale w moim przypadku, gdzie nieskończoność służy tylko jako zoptymalizowany przypadek krawędzi do zbieżnej sekwencji, jest po prostu idealny i nawet gdyby wprowadzono większą precyzję, nadal byłby w większości poprawny. Dzięki!
Lub Sharir

3
1, -1 i 0 ... idealnie poprawne / parsowalne liczby, stają się tymi trzema specjalnymi wartościami, gdy dodajesz /0je na końcu. Jest łatwo analizowalny, natychmiast widoczny, a nawet oceniany. Jest niewybaczalne, że nie dodali go jeszcze do standardu: {"Not A Number":0/0,"Infinity":1/0,"Negative Infinity":-1/0} << Dlaczego nie? alert(eval("\"Not A Number\"") //works alert(eval("1/0")) //also works, prints 'Infinity'. Bez wymówek.
Triynko,


1

Obecny standard IEEE 754-2008 zawiera definicje dwóch różnych 64-bitowych reprezentacji zmiennoprzecinkowych: dziesiętnego 64-bitowego typu zmiennoprzecinkowego i binarnego 64-bitowego typu zmiennoprzecinkowego.

Po zaokrągleniu ciąg znaków .99999990000000006jest taki sam jak .9999999w binarnej reprezentacji 64-bitowej IEEE, ale NIE jest taki sam jak .9999999w reprezentacji dziesiętnej 64-bitowej IEEE. W 64-bitowym .99999990000000006zaokrągleniu dziesiętnym zmiennoprzecinkowym IEEE do wartości, .9999999000000001która nie jest taka sama jak .9999999wartość dziesiętna .

Ponieważ JSON po prostu traktuje wartości liczbowe jako ciągi liczb dziesiętnych, system nie obsługuje zarówno binarnych, jak i dziesiętnych reprezentacji zmiennoprzecinkowych IEEE (takich jak IBM Power), aby określić, która z dwóch możliwych wartości liczbowych zmiennoprzecinkowych IEEE jest zamierzony.


Co to ma wspólnego z pytaniem? (która dotyczy Infinity i NaN)
Bryan

1

Potencjalne obejście takich przypadków jak {„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;
   });

Ogólną ideą jest zastąpienie wystąpień niepoprawnych wartości ciągiem, który rozpoznamy podczas analizy i zastąpienie go odpowiednią reprezentacją JavaScript.


Nie wiem, dlaczego to rozwiązanie zostało ocenione negatywnie, ponieważ szczerze mówiąc, jeśli napotkasz sytuację, w której Twój ciąg JSON zawiera wartości Infinity lub IsNaN, zakończy się niepowodzeniem, gdy spróbujesz go przeanalizować. Korzystając z tej techniki, najpierw zamieniasz wystąpienia IsNaN lub Infinity na coś innego (aby odizolować je od dowolnego poprawnego ciągu, który może zawierać te warunki), a następnie użyj JSON.parse (ciąg, wywołanie zwrotne), aby zwrócić prawidłowe, prawidłowe wartości JavaScript. Używam tego w kodzie produkcyjnym i nigdy nie miałem żadnych problemów.
SHamel

Czy to nie zepsułoby Infinity wewnątrz łańcuchów? W wielu przypadkach można bezpiecznie założyć, że nie stanowi to problemu, ale rozwiązanie nie jest w pełni niezawodne.
olejorgenb

1

Przyczynę podano na stronie ii w Standardowej ECMA-404 Składnia wymiany danych JSON, wydanie pierwsze

JSON jest agnostykiem co do liczb. W dowolnym języku programowania może istnieć wiele typów liczb o różnych pojemnościach i uzupełnieniach, stałych lub zmiennych, binarnych lub dziesiętnych. Może to utrudnić wymianę między różnymi językami programowania. Zamiast tego JSON oferuje tylko reprezentację liczb używanych przez ludzi: ciąg cyfr. Wszystkie języki programowania wiedzą, jak zrozumieć sekwencje cyfr, nawet jeśli nie zgadzają się co do wewnętrznych reprezentacji. To wystarczy, aby umożliwić wymianę.

Przyczyna nie jest, jak wielu twierdziło, ze względu na reprezentację NaNi Infinityskrypt ECMA. Prostota jest podstawową zasadą projektowania JSON.

Ponieważ jest to tak proste, nie oczekuje się, że gramatyka JSON kiedykolwiek się zmieni. To daje JSON, jako podstawową notację, ogromną stabilność


-3

Jeśli, podobnie jak ja, nie masz kontroli nad kodem serializacji, możesz poradzić sobie z wartościami NaN, zastępując je wartościami null lub dowolnymi innymi wartościami w ramach hackowania w następujący sposób:

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

Zasadniczo .fail zostanie wywołany, gdy oryginalny parser json wykryje nieprawidłowy token. Następnie zastąpiono ciąg znaków, aby zastąpić nieprawidłowe tokeny. W moim przypadku serializator jest wyjątkiem, aby zwracać wartości NaN, więc ta metoda jest najlepszym podejściem. Jeśli wyniki zwykle zawierają nieprawidłowy token, lepiej byłoby nie używać $ .get, ale zamiast tego ręcznie pobrać wynik JSON i zawsze uruchamiać zamianę łańcucha.


21
Sprytny, ale nie do końca niezawodny. Wypróbuj{ "tune": "NaNaNaNaNaNaNaNa BATMAN", "score": NaN }
JJJ

1
i musisz używać jQuery. Nie mam $ .get ().
Jason S
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.