This article has participated in the “Digitalstar Project” and won a creative gift package to challenge the creative incentive money.

JsonStringify is a very frequently used API, but it has one feature that we need to be aware of in order to avoid pitfalls in our code. Let’s implement a simple version of the jsonStringify function

Json.stringify has six features

Features a

Booleans, numbers, and string wrapper objects are automatically converted to their original values during serialization

Now there is an object like this:

const obj = {
    bol: new Boolean(true),
    num: new Number(1),
    str: new String(1)}Copy the code

Use Typeof to detect the data types of obJ attributes

typeof obj.bol; // object
typeof obj.num; // object
typeof obj.str; // object
Copy the code

After serializing Stringify

JSON.stringify(obj); // {"bol":true,"num":1,"str":"1"}
Copy the code

It is then parsed and parsed for the data types of each property

const stringifyObj = JSON.parse(JSON.stringify(obj));
typeof stringifyObj.bol; // boolean
typeof stringifyObj.num; // number
typeof stringifyObj.str; // string
Copy the code

Features two

NaN, Infinity, -infinity, and NULL are all treated as null when serializing Stringify

const obj = {
    nan: NaN.infinity: Infinity.null: null};JSON.stringify(obj); // {"nan":null,"infinity":null,"null":null}
Copy the code

Features three

When an object is serialized, if it has a toJSON function, the value returned by this function is the result of the entire object being serialized

const obj = {
    nan: NaN.infinity: Infinity.null: null.toJSON() {
        return "Have toJSON function"; }};JSON.stringify(obj); // "Have toJSON function"
Copy the code

You can see that the serialized data only contains the return value of the toJSON function, ignoring the rest of the data

⚠️ : Date data will be properly serialized because the toJSON function is deployed on Date, which can be seen by printing date.prototype.tojson to the console

const obj = {
    date: new Date()};JSON.stringify(obj); / / {" date ":" the 2021-10-08 T11:43:31. 881 z}"
Copy the code

Features four

Undefined, function, and symbol behave differently

As an object key-value pair:

As the value:

const obj = {
    undefined: undefined.fn() {},
    symbol: Symbol()
};

JSON.stringify(obj); / / {}
Copy the code

As the key:

const fn = function () {};
const obj = {
    [undefined] :undefined,
    [fn]: function () {},Symbol()] :Symbol()
};

JSON.stringify(obj); / / {}
Copy the code

Undefined, function, and symbol as the key and value of an object are ignored during serialization

⚠️ : The original order of objects may be changed at this point, because the above three types of data are ignored during serialization

As an array value:

const arr = [undefined.function fn() {}, Symbol()];

JSON.stringify(arr); // [null,null,null]
Copy the code

Undefined, function, and symbol as values of arrays are all converted to NULL on serialization

Standing alone:


JSON.stringify(undefined); // undefined
JSON.stringify(function () {}); // undefined
JSON.stringify(Symbol()); // undefined
Copy the code

Undefined, function, and symbol are all converted to undefined on serialization when they stand alone

Features five

During serialization, only enumerable attributes are serialized; non-enumerable attributes are ignored

const obj = {
    name: "nordon".age: 18};// Change age to non-enumerable
Object.defineProperty(obj, "age", {
    enumerable: false});JSON.stringify(obj); // {"name":"nordon"}
Copy the code

⚠️ : This also changes the original order of objects

Features 6

Objects that are referred to in a loop will throw an exception on serialization

const obj = {
    name: "nordon".age: 18};const p = {
    name: 'wy',
    obj
}

obj.p = p

JSON.stringify(obj);
Copy the code

This causes the console to throw an exception:

Uncaught TypeError: Converting circular structure to JSON --> starting at object with constructor 'Object' | property 'p' -> object with constructor 'Object' --- property 'obj' closes the circle at JSON.stringify (<anonymous>)

Implement Stringify manually

Now that you know some of the features of json.stringify, you can start implementing a version of Kack based on these features

Before implementing it, we’ll use The Currization to encapsulate some utility functions for datatype validation:

const currying = (fn, ... outParams) = > {
    // Get the number of arguments required by the fn function
    const paramsLen = fn.length;

    return (. args) = > {
        // Collect all parameters
        let params = [...outParams, ...args];
        // If the parameter does not reach the parameter required by fn, continue to collect parameters
        if (params.length < paramsLen) {
            returncurrying(fn, ... params); }returnfn(... params); }; };/** * type: type - [object Array], [object Number] * source: data source */
const judgeType = (type, source) = > {
    return Object.prototype.toString.call(source) === type;
};

const isUndefined = currying(judgeType, "[object Undefined]");
const isSymbol = currying(judgeType, "[object Symbol]");
const isFunction = currying(judgeType, "[object Function]");
const isObject = currying(judgeType, "[object Object]");
const isNull = currying(judgeType, "[object Null]");
Copy the code

Let’s go straight to the code:

function jsonStringify(data) {
    let type = typeof data;

    if (isNull(data)) { 
// null directly returns the string 'null'
        return "null";
    } else if (data.toJSON && typeof data.toJSON === "function") {
// The toJSON function is configured to directly use the data returned by toJSON and ignore other data
        return jsonStringify(data.toJSON());
    } else if (Array.isArray(data)) {
        let result = [];
        // If it is an array, then each item in the array may be multiple
        data.forEach((item, index) = > {
            if (isUndefined(item) || isSymbol(item) || isFunction(item)) {
                result[index] = "null";
            } else{ result[index] = jsonStringify(item); }}); result ="[" + result + "]";

        return result.replace(/'/g.'"');
    } else if (isObject(data)) {
        // Process common objects
        let result = [];
        Object.keys(data).forEach((item, index) = > {
            if (typeofitem ! = ="symbol") {
                //key if symbol object is ignored
                if( data[item] ! = =undefined &&
                    typeofdata[item] ! = ="function" &&
                    typeofdata[item] ! = ="symbol"
                ) {
                    // if undefined, function, and symbol are attributes, ignore them
                    result.push(
                        '"' + item + '"' + ":"+ jsonStringify(data[item]) ); }}});return ("{" + result + "}").replace(/'/g.'"');
    } else if(type ! = ="object") {
        let result = data;

        // Data may be an underlying data type
        if (Number.isNaN(data) || data === Infinity) {
            //NaN and Infinity serialize return "null"
            result = "null";
        } else if (isUndefined(data) || isSymbol(data) || isFunction(data)) {
            // Since function serialization returns undefined, it is processed with undefined and symbol
            return undefined;
        } else if (type === "string") {
            result = '"' + data + '"';
        }

        return String(result); }}Copy the code

This simplified version of json.stringify is complete, although it lacks a lot of power, mainly to provide an idea. The core comments are already commented in the code and can be understood in conjunction with the code and the above features