Some Things About JSON That You May Have Missed
Tags: javascript, tutorial, beginners, webdev
Published: 2020-01-04 21:12:23

**Subscribe to my email list now at http://jauyeung.net/subscribe/**

**Follow me on Twitter at https://twitter.com/AuMayeung**

**Many more articles at https://medium.com/@hohanga**

JSON stands for JavaScript Object Notation. It is a format for serializing data, which means that it can be used to transmit and receive data between different sources. In JavaScript, there’s a `JSON` utility object that provides methods to convert JavaScript objects to JSON strings and vice versa. The `JSON` utility object can’t be constructed or called — there are only 2 static methods which are `stringify` and `parse` to convert between JavaScript objects and JSON strings.

Properties of JSON
==================

JSON is a syntax for serializing objects, arrays, numbers, booleans, and `null`. It is based on the JavaScript object syntax, but they are not the same thing. Not all JavaScript object properties can be converted to valid JSON, and JSON strings must be correctly formatted to be converted into a JavaScript object.

For objects and arrays, JSON property names must be in double-quoted strings, and trailing commas for objects are prohibited. Numbers cannot have leading zeroes, and a decimal point must be followed by at least one digit. `NaN` and `Infinity` aren’t supported, and JSON strings can’t have `undefined` or comments. In addition, JSON can not contain functions.

Any JSON text must contain valid JavaScript expressions. In some browser engines, the U+2028 line separator and U+2029 paragraph separator are allowed in string literals and property keys in JSON, but when using them in JavaScript code will result in SyntaxError. Those 2 characters can be parsed with `JSON.parse` into valid JavaScript strings, but fails when passed into `eval`.

Insignificant whitespace may be included anywhere except within JSONNumber or JSONString. Numbers can’t have whitespace inside and strings would be interpreted as whitespace in the string or cause an error. The tab character (U+0009), carriage return (U+000D), line feed (U+000A), and space (U+0020) characters are the only valid whitespace characters in JSON.

Basic Usage of the JSON Object
==============================

There are 2 methods on the `JSON` utility object. There is the `stringify` method for converting a JavaScript object to a JSON string and the `parse` method for converting a JSON string to a JavaScript object.

The `parse` method parses a string as JSON with a function as a second argument to optionally transform JSON entities to the JavaScript entity that you specified and return the resulting JavaScript object. If the string has entities that aren’t allowed in the JSON syntax, then a SyntaxError would be raised. Also, tailing commas aren’t allowed in the JSON string that is passed into `JSON.parse`. For example, we can use it as in the following code:

```javascript
JSON.parse('{}'); // {}       
JSON.parse('false'); // false        
JSON.parse('"abc"'); // 'abc'         
JSON.parse('[1, 5, "abc"]');  // [1, 5, 'abc']  
JSON.parse('null'); // null
```

The first line would return an empty object. The second would return `false`. The third line would return `'abc'`. The fourth line would return `[1, 5, "abc"]`. The fifth line would return `null`. It returns what we expect since every line we pass in is valid JSON.


Customize the Behavior of Stringify and Parse
=============================================

Optionally, we can pass in a function as the second argument to convert values to whatever we want. The function we pass in will take the key as the first parameter and the value as the second and returns the value after manipulation is done. For example, we can write:

```javascript
JSON.parse('{"a:": 1}', (key, value) =>  
  typeof value === 'number'  
    ? value * 10  
    : value       
);
```

Then we get `{a: 10}` returned. The function returns the original value multiplied by 10 if the value’s type is a number.

The `JSON.stringify` method can take a function as the second parameter that maps entities in the JavaScript object to something else in JSON. By default, all instances of `undefined` and unsupported native data like functions are removed. For example, if we write the following code:

```javascript
const obj = {  
  fn1() {},  
  foo: 1,  
  bar: 2,  
  abc: 'abc'  
}  
const jsonString = JSON.stringify(obj);  
console.log(jsonString);
```

Then we see that `fn1` is removed from the JSON string after running `JSON.stringify` since functions aren’t supported in JSON syntax. For `undefined`, we can see from the following code that `undefined` properties will be removed.

```javascript
const obj = {  
  fn1() {},  
  foo: 1,  
  bar: 2,  
  abc: 'abc',  
  nullProp: null,  
  undefinedProp: undefined  
}  
const jsonString = JSON.stringify(obj);  
console.log(jsonString);
```

`undefinedProp` is not in the JSON string logged because it has been removed by `JSON.strinfiy`.

Also, `NaN` and `Infinity` all become `null` after converting to a JSON string:

```javascript
const obj = {  
  fn1() {},  
  foo: 1,  
  bar: 2,  
  abc: 'abc',  
  nullProp: null,  
  undefinedProp: undefined,  
  notNum: NaN,  
  infinity: Infinity  
}  
const jsonString = JSON.stringify(obj);  
console.log(jsonString);
```

We see that:

```javascript
'{“foo”:1,”bar”:2,”abc”:”abc”,”nullProp”:null,”notNum”:null,”infinity”:null}'
```

`NaN` and `Infinity` have both become `null` instead of the original values.

For unsupported values, we can map them to supported values with the replacer function in the second argument which we can optionally pass in. The replace function takes the key of a property as the first parameter and the value as the second parameter. For example, one way to keep `NaN`, `Infinity`, or functions is to map them to a string like in the following code:

```javascript
const obj = {  
  fn1() {},  
  foo: 1,  
  bar: 2,  
  abc: 'abc',  
  nullProp: null,  
  undefinedProp: undefined,  
  notNum: NaN,  
  infinity: Infinity  
}

const replacer = (key, value) => {  
  if (value instanceof Function) {  
    return value.toString();  
  } 
  else if (value === NaN) {  
    return 'NaN';  
  } 
  else if (value === Infinity) {  
    return 'Infinity';  
  } 
  else if (typeof value === 'undefined') {  
    return 'undefined';  
  } 
  else {  
    return value; // no change  
  }  
}

const jsonString = JSON.stringify(obj, replacer, 2);  
console.log(jsonString);
```

