A little trick about JSON in JavaScript
1. Formatting
The default stringizer also shrinks the 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 get to that 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 properties in stringified data
JSON.stringify
The second parameter, which is largely unknown. It's called replacer, and it's a function or array that decides what data to keep in the output and what not to.
Here 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;
}));
Here is the output:{"name":"John","age":30}
We can refactor further:
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 certain keys:
const user = {
name: 'John',
password: '12345',
age: 30
}
console.log(JSON.stringify(user, ['name', 'age']))
output the same thing.
This also works for arrays. If you have a bunch 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 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. Use toJSON to create a custom output format
If an object implements the toJSON function, JSON.stringify will use it to stringify 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 the toJSON property and outputs "1/2".
4. Recover data
Our fractional example above works well. But what if we want to recover data? Wouldn't it be cool if the score could be magically returned when we parsed the JSON again? 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 reviver function. The restorer's job is to "restore" the stringified data back to its original form. Here we pass a reviver which is the static fromJSON property Fraction of the class.
In this case, the reviver checks if the value is a valid fraction, and if so, it creates a new Fraction object and returns it.
Fun fact: this functionality is used with the built-in Date object. Try to find Date.prototype.toJSON
Here'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 revivers to hide data
Like parsers, restorers 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;
}));
Here is the output:{ name: 'John', age: 30 }
If you know of any other cool JSON tricks, please let me know.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。