JSON for JS devs

JSON , JSON , JSON deep dive in JS

Let’s say we have a complex object, and we’d like to convert it into a string, send it over a network, or just output it for logging purposes.

Naturally, such a string should include all important properties.

We could implement the conversion like this:

let user = {
  name: "John",
  age: 30,

  toString() {
    return `{name: "${this.name}", age: ${this.age}}`;
  }
};

console.log(user.toString()); // {name: "John", age: 30}

But in the process of development, new properties are added, and old properties are renamed and removed. Updating such toString every time can become a pain. We could try to loop over properties in it, but what if the object is complex and has nested objects in properties? We’d need to implement their conversion as well.

Luckily, there’s no need to write the code to handle all this. The task has been solved already.

Static function of JSON

💡
JSON.stringify(data:any,replacer:function,format:number)

The JSON (JavaScript Object Notation) is a general format to represent values and objects. It is described in the RFC 4627 standard. Initially, it was made for JavaScript, but many other languages have libraries to handle it as well. So it’s easy to use JSON for data exchange when the client uses JavaScript and the server is written in Ruby, PHP, Java, whatever.

JavaScript provides methods:

  • JSON.stringify to convert objects into JSON.

  • JSON.parse to convert JSON back into an object.

let student = {
  name: 'Vince',
  age: 30,
  isAdmin: false,
  courses: ['html', 'css', 'js'],
  spouse: null
};

let json = JSON.stringify(student);

console.log(typeof json); // we've got a string!

console.log(json);
/* JSON-encoded object:
'{
   "name" : "Vince",
   "age":30,
   "isAdmin":false,
   "course":['html','css','js'],
   "spouse":null
}'💡
💡
The object will be converted to a string wrapped around a single quote, the key will be a string with a double quote (no single quote,no backtick), and values will remain.

JSON.stringify can be applied to primitives as well.

JSON supports the following data types:

  • Objects { ... }

  • Arrays [ ... ]

  • Primitives:

    • strings,

    • numbers,

    • boolean values true/false,

    • null.

// a number in JSON is just a number
console.log( JSON.stringify(1) ) // '1'

// a string in JSON is still a string, but double-quoted
console.log( JSON.stringify('test') ) // 'test'

console.log( JSON.stringify(true) ); // 'true'

console.log( JSON.stringify([1, 2, 3]) ); // '[1,2,3]'

JSON is meant to be used for data-exchange so some of the data types in JS can't fit in and will be skipped by JSON

Namely:

  • Function properties (methods).

  • Symbolic keys and values [Symbol.iterator]()'

  • Properties that storeundefined.

💡
It also supports null but not undefined :) '{ "name" : null }' is OKAY because null in an "object"
let user = {
  sayHi() { // ignored
    alert("Hello");
  },
  [Symbol("id")]: 123, // ignored
  something: undefined // ignored
};

alert( JSON.stringify(user) ); // {} (empty object)

Nested object

let meetup = {
  title: "Conference",
  room: {
    number: 23,
    participants: ["john", "ann"]
  }
};

JSON.stringify(meetup);

'
{"title":"Conference",
 "room":{ "number":23,
          "participants":["john","ann"] 
        }
  }'

The important limitation is that there must be no circular references.

let room = {
  number: 23
};

let meetup = {
  title: "Conference",
  participants: ["john", "ann"]
};

meetup.place = room;       // meetup references room
room.occupiedBy = meetup; // room references meetup

JSON.stringify(meetup);
💡
Error: Converting circular structure to JSON

Implement JSON.stringify

Transforming replacer

let room = {
  number: 23
};

let meetup = {
  title: "Conference",
  participants: [{name: "John"}, {name: "Alice"}],
  place: room // meetup references room
};

room.occupiedBy = meetup; // room references meetup

console.log( JSON.stringify(meetup, function replacer(key, value) {
  return (key == 'occupiedBy') ? undefined : value;
  // If we return a value of undefined -> against JSON rules
  // It will be removed from JSON
}));



{"title":"Conference","participants":[{"name":"John"},
                      {"name":"Alice"}],"place":{"number":23}}
💡
We can filter out the value we want to have in a JSON format

Formatting:space

The third argument of JSON.stringify(value,replacer,space) is the number of spaces to use for pretty formatting

let user = {
  name: "John",
  age: 25,
  roles: {
    isAdmin: false,
    isEditor: true
  }
};

alert(JSON.stringify(user, null, 2));
/* two-space indents:
'{
  "name": "John",
  "age": 25,
  "roles": {
    "isAdmin": false,
    "isEditor": true
  }
}'
*/

