This article has participated in the “Digitalstar Project” and won a creative gift package to challenge the creative incentive money.
preface
Be in awe of the online environment. Any one of these points can cause an online breakdown and get you out of your annual bonus. For example, we use json.stringify, an extremely familiar but unfamiliar API.
After reading this article, you can learn:
- Learn about a sad story that nearly cost me my annual bonus O (╥﹏╥) O
- Learn 9 features and conversion rules for Json.stringify (key)
- Learn how to determine if an object has a circular reference (important)
- Write a json.stringify from scratch (emphasis)
- , etc.
Tell a sad story
Recently, a colleague of the team resigned, and I was responsible for the maintenance of a piece of business he was in charge of. As a result, I just took over, and the code was not yet covered, so I almost carried on p0. Let me take a moment to tell you the whole story.
At the beginning of sad
One day bighead was swimming in code when he was suddenly pulled into an online problem solving group, which was not without its buzz.
Product students are complaining: Online users cannot submit forms, which brings a lot of customer complaints. It may be p0 fault, hope to solve it as soon as possible.
Test students in wonder: this scene test and pre-release environment clearly tested, how the line is not good.
The interface is missing the value field, which causes an error.
Just no one says how to solve the problem!!
Just no one says how to solve the problem!!
Just no one says how to solve the problem!!
Do you have deja vu of this scene? O (╥﹏╥) O, no matter how the first priority is to solve the online problem, reduce the continuing impact, quickly turn out the handover code, began the process of investigation.
Question why
You have a dynamic form collection page where the user selects or fills in the information (fields can be submitted if they are not required), and the front end sends the data to the back end.
Direct error cause
If this parameter is not mandatory, the string object after json. stringify in the signInfo field lacks a value key. As a result, the back end parse cannot read the value correctly and reports an exception to the interface system.
// There is no value key in array string
{
signInfo: '[{"fieldId":539},{"fieldId":540},{"fieldId":546,"value":"10:30"}]'
}
// Enter the parameter data normally
{
signInfo: '[{" fieldId: "539," value ":" silver "}, {" fieldId: "540," value ":" 2021-03-01 "}, {" fieldId: "546," value ":" at 10:30}]'
}
Copy the code
How is abnormal data generated
// By default the data looks like this
let signInfo = [
{
fieldId: 539.value: undefined
},
{
fieldId: 540.value: undefined
},
{
fieldId: 546.value: undefined},]// After json.stringify, the value key is missing, so the back end cannot read the value and report an error
// The reason is that 'undefined', 'arbitrary function', and 'symbol' values are ignored in the serialization process when they appear in the attribute values of 'non-array objects'
console.log(JSON.stringify(signInfo))
// '[{"fieldId":539},{"fieldId":540},{"fieldId":546}]'
Copy the code
The solution
The cause of the problem is found, and the solution (here is only the front-end solution, of course, can also be solved by the back-end) is also very simple, the item whose value is undefined is converted to an empty string and then submitted.
Plan 1: open a new object processing
let signInfo = [
{
fieldId: 539.value: undefined
},
{
fieldId: 540.value: undefined
},
{
fieldId: 546.value: undefined},]let newSignInfo = signInfo.map((it) = > {
const value = typeof it.value === 'undefined' ? ' ' : it.value
return {
...it,
value
}
})
console.log(JSON.stringify(newSignInfo))
// '[{"fieldId":539,"value":""},{"fieldId":540,"value":""},{"fieldId":546,"value":""}]'
Copy the code
Option 2: Use the second parameter json. stringify, directly processing
The flaw of scheme 1 is that it requires a new object to operate, which is not elegant enough
let signInfo = [
{
fieldId: 539.value: undefined
},
{
fieldId: 540.value: undefined
},
{
fieldId: 546.value: undefined},]// If value is undefined, return an empty string
JSON.stringify(signInfo, (key, value) = > typeof value === 'undefined' ? ' ' : value)
// '[{"fieldId":539,"value":""},{"fieldId":540,"value":""},{"fieldId":546,"value":""}]'
Copy the code
The story of subsequent
This was a page that had been up for a while, so why did this suddenly happen and not before? After careful inquiry, it turned out that the product classmate proposed a small optimization point in the middle of the process, and the resigned friend felt that the point was too small and directly changed the code to go online, but did not think there was an online problem.
Behind this matter from the product to the test, to the back end, to the front end alone to do a complete review, the details will not expand to say.
Because from the discovery of problems to solve the problem faster, the impact of fewer users, has not reached the degree of accountability, my year-end bonus can be saved O (╥﹏╥) O.
Relearn JSON. Stringify
After this incident, I felt compelled to take a fresh look at the json.stringify method, thoroughly understand the conversion rules, and try to implement a json.stringify by hand
If you have encountered the same problem as me, welcome to study again, there will be a different harvest oh!
Learn through JSON. Stringify
The json.stringify () method converts a JavaScript object or value to a JSON string, optionally replacing the value if a replacer function is specified, or optionally containing only the properties specified by the array if the specified replacer is an array.
The following information is from MDN
grammar
JSON.stringify(value[, replacer [, space]])
Copy the code
parameter
-
value
The value to be serialized into a JSON string.
-
Replacer optional
- If the parameter is a function, each attribute of the serialized value is converted and processed by the function during serialization;
- If the parameter is an array, only the property names contained in the array will be serialized into the final JSON string;
- If this parameter is null or not provided, all attributes of the object are serialized.
-
Space optional
- Specifies a blank string for indentation, which is used to beautify the output (pretty-print).
- If the argument is a number, it represents how many Spaces there are; The upper limit is 10.
- If the value is less than 1, there are no Spaces.
- If the parameter is a string (the first 10 characters are taken when the string is longer than 10 characters), the string will be used as a space.
- If this parameter is not provided (or null), there will be no Spaces.
The return value
A JSON string representing the given value.Copy the code
abnormal
- An exception TypeError (” Cyclic Object Value “) is thrown when a cyclic reference is made
- When attempting to convert a value of type BigInt, TypeError is raised (“BigInt value can’t be serialized in JSON”).
The basic use
Pay attention to
- Json.stringify can convert objects or values (more commonly used to convert objects)
- You can specify
replacer
Selectively substituting for functions - You can also specify
replacer
For an array, you can convert the specified properties
Json.stringify: NDN: json.stringify: NDN: json.stringify
// 1. Convert objects
console.log(JSON.stringify({ name: 'Front End Bighead'.sex: 'boy' })) // '{"name":" head ","sex":"boy"}'
// 2. Convert normal values
console.log(JSON.stringify('Front End Bighead')) // "front-end bighead fish"
console.log(JSON.stringify(1)) / / "1"
console.log(JSON.stringify(true)) // "true"
console.log(JSON.stringify(null)) // "null"
// 3. Specify the replacer function
console.log(JSON.stringify({ name: 'Front End Bighead'.sex: 'boy'.age: 100 }, (key, value) = > {
return typeof value === 'number' ? undefined : value
}))
// '{"name":" head ","sex":"boy"}'
// 4. Specify arrays
console.log(JSON.stringify({ name: 'Front End Bighead'.sex: 'boy'.age: 100},'name' ]))
// '{"name":" head "}'
// 5. Specify space(beautify output)
console.log(JSON.stringify({ name: 'Front End Bighead'.sex: 'boy'.age: 100 }))
// '{"name":" head ","sex":"boy","age":100}'
console.log(JSON.stringify({ name: 'Front End Bighead'.sex: 'boy'.age: 100 }, null , 2))
/ * {" name ":" front-end bighead ", "sex" : "boy", "age" : 100} * /
Copy the code
9 features to keep in mind
I only used this method before, but I didn’t know his conversion rules in detail. There were as many as 9 of them.
Features a
undefined
,Arbitrary function
As well asSymbol value
, appeared in theNon-array object
Is ignored during serializationundefined
,Arbitrary function
As well asSymbol value
Appear in theAn array of
When will be converted tonull
.undefined
,Arbitrary function
As well asSymbol value
beSingle conversion
, undefined is returned
// 1. The presence of these three values in an object is ignored
console.log(JSON.stringify({
name: 'Front End Bighead'.sex: 'boy'.// The function is ignored
showName () {
console.log('Front End Bighead')},// undefined is ignored
age: undefined.// Symbol is ignored
symbolName: Symbol('Front End Bighead')}))// '{"name":" head ","sex":"boy"}'
// 2. There are three values in the array that will be converted to NULL
console.log(JSON.stringify([
'Front End Bighead'.'boy'.// The function is converted to NULL
function showName () {
console.log('Front End Bighead')},//undefined is converted to null
undefined.//Symbol is converted to NULL
Symbol('Front End Bighead')))// '["前端胖头鱼","boy",null,null,null]'
// 3. A single conversion returns undefined
console.log(JSON.stringify(
function showName () {
console.log('Front End Bighead')}))// undefined
console.log(JSON.stringify(undefined)) // undefined
console.log(JSON.stringify(Symbol('Front End Bighead'))) // undefined
Copy the code
Features two
Booleans, numbers, and string wrapper objects are automatically converted to their original values during serialization.
console.log(JSON.stringify([new Number(1), new String("Front end bighead fish"), new Boolean(false)))// '[1," bighead ",false]'
Copy the code
Features three
All properties with symbol as the property key are completely ignored, even if they are mandatory in the replacer parameter.
console.log(JSON.stringify({
name: Symbol('Front End Bighead'),}))/ / '{}'
console.log(JSON.stringify({
[ Symbol('Front End Bighead') :'Front End Bighead',},(key, value) = > {
if (typeof key === 'symbol') {
return value
}
}))
// undefined
Copy the code
Features four
Values and nulls in NaN and Infinity formats are treated as null.
console.log(JSON.stringify({
age: NaN.age2: Infinity.name: null
}))
// '{"age":null,"age2":null,"name":null}'
Copy the code
Features five
Convert values if there is a toJSON() method, which defines what values will be serialized.
const toJSONObj = {
name: 'Front End Bighead',
toJSON () {
return 'JSON.stringify'}}console.log(JSON.stringify(toJSONObj))
// "JSON.stringify"
Copy the code
Features 6
Date the Date is converted to a string by calling toJSON() (same as date.toisostring ()), so it is treated as a string.
const d = new Date(a)console.log(d.toJSON()) T14: / / 2021-10-05 01:23. 932 z
console.log(JSON.stringify(d)) / / "the 2021-10-05 T14:01:23. 932 z"
Copy the code
Features seven
Executing this method on objects that contain circular references (objects that refer to each other in an infinite loop) throws an error.
let cyclicObj = {
name: 'Front End Bighead',
}
cyclicObj.obj = cyclicObj
console.log(JSON.stringify(cyclicObj))
// Converting circular structure to JSON
Copy the code
Features eight
Other types of objects, including Map/Set/WeakMap/WeakSet, serialize only enumerable properties
let enumerableObj = {}
Object.defineProperties(enumerableObj, {
name: {
value: 'Front End Bighead'.enumerable: true
},
sex: {
value: 'boy'.enumerable: false}})console.log(JSON.stringify(enumerableObj))
// '{"name":" head "}'
Copy the code
Features nine
Error thrown when attempting to convert a value of type BigInt
const alsoHuge = BigInt(9007199254740991)
console.log(JSON.stringify(alsoHuge))
// TypeError: Do not know how to serialize a BigInt
Copy the code
Write a json.stringify by hand
Finally relearning the many features of json.stringify! Let’s write a simple version based on these features (no replacer function and space).
The source code to achieve
const jsonstringify = (data) = > {
// Check whether an object has a circular reference
const isCyclic = (obj) = > {
// Use the Set data type to store detected objects
let stackSet = new Set(a)let detected = false
const detect = (obj) = > {
// If it is not an object type, you can skip it
if (obj && typeofobj ! ='object') {
return
}
// A circular reference exists when the object to be checked already exists in stackSet
if (stackSet.has(obj)) {
return detected = true
}
// Save the current obj as stackSet
stackSet.add(obj)
for (let key in obj) {
// Check the attributes under obj one by one
if (obj.hasOwnProperty(key)) {
detect(obj[key])
}
}
// After horizontal detection is complete, delete the current object to prevent misjudgment
} let obj4 = {obj1: tempObj, obj2: tempObj} */ let obj4 = {obj1: tempObj, obj2: tempObj
stackSet.delete(obj)
}
detect(obj)
return detected
}
//
Executing this method on objects that contain circular references (objects that refer to each other in an infinite loop) throws an error.
if (isCyclic(data)) {
throw new TypeError('Converting circular structure to JSON')}//
// An error is thrown when attempting to convert a BigInt value
if (typeof data === 'bigint') {
throw new TypeError('Do not know how to serialize a BigInt')}const type = typeof data
const commonKeys1 = ['undefined'.'function'.'symbol']
const getType = (s) = > {
return Object.prototype.toString.call(s).replace(/\[object (.*?)\]/.'$1').toLowerCase()
}
/ / the object
if(type ! = ='object' || data === null) {
let result = data
//
// NaN and Infinity values and null are treated as null.
if ([NaN.Infinity.null].includes(data)) {
result = 'null'
//
// undefined, arbitrary functions, and symbol values are converted separately to return undefined
} else if (commonKeys1.includes(type)) {
// Get undefined directly instead of a string 'undefined'
return undefined
} else if (type === 'string') {
result = '"' + data + '"'
}
return String(result)
} else if (type === 'object') {
//
If there is a toJSON() method, what value does that method define to be serialized
//
// Date the Date calls toJSON() to convert it to a string (same as date.toisostring ()), so it is treated as a string.
if (typeof data.toJSON === 'function') {
return jsonstringify(data.toJSON())
} else if (Array.isArray(data)) {
let result = data.map((it) = > {
//
// 'undefined', 'arbitrary functions', and' symbol 'values are converted to' null 'when appearing in' arrays'
return commonKeys1.includes(typeof it) ? 'null' : jsonstringify(it)
})
return ` [${result}] `.replace(/'/g.'"')}else {
//
// Booleans, numbers, and strings are automatically converted to their original values during serialization.
if (['boolean'.'number'].includes(getType(data))) {
return String(data)
} else if (getType(data) === 'string') {
return '"' + data + '"'
} else {
let result = []
/ / features eight
// Other types of objects, including Map/Set/WeakMap/WeakSet, serialize only enumerable properties
Object.keys(data).forEach((key) = > {
//
// All properties with symbol as the property key are completely ignored, even if they are mandatory in the replacer argument.
if (typeofkey ! = ='symbol') {
const value = data[key]
/ / feature
// 'undefined', 'arbitrary function', and 'symbol' values are ignored in the serialization process when they appear in attribute values of 'non-array objects'
if(! commonKeys1.includes(typeof value)) {
result.push(`"${key}":${jsonstringify(value)}`)}}})return ` {${result}} `.replace(/ / '.'"')}}}}Copy the code
Test a
// 1. Test the basic output
console.log(jsonstringify(undefined)) // undefined
console.log(jsonstringify(() = >{}))// undefined
console.log(jsonstringify(Symbol('Front End Bighead'))) // undefined
console.log(jsonstringify((NaN))) // null
console.log(jsonstringify((Infinity))) // null
console.log(jsonstringify((null))) // null
console.log(jsonstringify({
name: 'Front End Bighead'.toJSON() {
return {
name: 'Bighead Head 2'.sex: 'boy'}}}))// {"name":" bighead 2","sex":"boy"}
// 2. Compare with the native json.stringify conversion
console.log(jsonstringify(null) = = =JSON.stringify(null));
// true
console.log(jsonstringify(undefined) = = =JSON.stringify(undefined));
// true
console.log(jsonstringify(false) = = =JSON.stringify(false));
// true
console.log(jsonstringify(NaN) = = =JSON.stringify(NaN));
// true
console.log(jsonstringify(Infinity) = = =JSON.stringify(Infinity));
// true
let str = "Front end bighead fish";
console.log(jsonstringify(str) === JSON.stringify(str));
// true
let reg = new RegExp("\w");
console.log(jsonstringify(reg) === JSON.stringify(reg));
// true
let date = new Date(a);console.log(jsonstringify(date) === JSON.stringify(date));
// true
let sym = Symbol('Front End Bighead');
console.log(jsonstringify(sym) === JSON.stringify(sym));
// true
let array = [1.2.3];
console.log(jsonstringify(array) === JSON.stringify(array));
// true
let obj = {
name: 'Front End Bighead'.age: 18.attr: ['coding'.123].date: new Date(),
uni: Symbol(2),
sayHi: function () {
console.log("hello world")},info: {
age: 16.intro: {
money: undefined.job: null}},pakingObj: {
boolean: new Boolean(false),
string: new String('Front End Bighead'),
number: new Number(1),}}console.log(jsonstringify(obj) === JSON.stringify(obj))
// true
console.log((jsonstringify(obj)))
// {" name ":" front-end variegated carp ", "age" : 18, "attr" : [" coding ", 123], "date" : "the 2021-10-06 T14:59:58. 306 z", "info" : {" age ": 16," type ": {" job" : null}}, "PakingObj" : {" Boolean ": false," string ":" front-end bighead ", "number" : 1}}
console.log(JSON.stringify(obj))
// {" name ":" front-end variegated carp ", "age" : 18, "attr" : [" coding ", 123], "date" : "the 2021-10-06 T14:59:58. 306 z", "info" : {" age ": 16," type ": {" job" : null}}, "PakingObj" : {" Boolean ": false," string ":" front-end bighead ", "number" : 1}}
// 3. Test traversable objects
let enumerableObj = {}
Object.defineProperties(enumerableObj, {
name: {
value: 'Front End Bighead'.enumerable: true
},
sex: {
value: 'boy'.enumerable: false}})console.log(jsonstringify(enumerableObj))
// {"name":" bighead "}
// 4. Test loop references and Bigint
let obj1 = { a: 'aa' }
let obj2 = { name: 'Front End Bighead'.a: obj1, b: obj1 }
obj2.obj = obj2
console.log(jsonstringify(obj2))
// TypeError: Converting circular structure to JSON
console.log(jsonStringify(BigInt(1)))
// TypeError: Do not know how to serialize a BigInt
Copy the code
As you can see from the above tests, jsonStringify and json.stringify perform basically the same.
At the end
Because of a BUG, I relearned json.stringify and realized that it still has so many features that I didn’t notice. The front end entertainment industry is too deep. I hope everyone is treated with gentleness, less bugs and more care. Good night