JSON has been used in lots of places to exchange data among different services/systems. Nowadays all mainstream programming languages have built-in library support of JSON data parse and stringify. In this post, let's talk about JSON.stringify() in JavaScript.
Let's first take a small example of Object conversion handling. There is a requirement to convert below object
const todayILearn = {
_id: 1,
content: '今天å¦ä¹ JSON.stringify(),我很开心ï¼',
created_at: 'Mon Nov 25 2019 14:03:55 GMT+0800 (ä¸å›½æ ‡å‡†æ—¶é—´)',
updated_at: 'Mon Nov 25 2019 16:03:55 GMT+0800 (ä¸å›½æ ‡å‡†æ—¶é—´)'
}
to below object.
const todayILearn = {
id: 1,
content: '今天å¦ä¹ JSON.stringify(),我很开心ï¼',
createdAt: 'Mon Nov 25 2019 14:03:55 GMT+0800 (ä¸å›½æ ‡å‡†æ—¶é—´)',
updatedAt: 'Mon Nov 25 2019 16:03:55 GMT+0800 (ä¸å›½æ ‡å‡†æ—¶é—´)'
}
The changes here are:
- Change property name
_id
toid
- Change property name
created_at
andupdated_at
tocreatedAt
andupdatedAt
The values remain untouched.
There are lots of methods to achieve above requirement.
1. Loop through all properties
const todayILearnTemp = {};
for (const [key, value] of Object.entries(todayILearn)) {
if (key === "_id") todayILearnTemp["id"] = value;
else if (key === "created_at") todayILearnTemp["createdAt"] = value;
else if (key === "updatedAt") todayILearnTemp["updatedAt"] = value;
else todayILearnTemp[key] = value;
}
console.log(todayILearnTemp);
// output:
// { id: 1,
// content: '今天å¦ä¹ JSON.stringify(),我很开心ï¼',
// createdAt: 'Mon Nov 25 2019 14:03:55 GMT+0800 (ä¸å›½æ ‡å‡†æ—¶é—´)',
// updated_at: 'Mon Nov 25 2019 16:03:55 GMT+0800 (ä¸å›½æ ‡å‡†æ—¶é—´)'
// }
This method creates a new temp variable and loops through all properties and introduces lots of if-else statements which is not elegant and scalable at all.
2. Delete and create properties
Can add three new properties and delete the three unneeded properties.
todayILearn.id = todayILearn._id;
todayILearn.createdAt = todayILearn.created_at;
todayILearn.updatedAt = todayILearn.updated_at;
delete todayILearn._id;
delete todayILearn.created_at;
delete todayILearn.updated_at;
console.log(todayILearn);
//output:
//{
// content: '今天å¦ä¹ JSON.stringify(),我很开心ï¼',
// id: 1,
// createdAt: 'Mon Nov 25 2019 14:03:55 GMT+0800 (ä¸å›½æ ‡å‡†æ—¶é—´)',
// updatedAt: 'Mon Nov 25 2019 16:03:55 GMT+0800 (ä¸å›½æ ‡å‡†æ—¶é—´)'
//}
It has the same drawback as above method and also the order of properties have been changed.
3. JSON.stringify()
JSON.stringify() can be used to replace the unwanted property names with the desired ones.
const mapObj = {
_id: "id",
created_at: "createdAt",
updated_at: "updatedAt"
};
JSON.parse(
JSON.stringify(todayILearn).replace(
/_id|created_at|updated_at/gi,
matched => mapObj[matched])
)
// {
// id: 1,
// content: '今天å¦ä¹ JSON.stringify(),我很开心ï¼',
// createdAt: 'Mon Nov 25 2019 14:03:55 GMT+0800 (ä¸å›½æ ‡å‡†æ—¶é—´)',
// updatedAt: 'Mon Nov 25 2019 16:03:55 GMT+0800 (ä¸å›½æ ‡å‡†æ—¶é—´)'
// }
It looks like this is much more elegant now.
Now let's get to know what are the major rules of JSON.stringify() so that it can be used properly and efficiently when needed.
Rule 1
The output value of undefined
, function and symbol will be different if they appear in object property value, array or as standalone value.
Take a guess on what will be output for below code snippet.
const data = {
a: "aaa",
b: undefined,
c: Symbol("dd"),
fn: function() {
return true;
}
};
JSON.stringify(data); // 输出:?
// "{"a":"aaa"}"
When undefined
, function and symbol appear as object property value, they will be ignored when stringifying.
How about when appearing in array?
JSON.stringify(["aaa", undefined, function aa() {
return true
}, Symbol('dd')]) // output:?
// "["aaa",null,null,null]"
When undefined
, function and symbol appear in array, they will be converted to null.
What if they appear standalone?
JSON.stringify(function a (){console.log('a')})
// undefined
JSON.stringify(undefined)
// undefined
JSON.stringify(Symbol('dd'))
// undefined
When undefined
, function and symbol appear standalone, they will be undefined.
Rule 2
The stringified properties may not be in the original order if it's not an array object.
const data = {
a: "aaa",
b: undefined,
c: Symbol("dd"),
fn: function() {
return true;
},
d: "ddd"
};
JSON.stringify(data); // output:?
// "{"a":"aaa","d":"ddd"}"
JSON.stringify(["aaa", undefined, function aa() {
return true
}, Symbol('dd'),"eee"]) // output:?
// "["aaa",null,null,null,"eee"]"
Just like what rule 1 mentioned, some properties would be ignored when stringifying if they are one of undefined
, function or symbol. Hence the order may not be kept.
Rule 3
If an object implemented toJSON()
function, the output value of JSON.stringify() will be the return value of the toJSON() function.
JSON.stringify({
say: "hello JSON.stringify",
toJSON: function() {
return "today i learn";
}
})
// "today i learn"
Rule 4
NaN
, Infinity
or null
will be stringified to null
.
JSON.stringify(NaN)
// "null"
JSON.stringify(null)
// "null"
JSON.stringify(Infinity)
// "null"
Rule 5
Boolean, Number or String object will be converted to their corresponding primitive values.
JSON.stringify([new Number(1), new String("false"), new Boolean(false)]);
// "[1,"false",false]"
Rule 6
Only enumerable properties can be stringified, these include Map/Set/WeakMap/WeakSet etc.
JSON.stringify(
Object.create(
null,
{
x: { value: 'json', enumerable: false },
y: { value: 'stringify', enumerable: true }
}
)
);
// "{"y","stringify"}"
Rule 7
Many people may know that the simplest but most brutal way to deep clone an object is to use JSON.parse(JSON.stringify(obj))
. But it has some unexpected behavior if the object to be converted contains cyclic reference.
const obj = {
name: "loopObj"
};
const loopObj = {
obj
};
obj.loopObj = loopObj;
function deepClone(obj) {
return JSON.parse(JSON.stringify(obj));
}
deepClone(obj)
/**
VM44:9 Uncaught TypeError: Converting circular structure to JSON
--> starting at object with constructor 'Object'
| property 'loopObj' -> object with constructor 'Object'
--- property 'obj' closes the circle
at JSON.stringify (<anonymous>)
at deepClone (<anonymous>:9:26)
at <anonymous>:11:13
*/
If there is cyclic reference in an object, it would throw exception when stringifying.
Rule 8
Any property with symbol as its key will be ignored even if replacer is defined to handle symbol.
JSON.stringify({ [Symbol.for("json")]: "stringify" }, function(k, v) {
if (typeof k === "symbol") {
return v;
}
})
// undefined
JSON.stringify() can take three parameters. Normally we don't use the second and third parameter too often. The second parameter is to define a replacer which is to loop through all key value pairs and do corresponding operation on them. It's similar to what map()
does. The third parameter is to tell what to fill in the space between different key value pair.
The replacer can be a function or an array. If it's a function, it can be used to overwrite lots of rules above.
const data = {
a: "aaa",
b: undefined,
c: Symbol("dd"),
fn: function() {
return true;
}
};
// when not using replacer
JSON.stringify(data);
// "{"a":"aaa"}"
// when using replacer
JSON.stringify(data, (key, value) => {
switch (true) {
case typeof value === "undefined":
return "undefined";
case typeof value === "symbol":
return value.toString();
case typeof value === "function":
return value.toString();
default:
break;
}
return value;
})
// "{"a":"aaa","b":"undefined","c":"Symbol(dd)","fn":"function() {\n return true;\n }"}"
If replacer is an array, it contains a list of properties which should be kept when stingifying, the rest should be deleted.
const jsonObj = {
name: "JSON.stringify",
params: "obj,replacer,space"
};
JSON.stringify(jsonObj, ["params"]);
// "{"params":"obj,replacer,space"}"
Hope you have a better understanding of JSON.stringify() now.
Reference: https://github.com/NieZhuZhu/Blog/issues/1