What you didn’t know about JSON.parse


Now that you know some more about JSON.stringify; let’s dive into its complement: JSON.parse.

1. JSON.Parse

The JSON parse function takes in a string (invalid JSON will cause a SyntaxError exception). If parsing succeeds, JSON.parse returns the corresponding value or object.

let value = JSON.parse("1");
console.log(value);
// 1

2. The reviver function

JSON.parse accepts an optional reviver function. This reviver function, if specified, allows you to carry out custom actions while parsing the retrieved JSON.

For example, say you want to process JSON values coming in from the server. You could parse the string and then modify it or write an interceptor that transforms the string during JSON.parse. In some scenarios, having this in JSON.parse is a neater approach.

A trivial example might be having a custom logger that invokes console.log during JSON.parse operations (you want to know what is coming over the wire when in debug mode) .

let logger = (key, value)
=> console.log(`${key} : ${value}`);
JSON.parse('{"a":1, "b":2}', logger);
//
// a : 1
// b : 2
//   : [object Object]

Usage

The reviver function accepts key and value parameters corresponding to each key-value-pair in the object.

Notes

1. The returned value for the input key will replace the original value in the JSON string.

let isOdd = (a) => !!(a & 1);
let jsonStr = '{"a":3, "b":2}';
let reviver = (key, value) => {
    if(isOdd(value)){
        return 2 * value;
    }
    return value;
};
let output = JSON.parse(jsonStr, reviver);
console.log(output);
// { a: 6, b: 2}

2. Inner properties are processed before outer properties.

let logger = (key, value) => console.log(`${key} : ${value}`); let output = JSON.parse('{"a":1, "b":2, "c": {"d": 4}}', logger);

3. Return undefined to filter out values. If you don’t want to change a value, you must return it. Otherwise, it’ll vanish in the converted object.

Note: JavaScript functions return undefined by default unless when invoked with the new keyword.

let jsonStr = '{"a":3, "b":2}';
let reviver = (key, value) => undefined;
let output = JSON.parse(jsonStr, reviver);
console.log(output);
// undefined

4. It is possible to transform the entire object; however, this requires some more processing. The last argument passed to the reviver function is the entire object and the key is an empty string (see picture in point 2).

The conundrum is that empty strings are also valid keys in JSON; now if you have a reviver function that removes values with the “” key, you risk losing the entire object. This should not be a problem though but knowing this edge case can help while debugging.

let jsonStr = '{"a":3, "b":2}';
let reviver = (key, value) => {
    if(key !== ""){
        return value;
    }
};
let output = JSON.parse('{"a":1, "b":2}', reviver);
console.log(output);
// undefined

4. Safe parser – TryParse

JSON.parse throws if invoked with invalid JSON and this can cause strange bugs. This problem happens often especially with the preponderance of JavaScript frameworks. At least, I have run into it while using Angular1 and Angular2.

Here is a sequence of steps that can trigger this scenario:

1. The 4XX and 5XX class of HTTP errors have optional response bodies so it is OK for a server to return a 400 with no body

2. The client side code expects a JSON body. For example, Angular2‘s HttpClient.get defaults to the JSON type.

3. Frameworks might use JSON.parse deep down in their stack (both Angular1 and Angular2 do this).

4. If the body is empty, then JSON.parse() throws and the error bubbles up the stack.

5. Fix?

  1. Have the server return valid JSON in the body of all requests
  2. Retrieve the HTTP response and then try to safely parse. A trivial safe parser that returns null for invalid input looks like this:
function tryParse(input) {
    try {
        JSON.parse(input);
    } catch (e) {
        return undefined;
    }
}
let b = tryParse('{');
console.log(b);
//undefined

I choose to return undefined whenever JSON.parse throws for two reasons:

  1. This allows for handling null values. The string “null” is valid JSON.
  2. Undefined is not a value in the JSON grammar

5. Gotchas

  • Comments invalidate JSON, there are some workarounds like repeating the same key to override earlier values but I am not so convinced
  • Serializing dates can be an issue, the workaround is to use timestamps which I think helps removes the overhead. In one of my past teams, we had to use a custom date time string representation which had to be parsed on the client specially.

Conclusion

I just realized I wrote the first part almost exactly a year ago; this complement has lived for an extremely long time as an unpublished draft because I needed to polish it.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.