JSON left out Infinity and NaN; JSON status in ECMAScript?
Asked Answered
M

10

224

Any idea why JSON left out NaN and +/- Infinity? It puts Javascript in the strange situation where objects that would otherwise be serializable, are not, if they contain NaN or +/- infinity values.

Looks like this has been cast in stone: see RFC4627 and ECMA-262 (section 24.5.2, JSON.stringify, NOTE 4, page 683 of the ECMA-262 pdf at last edit):

Finite numbers are stringified as if by calling ToString(number). NaN and Infinity regardless of sign are represented as the String null.

Martins answered 14/9, 2009 at 18:11 Comment(4)
I can't find that quote in either document.Suasion
fixed it, looks like there was a stale reference / stale edit somehow.Martins
dbj.org/json-how-to-create-an-undefined-value-or-an-nan-valueCassey
Also in ECMA-404 2nd ed.: "Numeric values that cannot be represented as sequences of digits (such as Infinity and NaN) are not permitted"Greco
V
106

Infinity and NaN aren't keywords or anything special, they are just properties on the global object (as is undefined) and as such can be changed. It's for that reason JSON doesn't include them in the spec -- in essence any true JSON string should have the same result in EcmaScript if you do eval(jsonString) or JSON.parse(jsonString).

If it were allowed then someone could inject code akin to

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

into a forum (or whatever) and then any json usage on that site could be compromised.