After running `console.log` on `jsonString` in the last line, we see that we have:

```javascript
{  
  "fn1": "fn1() {}",  
  "foo": 1,  
  "bar": 2,  
  "abc": "abc",  
  "nullProp": null,  
  "undefinedProp": "undefined",  
  "notNum": null,  
  "infinity": "Infinity"  
}
```

What the `replace` function did was add additional parsing using the key and the value from the object being converted with `JSON.stringify`. It checks that if the `value` is a function, then we convert it to a string and return it. Likewise, with `NaN`, `Infinity`, and `undefined`, we did the same thing. Otherwise, we return the value as-is.

The third parameter of the `JSON.stringfy` function takes in a number to set the number of whitespaces to be inserted into the output of the JSON to make the output more readable. The third parameter can also take any string that will be inserted instead of whitespaces. Note that if we put a string as the third parameter that contains something other than white space(s), we may create a “JSON” a string that is not valid JSON.

For example, if we write:

```javascript
const obj = {  
  fn1() {},  
  foo: 1,  
  bar: 2,  
  abc: 'abc',  
  nullProp: null,  
  undefinedProp: undefined,  
  notNum: NaN,  
  infinity: Infinity  
}
const replacer = (key, value) => {  
  if (value instanceof Function) {  
    return value.toString();  
  } 
  else if (value === NaN) {  
    return 'NaN';  
  } 
  else if (value === Infinity) {  
    return 'Infinity';  
  } 
  else if (typeof value === 'undefined') {  
    return 'undefined';  
  } 
  else {  
    return value; // no change  
  }  
}
const jsonString = JSON.stringify(obj, replacer, 'abc');  
console.log(jsonString);
```

Then `console.log` will be:

```json
{  
abc"fn1": "fn1() {}",  
abc"foo": 1,  
abc"bar": 2,  
abc"abc": "abc",  
abc"nullProp": null,  
abc"undefinedProp": "undefined",  
abc"notNum": null,  
abc"infinity": "Infinity"  
}
```

Which is obviously not valid JSON. `JSON.stringify` will throw a “cyclic object value” TypeError. Also, if an object has `BigInt` values, then the conversion will fail with a “BigInt value can’t be serialized in JSON” TypeError.

Also, note that Symbols are automatically discarded with `JSON.stringify` if they are used as a key in an object. So if we have:

```javascript
const obj = {  
  fn1() {},  
  foo: 1,  
  bar: 2,  
  abc: 'abc',  
  nullProp: null,  
  undefinedProp: undefined,  
  notNum: NaN,  
  infinity: Infinity,  
  [Symbol('foo')]: 'foo'  
}

const replacer = (key, value) => {
  if (value instanceof Function) {  
    return value.toString();  
  } 
  else if (value === NaN) {  
    return 'NaN';  
  } 
  else if (value === Infinity) {  
    return 'Infinity';  
  } 
  else if (typeof value === 'undefined') {  
    return 'undefined';  
  } 
  else {  
    return value; // no change  
  }  
}

const jsonString = JSON.stringify(obj, replacer, 2);  
console.log(jsonString);
```

We get back:

```javascript
{  
  "fn1": "fn1() {}",  
  "foo": 1,  
  "bar": 2,  
  "abc": "abc",  
  "nullProp": null,  
  "undefinedProp": "undefined",  
  "notNum": null,  
  "infinity": "Infinity"  
}
```

Date objects are converted to strings by using the same string as what `date.toISOString()` will return. For example, if we put:

```javascript
const obj = {  
  fn1() {},  
  foo: 1,  
  bar: 2,  
  abc: 'abc',  
  nullProp: null,  
  undefinedProp: undefined,  
  notNum: NaN,  
  infinity: Infinity,  
  [Symbol('foo')]: 'foo',  
  date: new Date(2019, 1, 1)  
}  
const replacer = (key, value) => {  
  if (value instanceof Function) {  
    return value.toString();  
  } 
  else if (value === NaN) {  
    return 'NaN';  
  } 
  else if (value === Infinity) {  
    return 'Infinity';  
  } 
  else if (typeof value === 'undefined') {  
    return 'undefined';  
  } 
  else {  
    return value; // no change  
  }  
}  
const jsonString = JSON.stringify(obj, replacer, 2);  
console.log(jsonString);
```

We get:

```javascript
{  
  "fn1": "fn1() {}",  
  "foo": 1,  
  "bar": 2,  
  "abc": "abc",  
  "nullProp": null,  
  "undefinedProp": "undefined",  
  "notNum": null,  
  "infinity": "Infinity",  
  "date": "2019-02-01T08:00:00.000Z"  
}
```

As we can see, the value of the `date` property is now a string after converting to JSON.

Deep Copy Objects
=================

We can also use `JSON.stringify` with `JSON.parse` to make a deep copy of JavaScript objects. For example, to do a deep copy of a object without a library, you can `JSON.stringify` then `JSON.parse`:

```javascript
const a = { foo: {bar: 1, {baz: 2}}  
const b = JSON.parse(JSON.stringfy(a)) // get a clone of a which you can change with out modifying a itself
```

This does a deep copy of an object, which means all levels of an object are cloned instead of referencing the original object. This works because `JSON.stringfy` converted the object to a string which are immutable, and a copy of it is returned when `JSON.parse` parses the string which returns a new object that doesn’t reference the original object.