I'm pretty sure you have used the global JSON
object for a variety of things, like in fetch requests and to avoid the dreaded [object Object]
. I also bet you didn't know about the rest of the largely unknown features that JSON
can provide!
JSON
can do cool stuff like revive data, use a custom format to encode/decode data, hide certain properties in stringified data, and format your JSON! 🤯
Sound interesting? Let's dive into it!
1. Formatting
The default stringifier also minifies 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
has a built in formatter too!
console.log(JSON.stringify(user, null, 2));
// {
// "name": "John",
// "age": 30,
// "isAdmin": true,
// "friends": [
// "Bob",
// "Jane"
// ],
// "address": {
// "city": "New York",
// "country": "USA"
// }
// }
(If you are wondering what that null is, we'll come to it later)
In this example, the JSON was formatted with 2 spaces of indentation.
We can also specify a custom character to use 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. Hiding certain properties in stringified data
JSON.stringify
had a second argument which is largely unknown. It's called the replacer
and it's a function or array that decides which data to keep in the output and which not to.
Here's a simple example where we can hide the password
of a user.
const user = {
name: 'John',
password: '12345',
age: 30
};
console.log(JSON.stringify(user, (key, value) => {
if (key === 'password') {
return;
}
return value;
}));
And this is the output:
{"name":"John","age":30}
We can further refactor this:
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')))
Which outputs:
{"name":"John","age":30}
You can also pass an array to get certain keys only:
const user = {
name: 'John',
password: '12345',
age: 30
}
console.log(JSON.stringify(user, ['name', 'age']))
Which output the same thing.
The cool thing is this works on arrays too. If you had a huge array 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"},...]
Cool stuff!
3. Using toJSON to create custom output formats
If an object implements the toJSON
function, JSON.stringify
will use it to stringify the data.
Consider this:
class Fraction {
constructor(n, d) {
this.numerator = n;
this.denominator = d;
}
}
console.log(JSON.stringify(new Fraction(1, 2)))
This would output {"numerator":1,"denominator":2}
. But what if we wanted to replace this with a string 1/2
?
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 output "1/2"
.
4. Reviving data
Our fraction example above works nicely. But what if we want to revive the data? Wouldn't it be cool if the fraction would be magically brought back when we parse the JSON again? We can!
Enter revivers!
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 a second argument to JSON.parse
to specify a reviver function. The job of the reviver is to "revive" stringified data back into it's original form. Here, we are passing a reviver, which is the static proprty fromJSON
of the Fraction
class.
In this case the reviver checks if the value is a valid fraction and if it is, it creates a new Fraction
object and returns it.
Fun fact: this feature is used in the built-in Date object. Try looking up
Date.prototype.toJSON
That's why this works:console.log(JSON.stringify(new Date())) //=> '"2022-03-01T06:28:41.308Z"'
To revive 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. Using revivers to hide data
Like resolvers, revivers can also be used to hide data. It works in the same way.
Here's 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;
}));
And this is the output:
{ name: 'John', age: 30 }
As an exercise, check if you can rewrite the previously shown resolvers as revivers.
That's a wrap!
Let me know if you know any other cool JSON
tricks 👀
Thanks for reading!
Top comments (25)
Please edit the Title.
This has nothing todo with JSON directly it's more features of Javascript you're showing...
I meant the JSON object in JavaScript, yes. I'll edit it. Thanks!
Well JSON states for JavaScript Object Notation so I'm giving a point to the OP here 😂
I never really thought about that 😂
Oh a "Knows it all" if you want to smartass, then you probably know that JSON is still not the same as Javascript.
Of course not, I just defined what JSON states for. Please sir, continue scrolling, nothing to see here
+1
Agreed. Looks like clickbait.
All clickbait isn't bad
I ran a quick benchmark and didn't realize how much passing an array of keys helps performance! I'll look out for that in the future, thanks 💪
🤯 Thanks for writing this, I learned a lot! What would a reviver for an array like the
cakes
array look like? Would you filter the array and populate the rest of the details if the name matched?It would probably be impossible to write a reviver for the current reducer I wrote, since all data is deleted. If we did have a reference to the original array, yes we could find the matching cake and then populate the rest of the data.
Thanks for explaining that!
Awesome 😎
😎
Really I don't know about this stuff. Thank you for sharing
Awesome
Great writeup.
Thanks for sharing
Awesome! Thanks for sharing
Thanks!
👏 bravo I knew 1 and a half of these, learn something new about old dogs every day... Is that the expression
Really cool, thanks for sharing!
Thanks for reading, too!
Some comments may only be visible to logged-in visitors. Sign in to view all comments.