-5

This question is multipart-

(1a) JSON is fundamental to JavaScript, so why is there no JSON type? A JSON type would be a string that is formatted as JSON. It would be marked as parsed/stringified until the data was altered. As soon as the data was altered it would not be marked as JSON and would need to be re-parsed/re-stringified.

(1b) In some software systems, isn't it possible to (accidentally) attempt to send a plain JS object over the network instead of a serialized JS object? Why not make an attempt to avoid that?

(1c) Why can't we call JSON.parse on a straight up JavaScript object without stringifying it first?

    var json = {   //JS object in properJSON format
        "baz":{
            "1":1,
            "2":true,
            "3":{}
        }
    };

    var json0 = JSON.parse(json); //will throw a parse error...bad...it should not throw an error if json var is actually proper JSON.

So we have no choice but to do this:

 var json0= JSON.parse(JSON.stringify(json));

However, there are some inconsistencies, for example:

JSON.parse(true); //works
JSON.parse(null); //works
JSON.parse({}); //throws error

(2) If we keep calling JSON.parse on the same object, eventually it will throw an error. For example:

var json = {   //same object as above
    "baz":{
        "1":1,
        "2":true,
        "3":{}
    }
};

var json1 = JSON.parse(JSON.stringify(json));
var json2 = JSON.parse(json1); //throws an error...why

(3) Why does JSON.stringify infinitely add more and more slashes to the input? It is not only hard to read the result for debugging, but it actually puts you in dangerous state because one JSON.parse call won't give you back a plain JS object, you have to call JSON.parse several times to get back the plain JS object. This is bad and means it is quite dangerous to call JSON.stringify more than once on a given JS object.

   var json = {
        "baz":{
            "1":1,
            "2":true,
            "3":{}
        }
    };

    var json2 = JSON.stringify(json);
    console.log(json2);

    var json3 = JSON.stringify(json2);
    console.log(json3);

    var json4 = JSON.stringify(json3);
    console.log(json4);

    var json5 = JSON.stringify(json4);
    console.log(json5); 

(4) What is the name for a function that we should be able to call over and over without changing the result (IMO how JSON.parse and JSON.stringify should behave)? The best term for this seems to be "idempotent" as you can see in the comments.

(5) Considering JSON is a serialization format that can be used for networked objects, it seems totally insane that you can't call JSON.parse or JSON.stringify twice or even once in some cases without incurring some problems. Why is this the case?

If you are someone who is inventing the next serialization format for Java, JavaScript or whatever language, please consider this problem.

IMO there should be two states for a given object. A serialized state and a deserialized state. In software languages with stronger type systems, this isn't usually a problem. But with JSON in JavaScript, if call JSON.parse twice on the same object, we run into fatal exceptions. Likewise, if we call JSON.stringify twice on the same object, we can get into an unrecoverable state. Like I said there should be two states and two states only, plain JS object and serialized JS object.

Alexander Mills
  • 90,741
  • 139
  • 482
  • 817
  • `parse` returns a valid JavaScript object from a valid JSON string, `stringify` returns a valid JSON string from a JavaScript object – thefourtheye Jun 03 '15 at 19:58
  • 3
    Your very first scenario, you dont have JSON, you have an *object*. – tymeJV Jun 03 '15 at 19:58
  • 1) Because JSON.parse expects a JSON string, and you are providing it a Javascript object, 2) Same issue. JSON.parse converts it to an object, and you are feeding another javascript object. – Hozikimaru Jun 03 '15 at 19:58
  • Think of `split` and `join` in javascript. `split` turns a string into an array, whereas `join` turns an array into a string. You can't `join` the output from a `join` because it's not the right type. – Raphael Serota Jun 03 '15 at 20:00
  • 1
    (4) The word you're looking for is idempotent. http://en.wikipedia.org/wiki/Idempotence – Raphael Serota Jun 03 '15 at 20:02
  • @RaphaelSerota yes, that is more or less correct. JSON.parse and JSON.stringify should both be idempotent and they are not. – Alexander Mills Jun 03 '15 at 20:03
  • "So we are forced to do" - Not true, and wouldnt work in most of scenarios. Best is to use try and catch – Tomas Jun 03 '15 at 20:05
  • An object is NOT valid JSON. `JSON` is `Javascript Object Notation`, a style of writing an object for parsing. Your `json` variable does not contain JSON, it contains an object. The style of JS that you have written when assigning, e.g. everything after `var json =` is JSON. Except that JSON was already parsed by your browser when you loaded the JS file. This can be seen by the fact that if you have a syntax error in it, you get that message before any of your code runs. – loganfsmyth Jun 03 '15 at 20:32
  • A string can in-fact be valid JSON. JSON has a very [concise and well-defined spec](http://json.org/). `JSON.parse` operates on JSON strings and nothing else. And that is a perfectly acceptable design. What is your problem, exactly? – Mulan Jun 04 '15 at 06:24
  • "Why can't JSON.parse parse a JS object as well as a string, that is the question" (@AlexMills). Uh, because when you have a JS object, there's nothing to parse... it's already an object. – Mulan Jun 04 '15 at 06:26
  • if you call JSON.parse(jsObj) it throws an Error, and it should NOT – Alexander Mills Jun 04 '15 at 06:27
  • Anyway, it should throw and error because you gave it an object and not a string. `JSON.parse` expects a string. Have you ever tried using a hammer on a screw? It doesn't work. – Mulan Jun 04 '15 at 06:30
  • This seems sort of like asking whether a C compiler should accept a compiled executable as input. – Joshua Taylor Jun 05 '15 at 19:52

3 Answers3

3

1) JSON.parse expects a string, you are feeding it a Javascript object.

