74

I am having problems understanding the behavior of JSON.parse. JSON.parse should work for only strings. But it seems to work for an array which contains only one string (even single quoted), if the string contains only numbers.

JSON.parse(['1234']) // => 1234
JSON.parse(['1234as']) // => throws error
JSON.parse(['123', '123']) // => throws error
Salman A
  • 262,204
  • 82
  • 430
  • 521
Akshendra Pratap
  • 2,002
  • 1
  • 12
  • 25
  • 2
    I know that none of them are not valid JSON. But then JSON.parse should throw error, as I expected in case one, but it's not. – Akshendra Pratap May 01 '17 at 11:04
  • 57
    ***PLEASE*** don't refer to w3schools to understand JSON. I just reviewed it and it is so so wrong in so many ways. – Dancrumb May 01 '17 at 16:31
  • 2
    Also, I think what Solomonoff meant was "This is JavaScript. You should expect it to conform to the ECMAScript specification which explains exactly what happens when you pass a non-String into a function that expects a String" – Dancrumb May 01 '17 at 16:32
  • 1
    FYI: "Even single quoted" has no relevance here. Single quotes and double quotes around a **JavaScript** string have no bearing on its validity as JSON. – user229044 May 02 '17 at 17:54
  • @meagar - That is actually incorrect. `JSON.parse(['\'1234ab\'']);` (note only single quotes are used) will cause a syntax error, whereas `JSON.parse(['\"1234ab\"']);` will **not**. The parser explicitly states that they must be double quotes for JSON to properly parse. – Travis J May 14 '17 at 06:54
  • 1
    @TravisJ You're confused about what part is JavaScript and what part is JSON. The quotes around the **JavaScript** string do not matter in your example, and you chose single quotes for both examples, completly missing the point. The quotes *inside* the JavaScript string, which are part of the encoded JSON, absolutely matter and I never indicated they didn't. The quotes used to create a string literal **in JavaScript** have no impact on whether the characters in that string are valid JSON; there is no difference between parsing `'"foo"'` and `"\"foo\""`, they are literally identical strings. – user229044 May 14 '17 at 11:56
  • @meagar - You are terribly mistaken, and are perpetuating your misunderstanding with your response. The quotes *inside* **are the entire point**. I had hoped you would of course understand there was no need to examine the outside quotes. Clearly you **missed that point**. and in doing so **demonstrate the lack of understanding** that your previous comment perpetuates. The use of quotes inside of the JSON string are the topic here, not the simple surrounding quotes of the expression. – Travis J May 15 '17 at 03:58
  • @TravisJ The "quotes inside" are something *you* introduced to this question. There are no "quotes inside" in the original question. The use of quotes inside of the JSON string are the topic here *only for you*. Nobody else is talking about them, including OP. Neither the question or any other answer, including the accepted one, has *anything* to do with quotes inside the string, and my original comment that you replied to certainly didn't. The mistake here is yours, given that everybody else appears to be on the same page and you have the lone downvoted non sequitur answer. – user229044 May 15 '17 at 10:55
  • @TravisJ And finally: Please reread my original comment, the one you called incorrect. It has nothing to do with JSON quotes, inside a JavaScript string. It clearly says that single and double quotes **"around a JavaScript string"** have no bearing on its validity as JSON. **Nobody** but you is talking about quotes *inside* the JavaScript string literal. – user229044 May 15 '17 at 10:58
  • @meagar - Correct, no one properly addressed formatting inside of the JSON being parsed. – Travis J May 17 '17 at 17:51

3 Answers3

178

As you have pointed out, JSON.parse() expects a string and not an array. However, when given an array or any other non-string value, the method will automatically coerce it to a string and proceed instead of throwing immediately. From the spec:

  1. Let JText be ToString(text).
  2. ...

The string representation of an array consists of its values, delimited by commas. So

  • String(['1234']) returns '1234',
  • String(['1234as']) returns '1234as', and
  • String(['123', '123']) returns '123,123'.

Notice that string values are not quoted again. This means that ['1234'] and [1234] both convert to the same string, '1234'.

So what you're really doing is:

JSON.parse('1234')
JSON.parse('1234as')
JSON.parse('123,123')

1234as and 123,123 are not valid JSON, and so JSON.parse() throws in both cases. (The former isn't legal JavaScript syntax to begin with, and the latter contains a comma operator that doesn't belong.)