/* for JSON.stringify(user, null, 4) the result would be more indented:
'{
    "name": "John",
    "age": 25,
    "roles": {
        "isAdmin" : false,
        "isEditor": true
    }
}'
*/

Custom "to JSON"

An object may provide a custom toJSON methods

let room = {
  number: 23
};

let meetup = {
  title: "Conference",
  date: new Date(Date.UTC(2017, 0, 1)), //Date.prototype.JSON converts to string
  room
};

alert( JSON.stringify(meetup) );
/*
  {
    "title":"Conference",
    "date":"2017-01-01T00:00:00.000Z",  // (1)
    "room": {"number":23}               // (2)
  }
*/

We can write our own custom JSON to convert the object to the type we want

JSON.parse

To decode a JSON string, we need another method named JSON.parse

let value = JSON.parse(str, [reviver]);

// stringified array
let numbers = "[0, 1, 2, 3]";

numbers = JSON.parse(numbers);

alert( numbers[1] ); // 1

let userData = '
  { "name": "John", 
     "age": 35, 
     "isAdmin": false, 
     "friends": [0,1,2,3] }';

let user = JSON.parse(userData);

alert( user.friends[1] ); // 1
💡
The JSON may be as complex as necessary; objects and arrays can include other objects and arrays. But they must obey the same JSON format.
let json = `{
  name: "John",                     // mistake: property name without quotes
  "surname": 'Smith',               // mistake: single quotes in value (must be double)
  'isAdmin': false                  // mistake: single quotes in key (must be double)
  "birthday": new Date(2000, 2, 3), // mistake: no "new" is allowed, only bare values
  "friends": [0,1,2,3]              // here all fine
}`;

Using reviver as middleware

let str = '{"title":"Conference","date":"2017-11-30T12:00:00.000Z"}';

let meetup = JSON.parse(str);

alert( meetup.date.getDate() ); // Error!

meetup.date // '2017-11-30T12:00:00.000Z'

let schedule = `{
  "meetups": [
    {"title":"Conference","date":"2017-11-30T12:00:00.000Z"},
    {"title":"Birthday","date":"2017-04-18T12:00:00.000Z"}
  ]
}`;

schedule = JSON.parse(schedule, function(key, value) {
  if (key == 'date') return new Date(value);
  return value;
});

alert( schedule.meetups[1].date.getDate() ); // works!

Summary

  • JSON is a data format that has its own independent standard and libraries for most programming languages.

  • JSON supports plain objects, arrays, strings, numbers, booleans, and null.

  • JavaScript provides methods JSON.stringify to serialize into JSON and JSON.parse to read from JSON.

  • Both methods support transformer functions for smart reading and writing.

  • If an object has toJSON, then it is called by JSON.stringify.

Exercises

let room = {
  number: 23
};

let meetup = {
  title: "Conference",
  occupiedBy: [{name: "John"}, {name: "Alice"}],
  place: room
};

// circular references
room.occupiedBy = meetup;
meetup.self = meetup;

console.log(JSON.stringify(meetup,(key,value)=>{
    if(key == "occupiedBy" | key == "self") {
        return undefined 
    }
    else {
        return value;
    }
}));
// {"title":"Conference","place":{"number":23}}
let room = {
  number: 23
};

let meetup = {
  title: "Conference",
  occupiedBy: [{name: "John"}, {name: "Alice"}],
  place: room
};

// circular references
room.occupiedBy = meetup;
meetup.self = meetup;

console.log(JSON.stringify(meetup, (key,value)=>{
    if(key != "" && value == meetup ) {
        return undefined; 
    }
    else {
        return value;
    }
}));

// {"title":"Conference","occupiedBy":[{"name":"John"},
// {"name":"Alice"}],"place":{"number":23}}