2) Similar issue to the first one. You feed a string to a function that needs an object.

3) Stringfy actually expects a string, but you are feeding it a String object. Therefore, it applies the same measures to escape the quotes and slashes as it would for the first string. So that the language can understand the quotes, other special characters inside the string.

4) You can write your own function for this.

5) Because you are trying to do a conversion that is illegal. This is related to the first and second question. As long as the correct object types are fed, you can call it as many times as you want. The only problem is the extra slashes but it is in fact the standard.

Hozikimaru
  • 1,144
  • 1
  • 9
  • 20
  • In my opinion, there is absolutely no good reason why #1 needs to be the case. JSON.parse should be able to parse a JS object that is formatted properly as JSON, no if ands or buts. it is total BS. – Alexander Mills Jun 03 '15 at 20:09
  • 1
    @AlexMills whilst I agree your point, it is highly structured, and have fault shield approach that is implemented. Indeed something more global could have been done, but that would be more fault tolerant thus less error proof. – Hozikimaru Jun 03 '15 at 20:11
  • @AlexMills, JSON.parse is a function the expects a string since JSON is a string representation of a javascript object. The name "JSON.parse" tells me that this function will parse JSON, and nothing else. If I invoke JSON.parse with something else then JSON as an argument I would expect an exception or similar, not just a silent "ok" - such a behaviour would probably lead to strange bugs – Onkelborg Jun 03 '15 at 20:15
  • I wrote some code to solve this problem. This type of thing should not be necessary, but I don't know of a better way. – Alexander Mills Jun 03 '15 at 22:57
  • 2
    @AlexMills you seem misguided with your assertions. There's no reason `JSON.parse` should take anything other than a valid JSON string. The JS object literal you have in #1 is not a JSON string, therefore it will not work. – Mulan Jun 04 '15 at 06:22
  • @naomik, you are absolutely 100%, totally, completely, right it will not work, but there is no good reason why it shouldn't. I want to know if there is a good reason why it shouldn't work. – Alexander Mills Jun 04 '15 at 06:25
  • @AlexMills there is a perfect reason why it shouldn't. Your car tells you to put gas in it, and JSON.parse tells you to put a string in. Follow the instructions and each will work as designed. If you give `JSON.parse` an object, there's nothing for it to do... – Mulan Jun 04 '15 at 06:27
  • @naomik, yeah nothing for it to do it except throw a fatal Error – Alexander Mills Jun 04 '15 at 06:29
  • @naomik, try this code: "var obj = {}; JSON.parse(obj);" – Alexander Mills Jun 04 '15 at 06:30
  • @AlexMills lol no. That's an invalid call to JSON.parse and it behaves exactly as i'd like it to. To me, it seems your problem is you're being lazy with your types and you're hoping that underlying functions will just hold your hand and change your diapers for you. You should know the types of your data in your program; explicit conversions are much better than implicit ones. – Mulan Jun 04 '15 at 06:32
  • @naomik try doing this in deeply networked systems and in a team environment where you don't necessarily know what you are getting because this isn't Java. Come back to me in 6 months. – Alexander Mills Jun 04 '15 at 06:34
  • and @naomik, this was only addressing one of the questions, feel free to address the others – Alexander Mills Jun 04 '15 at 06:35
  • @AlexMills I've contacted your personally via email, if you'd like to actually chat about the subject. – Mulan Jun 04 '15 at 06:38
2

We'll start with this nightmare of your creation: string input and integer output.
IJSON.parse(IJSON.stringify("5")); //=> 5

The built-in JSON functions would not fail us this way: string input and string output.
JSON.parse(JSON.stringify("5")); //=> "5"

JSON must preserve your original data types

Think of JSON.stringify as a function that wraps your data up in a box, and JSON.parse as the function that takes it out of a box.

Consider the following:

var a = JSON.stringify;
var b = JSON.parse;

var data = "whatever";

b(a(data)) === data; // true

b(b(a(a(data)))) === data; // true

b(b(b(a(a(a(data)))))) === data; // true

That is, if we put the data in 3 boxes, we have to take it out of 3 boxes. Right?

If I put my data in 2 boxes and take it out of 1, I'm not holding my data yet, I'm holding a box that contains my data. Right?

b(a(a(data))) === data; // false