1234 on the other hand is a Number literal and therefore valid JSON, representing itself. And that's why JSON.parse('1234') (and by extension JSON.parse(['1234'])) returns the numeric value 1234.

BoltClock
  • 700,868
  • 160
  • 1,392
  • 1,356
  • 12
    _"..is a Number literal and therefore valid JSON.."_ - A single number literal is not valid JSON, but `JSON.parse` is not very strict _(specifically, it parses JSON objects and single values)_. See [here](http://stackoverflow.com/a/18419503/238419) for more info. – BlueRaja - Danny Pflughoeft May 01 '17 at 16:28
  • 18
    @BlueRaja-DannyPflughoeft I followed your link. It disagrees with your conclusion. If by "valid JSON" you mean "a valid JSON text", then a single number literal used to be invalid, but is now valid. Additionally, "valid JSON" has other valid interpretations than "a valid JSON text", and by some of the other interpretations, a single number literal has always been valid. –  May 01 '17 at 17:59
  • 11
    One can take this to the next level with `JSON.parse(['[123', '123]'])` :P – Siguza May 01 '17 at 18:08
  • @hvd A number literal is not a json text with only a number. Where `var a = "42";`, `a` contains a valid json text, but where `var a = 42;`, it does not. – Izkata May 01 '17 at 19:45
  • @Izkata We were talking about a string consisting of a single number literal. –  May 01 '17 at 19:47
  • 5
    @BlueRaja-DannyPflughoeft RFC 7159 and ECMA-404 have revised the JSON specification to allow the top-level value to be a number or string, not just an object or array. `JSON.parse()` is consistent with this change. – Barmar May 01 '17 at 20:39
  • Good explanation of how arrays convert to strings. Pardon me if this question is naive, but is there a reason `JSON.parse` does this automatic conversion for an array input? (Not to imply that it shouldn't, just wondering what the motivation was.) – Luke Sawczak May 01 '17 at 21:40
  • 6
    @BlueRaja-DannyPflughoeft: There are multiple versions of JSON, unfortunately. There's the original version published by Doug Crockford on http://JSON.org/ . But there are also two international standards for JSON, namely [ECMA-404](http://ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf) (which supersedes the specification in [section 15.12.1 of ECMA-262 5.1 Edition](http://www.ecma-international.org/ecma-262/5.1/index.html#sec-15.12.1)) and [RFC 7159](http://rfc-editor.org/rfc/rfc7159.txt) (which supersedes [RFC 4627](http://rfc-editor.org/rfc/rfc4627.txt)). There's also the … – Jörg W Mittag May 02 '17 at 00:24
  • 1
    … new [I-JSON specification in RFC 7493](https://rfc-editor.org/rfc/rfc7493.txt). Tim Bray (editor of RFC 7159 and RFC 7493) has a [humorous write-up about the events that led to this explosion of specifications](https://tbray.org/ongoing/When/201x/2014/03/05/RFC7159-JSON). – Jörg W Mittag May 02 '17 at 00:26
  • "1234as is a syntax error in JavaScript" - can you expand upon this? As a JavaScript amature my interpretation of this statement is that 1234as is some magic string that breaks javascript. – ESR May 02 '17 at 05:10
  • @Luke Sawczak: JSON.parse() doesn't choose to perform this conversion - it happens automatically when you try to perform string comparison/manipulation on something that isn't a string, but can be converted to one. You could say that JSON.parse() doesn't care about the type of its argument. – BoltClock May 02 '17 at 05:22
  • @Edmund Reed: Y'know, I have no idea how to explain this one. All I can say is that 1234as cannot be parsed as a Number, an identifier (variable name, function name, etc), or any other meaningful construct in JavaScript, and therefore it's a syntax error. It's not a magic string, it just cannot be interpreted meaningfully. – BoltClock May 02 '17 at 06:14
  • @BoltClock Ah, okay. (And why *that* happens is, I suppose, its own topic!) Clearly I have to familiarize myself with JSON, in any case. – Luke Sawczak May 02 '17 at 12:55
  • @LukeSawczak *Technically* the array-to-string conversion does happen internal to `JSON.parse` (rather than via some syntax-level magic), because the first step in the specification of [`JSON.parse`](http://www.ecma-international.org/ecma-262/6.0/#sec-json.parse) is "1. Let *JText* be ToString(*text*)." (where *text* is the first argument and [ToString](http://www.ecma-international.org/ecma-262/6.0/#sec-tostring) is the language-specified abstract operation to coerce a value to a string). It could have been specified to reject non-string input with an error, but it is not specified that way. – apsillers May 02 '17 at 17:32
  • @apsillers: Hmm, does that mean implementations of JSON.parse() compliant with the spec perform this coercion explicitly? – BoltClock May 02 '17 at 17:34
  • @BoltClock Yes, `JSON.parse` is specified to coerce its input to a string (via the rules of ToString) before beginning to parse. – apsillers May 02 '17 at 17:36
  • @EdmundReed `JSON.parse` uses the JS language parser to parse its input -- that is, the input is actually read as a JS program, with specific restrictions. The [ES6 spec specifies](http://www.ecma-international.org/ecma-262/6.0/#sec-json.parse) one of the transformational steps of `JSON.parse` as, "Let *completion* be the result of parsing and evaluating *scriptText* as if it was the source text of an ECMAScript *Script*..." followed by an assertion that this resultant value "...will be either a primitive value or an object that is defined by either an ArrayLiteral or an ObjectLiteral." – apsillers May 02 '17 at 17:42
  • @apsillers: Thanks for the correction, I've edited my answer. – BoltClock May 02 '17 at 17:43
  • 1
    @BoltClock I think it's because `1234as` is not a valid **JSON** *literal*. It's not a string (The string would have to contain `"1234as"` to be parsable as a JSON string.), number (because of the trailing `as`), Boolean, or `null`. It obviously isn't a JSON object or array, either, of course. – jpmc26 May 02 '17 at 20:10
  • Right, `JSON.parse()` doesn't care if what it's parsing is valid JavaScript or not. It throws an error if it's not valid JSON and `1234as` is not. – JJJ May 02 '17 at 20:25
  • @jpmc26, JJJ: Is there anything at all that is simultaneously valid JSON and not a valid JavaScript expression? If not, what does it matter really? – BoltClock May 02 '17 at 20:53
  • 1
    [Only in some edge cases](http://stackoverflow.com/questions/23752156/are-all-json-objects-also-valid-javascript-objects), but it's just a bit misleading to say that A throws because it's not valid JS and B throws because it's not valid JSON, when in fact they both throw for the exact same reason. It's factually correct but might give the false impression that the parser makes that distinction. – JJJ May 02 '17 at 21:15
  • @BoltClock I would argue the distinction actually matters more in the *other* direction. There's a lot of valid JavaScript that isn't valid JSON (I believe this used to be a fairly common source of confusion among some questions.), so it's worth being careful to avoid confusing someone who is unfamiliar with the technology. But there's also some weird cases where JS *will* convert `"1234as"` to an integer; e.g., `parseInt("1234as")` yields `1234`. (Yes, we asked it to parse an int; it's still super weird to just ignore the trailing chars instead of erroring like the literal would.) – jpmc26 May 02 '17 at 21:37
  • 1
    @jpmc26, JJJ: Ah I see where you're both coming from. I've rewritten that bit. – BoltClock May 03 '17 at 05:18
22

If JSON.parse doesnt get a string, it will first convert the input to string.

["1234"].toString() // "1234"
["1234as"].toString() // "1324as"
["123","123"].toString() // "123,123"

From all those outputs it only knows how to parse "1234".

melpomene
  • 84,125
  • 8
  • 85
  • 148
naortor
  • 2,019
  • 12
  • 26
4

Two things to note here:

1) JSON.parse converts the argument to a string (refer to the first step of algorithm in the spec). Your input results in the following:

['1234']       // String 1234
['1234as']     // String 1234as
['123', '123'] // String 123,123

2) The specs at json.org state that:

[...] A value can be a string in double quotes, or a number, or true or false or null, or an object or an array. These structures can be nested.

So we have:

JSON.parse(['1234'])
// Becomes JSON.parse("1234")
// 1234 could be parsed as a number
// Result is Number 1234 

JSON.parse(['1234as'])
// Becomes JSON.parse("1234as")
// 1234as cannot be parsed as a number/true/false/null
// 1234as cannot be parsed as a string/object/array either
// Throws error (complains about the "a")

JSON.parse(['123', '123'])
// Becomes JSON.parse("123,123")
// 123 could be parsed as a number but then the comma is unexpected
// Throws error (complains about the ",")
Salman A
  • 262,204
  • 82
  • 430
  • 521