Vtol answered 14/9, 2009 at 21:34 Comment(4)
I have read most of the ECMAScript spec and, of course, I knew about NaN and Infinity, but I just learned 'undefined' is actually a property of the global object and I am absolutely counfounded.Nimwegen
This is nonsense. eval(jsonString) will be able to compromise anything regardless of whether NaN or Infinity are allowed. eval("alert('1')") isn't valid json but will work There's not even a way to express function()` in json. This is just a misuse of eval.Supercilious
If you are evaluating properties using eval, any property can be used to compromise the system - it has nothing to do with NaN or Infinity.Hog
@OrestisP. So as a heads up, when I wrote this answer, JSON.parse was not universally available. The way json was originally parsed was essentially eval(), or various scripts (json.js) that endeavored to filter/regex match a string prior to passing it to ... eval. That dictated how JSON started its existence as a "spec".Vtol
T
67

On the original question: I agree with user "cbare" in that this is an unfortunate omission in JSON. IEEE754 defines these as three special values of a floating point number. So JSON cannot fully represent IEEE754 floating point numbers. It is in fact even worse, since JSON as defined in ECMA262 5.1 does not even define whether its numbers are based on IEEE754. Since the design flow described for the stringify() function in ECMA262 does mention the three special IEEE values, one can suspect that the intention was in fact to support IEEE754 floating point numbers.

As one other data point, unrelated to the question: XML datatypes xs:float and xs:double do state that they are based on IEEE754 floating point numbers, and they do support the representation of these three special values (See W3C XSD 1.0 Part 2, Datatypes).

Titus answered 9/2, 2012 at 21:19 Comment(7)
I agree this is all unfortunate. But perhaps it is a good thing that JSON numbers don't specify the exact floating point format. Even IEEE754 specifies many formats -- different sizes, and a distinction between decimal and binary exponents. JSON is particularly well suited to decimal, so it would be a pity if some standard were to pin it to binary.Friedland
@AdrianRatnapala +1 Indeed: JSON numbers have potentially infinite precision, so are much better than IEEE specifications, since they have no size limit, no precision limit, and no rounding effect (if the serializer can handle it).Sophisticate
@ArnaudBouchez. That said, JSON should still support strings representing NaN and +-Infinity. Even if JSON should not be pinned to any IEEE format, people defining number format should at least look at the wikipedia page IEEE754 and stop a while to think.Friedland
dbj.org/json-how-to-create-an-undefined-value-or-an-nan-valueCassey
This is not unfortunate. See the answer by @CervEd. It is not tied to IEE754 which is a good thing (even if most programming languages use IEEE754 and thus requires additional processing in case of NaN, etc.).Tammietammuz
Clearly +-INF and NaN were intended as error reporting for failed math operations, not as "numbers". The behavior of processors when doing additional math with these as inputs is a small nightmare all its own. NaN means "Not A Number"; why the heck would you allow storing it in a number field? Might as well ask why we can't store arbitrary strings or PNG files in double fields?Jacobine
Like it or not, NaN and Infinity are perfectly valid floating point numbers supported by every processor out there. If JSON won't support them, then every user is forced to deal with this issue every time they try to encode floating point numbers in JSON.Gnomic
L
21

Could you adapt the null object pattern, and in your JSON represent such values as

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

Then when checking, you can check for the type

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

I know in Java you can override serialization methods in order to implement such a thing. Not sure where your serializing from, so I can't give details on how to implement it in the serialization methods.

Leix answered 14/9, 2009 at 18:18 Comment(9)
hmmm... that's an answer to a workaround; I wasn't really asking for a workaround but rather for why these values were excluding. But +1 anyway.Martins
As for the why, I am not sure why they would leave these out. Perhaps they expect you to use the max and min values for the particular number, such as Integer.MAX_VALUE. Up until I.E. 5.5 they didn't even have a null keyword in javascript (at least for IE which sucks), you couldn't even use the undefined keyword, which made javascript so much more difficult. This may be the same thing, perhaps browsers will adopt this more in the future.Leix
@Zoidberg: undefined isn't a keyword, it's a property on the global objectVtol
@olliej: if (myVar == undefined) and undefined is in the global object? I don't understand, can you elaborate?Leix
@Zoidberg: undefined is a property on the global object -- it's not a keyword, so "undefined" in this returns true in the global scope. It also means you can do undefined = 42 and if (myVar == undefined) becomes (essentially) myVar == 42. This harks back to the early days of ecmascript nee javascript where undefined didn't exist by default, so people just did var undefined in the global scope. Consequently undefined couldn't be made a keyword without breaking existing sites, and so we were doomed for all time to have undefined be a normal property.Vtol
@olliej: Cool, I had no idea, that actually makes complete sense. Does the same go for null?Leix
@olliej: I have no idea why you think undefined is a property on the global object. By default the lookup of undefined is the built-in value of undefined. If you override it with "undefined=42" then when you access undefined as a variable lookup, you get the overridden value. But try doing "zz=undefined; undefined=42; x={}; 'undefined old='+(x.a === zz)+', undefined new='+(x.a === undefined)". You can never redefine the internal values of null, undefined, NaN, or Infinity, even if you can override their symbol lookups.Martins
@Jason undefined is a global property because it is specified as such. Consult 15.1.1.3 of ECMAScript-262 3rd ed.Peta
@Vtol But when I do this.undefined = 42; console.log(this.undefined); in Firefox console I get undefined in log. That is, you are only half right, I can't change values of undefined, NaN and Infinity (checked all of them).Perforation
K
19

The strings "Infinity", "-Infinity", and "NaN" all coerce to the expected values in JS. So I'd argue the right way to represent these values in JSON is as strings.

> +"Infinity"
Infinity

> +"-Infinity"
-Infinity

> +"NaN"
NaN

It's just a shame JSON.stringify doesn't do this by default. But there is a way:

> JSON.stringify({ x: Infinity }, function (k,v) { return v === Infinity ? "Infinity" : v; })
"{"x":"Infinity"}"
Kinship answered 27/2, 2015 at 10:57 Comment(6)
0/0, etc, are not valid JSON. You have to work within the confines of the standard, and strings do the job nicely.Kinship
On the contrary, I think this is the only practical solution, but I'll do a function that return NaN if the input value is "NaN", etc. The way you do the conversion is prone to code injection.Nonchalance
JSON values can't be arithmetic expressions... the goal of making the standard separate from the language literal syntax is to make JSON deeserializable without executing any of it as code. Not sure why we couldn't have NaN and Infinity added as keyword values like true and false, though.Krefetz
To make it more explicit, we can use Number("Infinity"), Number("-Infinity") and Number("NaN")Adductor
This is work like magic.JSON.parse("{ \"value\" : -1e99999 }") easily return { value:-Infinity } in javascript. Only it just not compatible with custom number type that could be larger than thatKarimakarin
I am blown away that "N", "NaN", "NaNxxx" all parse to NaN and that "[+|-]Infinity" parse as [+-]Infinity and I didn't know it after writing JavaScript for over 25 years...Aton
I
8

If you have access to the serialization code you might represent Infinity as 1.0e+1024. The exponent is too large to represent in a double and when deserialized this is represented as Infinity. Works on webkit, unsure about other json parsers!

Introgression answered 20/2, 2012 at 5:21 Comment(7)
IEEE754 supports 128 bit floating point numbers so 1.0e5000 is betterVander
Ton: 128 bit was added later. What if they decide to add 256 bit? Then you’ll have to add more zeros, and existing code will behave differently. Infinity will always be Infinity, so why not support that?Bornie
Clever idea! I was just about to either switch to a different format or add cumbersome workaround code to my parser. Not ideal for every every case, but in my case, where infinity serves as just an optimized edge case to a converging sequence, it's just perfect and even if larger precision would be introduce it would still be mostly correct. Thanks!Haemophilia
1, -1, and 0..... perfectly valid/parsable numbers, become those three special values when you simply add /0 to the end of them. It's easily parsable, immediately visible, and even evaluable. It's inexcusable that they haven't yet added it to the standard: {"Not A Number":0/0,"Infinity":1/0,"Negative Infinity":-1/0} <<Why not? alert(eval("\"Not A Number\"") //works alert(eval("1/0")) //also works, prints 'Infinity'. No excuse.Shiite
dbj.org/json-how-to-create-an-undefined-value-or-an-nan-valueCassey
If you're assuming compatibility on a system where you know the number can be at most an IEEE double, then it's enough to use 9E999, since 1.8e308 is already too large to fit. This works at least on Firefox JSON.parse which parses this as a JS Infinity, and -9E999 as -Infinity. You could even privately use a convention of having uppercase E in the exponent specially to further differentiate it from other exponential notation numbers where you could use lowercase e. For NaN, however, there is no reliable workaround unless you implement both serializer and parser yourself.Eirena
One could think 0E999 could make a NaN, using this same logic (0*1E999 in JS is NaN), but unfortunately (or fortunately) it doesn't work, at least in JSON.parse.Eirena
N
7

The reason is stated on page ii in Standard ECMA-404 The JSON Data Interchange Syntax, 1st Edition

JSON is agnostic about numbers. In any programming language, there can be a variety of number types of various capacities and complements, fixed or floating, binary or decimal. That can make interchange between different programming languages difficult. JSON instead offers only the representation of numbers that humans use: a sequence of digits. All programming languages know how to make sense of digit sequences even if they disagree on internal representations. That is enough to allow interchange.

The reason is not, as many have claimed, due to the representations of NaN and Infinity ECMA script. Simplicity is a core design principle of JSON.

Because it is so simple, it is not expected that the JSON grammar will ever change. This gives JSON, as a foundational notation, tremendous stability

Nature answered 10/10, 2019 at 12:29 Comment(13)
...only the representation of numbers that humans use... So, mathematicians, scientists, engineers and programmers that use the infinity symbol are not humans? (Snark aimed at the standard, not at CervEd.)Eupheemia
That explanation really makes no sense. In practice, real world programs in most programming languages will be using floating point values that support NaN and Infinity. Humans already have ways to write numbers down and send them to each other with JSON. JSON is for computers. Based on their logic, JSON should only support strings.Gnomic
@EmileCormier, the thing is that they use the term (and the symbol) with different meanings. In mathematics, +∞ is not the same thing as ; and besides that there are aleph numbers and various other interpretations. IEEE-754's interpretation of infinities is just a one of them. And JSON (fortunately or not) decided not to stick to a specific interpretation. (BTW, support for IEEE-754's infinity and minus infinity must be accompanied with support for IEEE-754's minus zero.)Kirsch
@Kirsch I have no stats or anything to back this up, but my hunch is that the majority of use cases would want to use the same meanings as IEEE-754.Eupheemia
@EmileCormier, the same for me. I may be wrong and I can't back it by any statistics, but I feel that making JSON to support only rational numbers (but of arbitrary internal representation and range) was a very proper design choice (at least, I feel it to be mathematically elegant). Most of the user code is simply not ready to cope with inf/nan values (to validate and use them correctly). And those that is ready can simply apply additional processing to "inf"/"-inf"/"nan" strings. (I would think otherwise if JSON didn't support strings; with strings, one can implement any special value.)Kirsch
@Kirsch If you simply encode the float infinity as "inf", how do you, when reading the file, distinguish it from the string "inf" encoded as the same?Lysin
@KellyBundy what sensible reason do you have to encode "infinity"? It's a concept. There's no sensible encoding besides, we treat this arbitrary string as "something larger than our number system can hold".Nature
@Nature I don't see what that has to do with what I said.Lysin
@KellyBundy in what context do you need to encode/decode +/-∞? And in such context, why can't you just use chose a string representation +∞, -∞ or +Infinity/-Infinity. I don't see a problem hereNature
@Nature I guess you need to do that in contexts where you need to do that. But I wasn't talking about that. All I said was that they're in trouble if they encode two different things the same, as they then don't know which one to decode to. Doesn't matter what string you choose, it collides with that string.Lysin
@KellyBundy If one insists on serializing Infinity (!?) one presumably writes the application to expect either a numeric or string value for a certain field. If it's numeric and the value is represented as a string, one presumably tries to parse that in a suitable manner.Nature
There is actually a standard on how to serialize/de-serialize floating point values. See ISO 14882 : 2017 (std::to_chars, std::from_chars). I think json should adopt the exact same convention.Pliner
@Pliner there are lots of standard. That looks like a C++ standard from 2017. Hard to say anything about it without reading it. Maybe you can share a link?Nature
T
4

Potential work-around for cases like {"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;
   });

The general idea is to replace occurences of invalid values with a string we will recognize when parsing and replace it back with the appropriate JavaScript representation.

Thuggee answered 3/8, 2018 at 18:42 Comment(3)
I don't know why this solution got a downvote because frankly, if you face a situation where your JSON string contains Infinity or IsNaN values it will fail when you try to parse it. Using this technique, you first replace occurrences of IsNaN or Infinity with something else (to isolate them from any valid string which might contain those terms), and use the JSON.parse(string,callback) to return the proper, valid JavaScript values. I'm using this in production code and never had any issue.Thuggee
Wouldn't this mess up Infinity inside strings? For many usecases it's probably safe to assume it's not an issue, but the solution is not fully robust.Napoleon
@Napoleon You could wrap your data in an object that stores a string representation of the orig data type, then use this code on elements where the orig data type was not a string. True, nested objects of mixed data types take extra care!. But if you know something about the data coming in, you could use that to your advantage. In a best case scenario, if all the data are simple numbers, then this would be easy. More complex data needs more complex solution. Depends on how important it is to restore the proper data. Anyway, this code is a great start toward a robust version for complex data.Jolandajolanta
M
4

JSON5 allows standard Javascript notation for positive and negative infinity, NaN, and numerous other things that are valid ECMAScript that were left out of JSON (trailing commas, etc.).

https://json5.org/

This makes JSON a much more useful format.

However, whether using JSON or JSON5: for security reasons, always always parse -- don't evaluate!!

Mona answered 1/7, 2021 at 11:32 Comment(1)
Note that "JSON5" is an independent project and is not a later version of any official standard.Grapefruit
T
1

The current IEEE Std 754-2008 includes definitions for two different 64-bit floating-point representations: a decimal 64-bit floating-point type and a binary 64-bit floating-point type.

After rounding the string .99999990000000006 is the same as .9999999 in the IEEE binary 64-bit representation but it is NOT the same as .9999999 in the IEEE decimal 64-bit representation. In 64-bit IEEE decimal floating-point .99999990000000006 rounds to the value .9999999000000001 which is not the same as the decimal .9999999 value.

Since JSON just treats numeric values as numeric strings of decimal digits there is no way for a system that supports both IEEE binary and decimal floating-point representations (such as IBM Power) to determine which of the two possible IEEE numeric floating-point values is intended.

Theran answered 27/10, 2015 at 21:30 Comment(1)
What does this have to do with the question? (which is about Infinity and NaN)Prefatory
K
-5

If like me you have no control over the serialisation code, you can deal with NaN values by replacing them with null or any other value as a bit of a hack as follows:

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

In essence, .fail will get called when the original json parser detects an invalid token. Then a string replace is used to replace the invalid tokens. In my case it is an exception for the serialiser to return NaN values so this method is the best approach. If results normally contain invalid token you would be better off not to use $.get but instead to manually retrieve the JSON result and always run the string replacement.

Kidd answered 4/4, 2013 at 4:24 Comment(2)
Clever, but not entirely foolproof. Try it with { "tune": "NaNaNaNaNaNaNaNa BATMAN", "score": NaN }Smallwood
and you must be using jQuery. I don't have $.get().Martins

© 2022 - 2024 — McMap. All rights reserved.