Some tips about JavaScript JSON
1. Format
The default stringer also shrinks JSON, which looks ugly
const user = { name: 'John', age: 30, isAdmin: true, friends: ['Bob', 'Jane'], address: { city: 'New York', country: 'USA' } }; console.log(JSON.stringify(user)); //=> {"name":"John","age":30,"isAdmin":true,"friends":["Bob","Jane"],"address":{"city":"New York","country":"USA"}}
JSON.stringify also has a built-in formatter!
console.log(JSON.stringify(user, null, 2)); // { // "name": "John", // "age": 30, // "isAdmin": true, // "friends": [ // "Bob", // "Jane" // ], // "address": { // "city": "New York", // "country": "USA" // } // }
(if you want to know what that null is, we'll talk about it later)
In this example, the JSON format is 2 indented spaces.
We can also specify custom characters for indentation.
console.log(JSON.stringify(user, null, 'lol')); // { // lol"name": "John", // lol"age": 30, // lol"isAdmin": true, // lol"friends": [ // lollol"Bob", // lollol"Jane" // lol], // lol"address": { // lollol"city": "New York", // lollol"country": "USA" // lol} // }
2. Hide some attributes in string data
JSON.stringify the second parameter, which is largely unknown. It is called replacement, which is a function or array used to determine which data is retained in the output and which is not.
This is a simple example where we can hide the password user.
const user = { name: 'John', password: '12345', age: 30 }; console.log(JSON.stringify(user, (key, value) => { if (key === 'password') { return; } return value; }));
This is the output:
{"name":"John","age":30}
We can further reconstruct:
function stripKeys(...keys) { return (key, value) => { if (keys.includes(key)) { return; } return value; }; } const user = { name: 'John', password: '12345', age: 30, gender: 'male' }; console.log(JSON.stringify(user, stripKeys('password', 'gender')))
Output:
{"name":"John","age":30}
You can also pass an array to get only some keys:
const user = { name: 'John', password: '12345', age: 30 } console.log(JSON.stringify(user, ['name', 'age']))
Output the same thing.
This also applies to arrays. If you have a lot of cakes:
const cakes = [ { name: 'Chocolate Cake', recipe: [ 'Mix flour, sugar, cocoa powder, baking powder, eggs, vanilla, and butter', 'Mix in milk', 'Bake at 350 degrees for 1 hour', // ... ], ingredients: ['flour', 'sugar', 'cocoa powder', 'baking powder', 'eggs', 'vanilla', 'butter'] }, // tons of these ];
We can easily do the same thing, and the replacer will be applied to each cake:
const cakes = [ { name: 'Chocolate Cake', recipe: [ 'Mix flour, sugar, cocoa powder, baking powder, eggs, vanilla, and butter', 'Mix in milk', 'Bake at 350 degrees for 1 hour', // ... ], ingredients: ['flour', 'sugar', 'cocoa powder', 'baking powder', 'eggs', 'vanilla', 'butter'] }, // tons of these ]; console.log(JSON.stringify(cakes, ['name']))
We get this:
[{"name":"Chocolate Cake"},{"name":"Vanilla Cake"},...]
3. Create a custom output format using toJSON
If an object implements the toJSON function, JSON Stringify will use it to string the data.
Think about it:
class Fraction { constructor(n, d) { this.numerator = n; this.denominator = d; } } console.log(JSON.stringify(new Fraction(1, 2)))
This will output {"numerator":1,"denominator":2} But what if we want to replace 1 / 2 of it with a string?
Enter toJSON
class Fraction { constructor(n, d) { this.numerator = n; this.denominator = d; } toJSON() { return `${this.numerator}/${this.denominator}` } } console.log(JSON.stringify(new Fraction(1, 2)))
JSON.stringify respects toJSON property and output "1 / 2".
4. Recover data
Our score example above works well. But what if we want to recover the data? When we parse JSON again, isn't it cool if the score can be magically returned? We can!
Enter the resurrection!
class Fraction { constructor(n, d) { this.numerator = n; this.denominator = d; } toJSON() { return `${this.numerator}/${this.denominator}` } static fromJSON(key, value) { if (typeof value === 'string') { const parts = value.split('/').map(Number); if (parts.length === 2) return new Fraction(parts); } return value; } } const fraction = new Fraction(1, 2); const stringified = JSON.stringify(fraction); console.log(stringified); // "1/2" const revived = JSON.parse(stringified, Fraction.fromJSON); console.log(revived); // Fraction { numerator: 1, denominator: 2 }
We can pass the second parameter JSON Parse to specify the revever function. The job of the restorer is to "restore" the string data back to its original form. Here, we pass a reviver, which is the static fromJSON attribute Fraction of the class.
In this case, the reviver checks whether the value is a valid score, and if so, it creates a new Fraction object and returns it.
Interesting fact: this feature is used for built-in Date objects. Try to find Date prototype. toJSON
That's why it works:
console.log(JSON.stringify(new Date())) //=> '"2022-03-01T06:28:41.308Z"'
To restore the date, we can use JSON parse:
function reviveDate(key, value) { const regex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,}|)Z$/; if (typeof value === "string" && regex.test(value)) { return new Date(value); } return value; } console.log(JSON.parse('"2022-03-01T06:28:41.308Z"', reviveDate)) //=> Tue Mar 01 2022 06:28:41 GMT-0700 (Pacific Daylight Time)
5. Use reversers to hide data
Like the parser, the restorer can also be used to hide data. It works the same way.
Here is an example:
const user = JSON.stringify({ name: 'John', password: '12345', age: 30 }); console.log(JSON.parse(user, (key, value) => { if (key === 'password') { return; } return value; }));
This is the output:
{ name: 'John', age: 30 }
If you know any other cool JSON tips, please speak up.