Seems sane to me...


  1. JSON.parse unboxes your data. If it is not boxed, it cannot unbox it. JSON.parse expects a string input and you're giving it a JavaScript object literal

  2. The first valid call to JSON.parse would return an object. Calling JSON.parse again on this object output would result in the same failure as #1

  3. repeated calls to JSON.stringify will "box" our data multiple times. So of course you have to use repeated calls to JSON.parse then to get your data out of each "box"

  4. Idempotence

  5. No, this is perfectly sane. You can't triple-stamp a double-stamp.

    You'd never make a mistake like this, would you?

    var json = IJSON.stringify("hi");
    IJSON.parse(json);
    //=> "hi"
    

    OK, that's idempotent, but what about

    var json = IJSON.stringify("5");
    IJSON.parse(json);
    //=> 5
    

    UH OH! We gave it a string each time, but the second example returns an integer. The input data type has been lost!

    Would the JSON functions have failed us here?

    var json = JSON.stringify("hi");
    JSON.parse(json);
    //=> "hi"
    

    All good. And what about the "5" ?

    var json = JSON.stringify("5");
    JSON.parse(json));
    //=> "5"
    

    Yay, the types have been preseved! JSON works, IJSON does not.


Maybe a more real-life example:

OK, so you have a busy app with a lot of developers working on it. It makes reckless assumptions about the types of your underlying data. Let's say it's a chat app that makes several transformations on messages as they move from point to point.

Along the way you'll have:

  1. IJSON.stringify
  2. data moves across a network
  3. IJSON.parse
  4. Another IJSON.parse because who cares? It's idempotent, right?
  5. String.prototype.toUpperCase — because this is a formatting choice

Let's see the messages

bob: 'hi'
// 1) '"hi"', 2) <network>, 3) "hi", 4) "hi", 5) "HI"

Bob's message looks fine. Let's see Alice's.

alice: '5'
// 1) '5'
// 2) <network>
// 3) 5
// 4) 5
// 5) Uncaught TypeError: message.toUpperCase is not a function

Oh no! The server just crashed. You'll notice it's not even the repeated calling of IJSON.parse that failed here. It would've failed even if you called it once.

Seems like you were doomed from the start... Damned reckless devs and their careless data handling!

It would fail if Alice used any input that happened to also be valid JSON

alice: '{"lol":"pwnd"}'
// 1) '{"lol":"pwnd"}'
// 2) <network>
// 3) {lol:"pwnd"}
// 4) {lol:"pwnd"}
// 5) Uncaught TypeError: message.toUpperCase is not a function

OK, unfair example maybe, right? You're thinking, "I'm not that reckless, I wouldn't call IJSON.stringify or IJSON.parse on user input like that!" It doesn't matter. You've fundamentally broken JSON because the original types can no longer be extracted.

If I box up a string using IJSON, and then unbox it, who knows what I will get back? Certainly not you, and certainly not the developer using your reckless function.

  • "Will I get a string type back?"
  • "Will I get an integer?"
  • "Maybe I'll get an object?"
  • "Maybe I will get cake. I hope it's cake"

It's impossible to tell!

You're in a whole new world of pain because you've been careless with your data types from the start. Your types are important so start handling them with care.

JSON.stringify expects an object type and JSON.parse expects a string type.

Now do you see the light?

Mulan
  • 129,518
  • 31
  • 228
  • 259
2

I'll try to give you one reason why JSON.parse cannot be called multiple time on the same data without us having a problem.

you might not know it but a JSON document does not have to be an object.

this is a valid JSON document:

"some text"

lets store the representation of this document inside a javascript variable:

var JSONDocumentAsString = '"some text"';

and work on it:

var JSONdocument = JSON.parse(JSONDocumentAsString);
JSONdocument === 'some text';

this will cause an error because this string is not the representation of a JSON document

JSON.parse(JSONdocument);
// SyntaxError: JSON.parse: unexpected character at line 1 column 1 of the JSON data

in this case how could have JSON.parse guessed that JSONdocument (being a string) was a JSON document and that it should have returned it untouched ?

Mathieu
  • 5,495
  • 2
  • 31
  • 48
  • exactly, that's why we need something better than the current standards of JSON. something with types, where we know what we are getting is already serialized, or already deserialized. You are using JSON in your example, but imagine if there were something better. With the case of serialization, you will have to mark with some characters. So "some text" shouldn't be a valid JSON document, it should be '{json:"some text"}' or whatever. – Alexander Mills Jun 05 '15 at 23:05
  • Using Strings as valid JSON is bad idea. If you call JSON.stringify on a string, then you really don't know if the original argument represents valid data or not. For example, you could send JSON.stringify('garbage\n\n}{"haveFunParsingthis;;;;;');. IMO, JSON.stringify should not accept Strings, because Strings themselves should not valid JSON, AFAICT. – Alexander Mills Jun 05 '15 at 23:22
  • 1
    you might think that using strings as valid JSON is a bad idea and that strings should not be valid JSON but they are and that's very unlikely to ever change. JSON.parse & JSON.stringify are working with JSON, not what you think JSON should be. JSON probably won't change, but you are very welcome to imagine a better data format – Mathieu Jun 06 '15 at 20:51