Introduction to the

In the previous article, we learned what deep copy and shallow copy are, and also highlighted the following implementation methods related to shallow copy, or implementing a shallow copy yourself, etc. This article mainly introduces the deep copy. How to realize a simple JSON parse/JSON stringify. We often see people using it in normal development, or we use it ourselves when we don’t know much about deep copy.

JSON. Parse/JSON. Stringify is actually used to serialize the JSON format of the data. So why does it work with a simple deep copy? When we execute json. stringify, we serialize one of our objects to a string, and string is the basic type. When the string type is deserialized to an object via json. parse, it points to a new address because it was a primitive type before deserialization. After deserialization, it is an object and memory space is allocated. So the JSON. Parse/JSON. Stringify can realize a simple copy.

This article, first of all, realize a JSON. Stringify/JSON parse, the next article to achieve a relatively complete deep copy.

The instance

Let’s just go to the code and verify that

// Declare the original object
var old = {
  name: "old".attr: {
    age: 18.sex: "man"
  },
  title: ["M1"."P6"]};// Declare a new object, using son.parse/json.stringify to make a deep copy of the original object and assign values to the new object
var newValue = JSON.parse(JSON.stringify(old));
console.log(newValue); // {name: "old", attr: {age: 18, sex: "man"}, title: [['M1', 'P6']]}

// Change the name of the original object, the new object is not affected
old.name = "new";
console.log(newValue); // {name: "old", attr: {age: 18, sex: "man"}, title: [['M1', 'P6']]}
console.log(old); // {name: "new", attr: {age: 18, sex: "man"}, title: [['M1', 'P6']]}

// Change the reference type of the original object, and the new object is not affected
old.attr.age = 20;
console.log(newValue); // {name: "old", attr: {age: 18, sex: "man"}, title: [['M1', 'P6']]}
console.log(old); // {name: "new", attr: {age: 20, sex: "man"}, title: [['M1', 'P6']]}
Copy the code

In fact, is it ok to use this, and there is no problem ah, below we will uncover its veil a little bit.

limitations

Actually the JSON. Parse/JSON. Stringify still has many limitations, roughly as follows:

  • ignoreundefined
  • ignoreSymbol
  • Unable to serializefunction, will be ignored
  • Circular references cannot be resolved. An error is reported
  • Deep object conversion stacks up

Go straight to code validation

// Declare an object containing undefined, null, symbol, function
var oldObj = {
  name: "old".age: undefined.sex: Symbol("setter"),
  title: function() {},
  lastName: null
};
var newObj = JSON.parse(JSON.stringify(oldObj));
// We can see that undefined, symbol, function objects are ignored
console.log(newObj); // {name: "old", lastName: null}

var firstObj = {
  name: "firstObj"
};
firstObj.newKey = firstObj;
// Converting circular structure to JSON
var newFirstObj = JSON.parse(JSON.stringify(firstObj));
Copy the code

If the circular reference is incorrect, as shown in the figure below:

A method that generates objects of any depth or breadth.

function createData(deep, breadth) {
  var data = {};
  var temp = data;

  for (var i = 0; i < deep; i++) {
    temp = temp["data"] = {};
    for (var j = 0; j < breadth; j++) { temp[j] = j; }}return data;
}
Copy the code

Verify json. stringify recursively bursts the stack

JSON.stringify(createData(10000));
// VM97994:1 Uncaught RangeError: Maximum call stack size exceeded
Copy the code

Implement json.stringify yourself

  • First, a simple recursion
  • Distinguish betweenStringwithBoolean,Number,null
  • filterundefined,symbol,function
  • Circular reference warning

A simple recursion

Achieve the goal

  • Recursive calls
// Determine the data type
function getType(attr) {
  let type = Object.prototype.toString.call(attr);
  let newType = type.substr(8, type.length - 9);
  return newType;
}

// Convert function
function StringIfy(obj) {
  // Return the original String if the type is non-object or null
  if (typeofobj ! = ="object" || getType(obj) === null) {
    return String(obj);
  }
  // Declare an array
  let json = [];
  // Check whether the current parameter is an object or an array
  let arr = obj ? getType(obj) === "Array" : false;
  // Loop object properties
  for (let key in obj) {
    // Check whether the attribute is on the object itself
    if (obj.hasOwnProperty(key)) {
      // Get the attribute and determine the attribute value type
      let item = obj[key];
      // Call recursively for type object
      if (getType(obj) === "Object") {
        // consoarrle.log(item)
        item = StringIfy(item);
      }
      // Concatenate array fields
      json.push((arr ? '"' : '"' + key + '" : "') + String(item) + '"'); }}console.log(arr, String(json));
  // Convert the array field to a string
  return (arr ? "[" : "{") + String(json) + (arr ? "]" : "}");
}

// Test the code
StringIfy({ name: { name: "abc"}});// "{"name": "{"name": "abc"}"}"
StringIfy([1.2.4]); / / "[" 1", "2", "4"]"
Copy the code

In the above code our basic JSON serialization can serialize both the reference type and the base type.

Distinguish data types

The type I’m talking about is jjson. Stringify does not double quote numbers, Boolean, or null when serializing. Only keys in String or Object are double quoted.

  • Added a judge to the current attribute type
/ /... Omit code

// Convert function
function StringIfy(obj) {
  / /... Omit code
  let DelQuetoFlag = getType(item) === "Number" || getType(item) === "Boolean" || getType(item) === "Null"
  let IsQueto = DelQuetoFlag ? "" : '"';
  // Concatenate array fields
  json.push((arr ? IsQueto : '"' + key + '" : + (DelQuetoFlag ? ' ' : '"')) + String(item) + IsQueto);
  / /... Omit the generation
}

// Test the code
StringIfy({ name: { name: "abc"}});// "{"name": "{"name": "abc"}"}"
StringIfy([1.2.4]); / / "4-trichlorobenzene [1]"
Copy the code

Partial values are not processed

  • Filter by regular judgmentSymbol|Function|Undefined
  • Skip the current loop
    if (/Symbol|Function|Undefined/.test(getType(item))) {
        delete obj[key];
        continue;
    }
    let test = {
        name: 'name'.age: undefined.func: function () {},
        sym: Symbol('setter')};let newTest = StringIfy(test);
    console.log(newTest); // {"name": "name"}
Copy the code

Circular reference warning

  • Handle circular references, warn, and exit the loop
if (item === obj) {
  console.error(new TypeError("Converting circular structure to JSON"));
  return false;
}
Copy the code

Json. stingify Other parameters

Json. stringify It can be passed as three arguments.

Json.stringify (value[, replacer [, space]])

parameter

  • value: the value to be serialized into a JSON string.
  • Replacer (optional): If the parameter is afunctionDuring the serialization process, the value ofEach of these properties willThrough this functionTransformation and processing; If the parameter is aAn array of,Only containsIn this arrayThe property nameWill be serialized to the finalJSONString;
  • space: for indentationBlank stringforPretty print;

The replacer parameter is used to implement the replacer. The replacer parameter is used to implement the replacer.

Replacer instance

let oJson = {
  name: "oJson".age: 20.sex: "man".calss: "one"
};
JSON.stringify(oJson, ["sex"."name"]); // "{"sex":"man","name":"oJson"}"
// The form of the two parameters, key/value
JSON.stringify(oJson, function(key, value) {
  if (typeof value === "string") {
    return undefined;
  }
  return value;
}); // "{"age":20}"
Copy the code

implementation

// Convert function
function StringIfy(obj, replacer) {
  // Return the original String if the type is non-object or null
  if (typeofobj ! = ="object" || getType(obj) === null) {
    return String(obj);
  }
  // Declare an array
  let json = [];

  // Check whether the current parameter is an object or an array
  let arr = obj ? getType(obj) === "Array" : false;
  // Loop object properties
  for (let key in obj) {
    // Check whether the attribute is enumerable
    if (obj.hasOwnProperty(key)) {
      // console.log(key, item);

      // Get the attribute and determine the attribute value type
      let item = obj[key];
      / / <! ------- Modification starts -------! >
      let flag = true;
      // Handle the second argument
      if (replacer) {
        // Determine the type of the second argument
        switch (getType(replacer)) {
          case "Function":
            // If executed for a function
            flag = replacer(key, item);
            break;
          case "Array":
            // If it is an arrayflag = replacer.indexOf(key) ! = = -1;
            break; }}// Judge the return result
      if(! flag) {continue;
      }
      / / <! ------- Modification end -------! >
      if (item === obj) {
        console.error(new TypeError("Converting circular structure to JSON"));
        return false;
      }
      if (/Symbol|Function|Undefined/.test(getType(item))) {
        delete obj[key];
        continue;
      }
      // Call recursively for type object
      if (getType(item) === "Object") {
        // consoarrle.log(item)
        item = StringIfy(item);
      }
      let DelQuetoFlag = getType(item) === "Number" || getType(item) === "Boolean" || getType(item) === "Null"
      let IsQueto = DelQuetoFlag ? "" : '"';
      // Concatenate array fields
      json.push((arr ? IsQueto : '"'}}console.log(arr, String(json));
  // Convert the array field to a string
  return (arr ? "[" : "{") + String(json) + (arr ? "]" : "}");
}
Copy the code

The second parameter is used to set space. The third parameter is used to set space. The second parameter is used to set space.

  let test = {
    name: "name".age: undefined.func: function() {},
    sym: Symbol("setter"),
    age: 30.sex: 'man'
  };
  console.log(StringIfy(test, ['name'.'sex'])); // {"name": "name","sex": "man"}
  let newTest = StringIfy(test, function (key, value) {
    if (typeof value === 'string') {
      return undefined;
    }
    return value;
  });
  console.log(newTest); // {"age": "30}
Copy the code

This ends the StringIfy implementation.

Stringify summary

To implement json. stringify to implement json. stringify

// Determine the data type
function getType(attr) {
  let type = Object.prototype.toString.call(attr);
  let newType = type.substr(8, type.length - 9);
  return newType;
}
// Convert function
function StringIfy(obj, replacer) {
  // Return the original String if the type is non-object or null
  if (typeofobj ! = ="object" || getType(obj) === null) {
    return String(obj);
  }
  // Declare an array
  let json = [];

  // Check whether the current parameter is an object or an array
  let arr = obj ? getType(obj) === "Array" : false;
  // Loop object properties
  for (let key in obj) {
    // Check whether the attribute is enumerable
    if (obj.hasOwnProperty(key)) {
      // console.log(key, item);

      // Get the attribute and determine the attribute value type
      let item = obj[key];
      / / <! ------- Modification starts -------! >
      let flag = true;
      // Handle the second argument
      if (replacer) {
        // Determine the type of the second argument
        switch (getType(replacer)) {
          case "Function":
            // If executed for a function
            flag = replacer(key, item);
            break;
          case "Array":
            // If it is an arrayflag = replacer.indexOf(key) ! = = -1;
            break; }}// Judge the return result
      if(! flag) {continue;
      }
      / / <! ------- Modification end -------! >
      if (item === obj) {
        console.error(new TypeError("Converting circular structure to JSON"));
        return false;
      }
      if (/Symbol|Function|Undefined/.test(getType(item))) {
        delete obj[key];
        continue;
      }
      // Call recursively for type object
      if (getType(item) === "Object") {
        // consoarrle.log(item)
        item = StringIfy(item);
      }
      let DelQuetoFlag = getType(item) === "Number" || getType(item) === "Boolean" || getType(item) === "Null"
      let IsQueto = DelQuetoFlag ? "" : '"';
      // Concatenate array fields
      json.push((arr ? IsQueto : '"' + key + '" : + (DelQuetoFlag ? ' ' : '"')) + String(item) + IsQueto); }}console.log(arr, String(json));
  // Convert the array field to a string
  return (arr ? "[" : "{") + String(json) + (arr ? "]" : "}");
}
let testNull = StringIfy({ name: 111.value: null});
console.log('testNull: ', testNull);
let aa = StringIfy([1.2.4]);
let test = {
  name: "name".age: undefined.func: function() {},
  sym: Symbol("setter")};let newTest = StringIfy(test);
console.log(aa, newTest);
var firstObj = {
  name: "firstObj"
};
firstObj.newKey = firstObj;
StringIfy(firstObj);

Copy the code

JSON. Parse implementation

There are two ways to implement the parse effect, the eval implementation and the Function implementation, so let’s get right to it.

The eval implementation

function ParseJson(opt) {
  return eval("(" + opt + ")");
}

let aa = StringIfy([1.2.4]);
ParseJson(aa); / / [1, 2, 4]

let test = {
  name: "name".age: undefined.func: function() {},
  sym: Symbol("setter")};let newTest = StringIfy(test);
console.log(ParseJson(newTest)); // {name: "name"}
Copy the code

As you can see, the code above performs basic deserialization.

Avoid using eval unnecessarily. Eval () is a dangerous function that executes code that has the rights of the implementer. If the string code you run with eval() is manipulated by malicious parties (people with bad intentions), you may end up running malicious code on the user’s computer under the authority of your web page/extension.

The Function realization

function ParseJsonTwo(opt) {
  return new Function("return " + opt)();
}

let aa = StringIfy([1.2.4]);
ParseJson(aa); / / [1, 2, 4]

let test = {
  name: "name".age: undefined.func: function() {},
  sym: Symbol("setter")};let newTest = StringIfy(test);
console.log(ParseJson(newTest)); // {name: "name"}
Copy the code

Both eval and Function can be used to dynamically compile JS code, but they are not recommended in actual programming.

Handling XSS

It executes JS code and has XSS vulnerabilities.

If you just want to remember this method, you have to check the json parameter.

var rx_one = /^[\],:{}\s]*$/;
var rx_two = / \ \? :["\\\/bfnrt]|u[0-9a-fA-F]{4})/g;

var rx_three = /"[^"\\\n\r]*"|true|false|null|-? \d+(? :\.\d*)? (? :[eE][+\-]? \d+)? /g;

var rx_four = / (? (: : ^ | |,)? :\s*\[)+/g;
if (
	rx_one.test(
		json.replace(rx_two, "@").replace(rx_three, "]").replace(rx_four, "")); {var obj = ParseJson(json); // ParseJson(json) or ParseJsonTwo(json)
}
Copy the code

The Parse summary

In fact, eval and function are not recommended at all times because they are intrusive. If you are interested, you can check out the three implementations of json. parse. It involves recursive implementation, state machine implementation, and good talk.

conclusion

. This article mainly explained the JSON parse/JSON stringify is how to achieve deep copy, and deeply understand the JSON. Parse/JSON stringify on deep copy of implementation, there are how to accelerate the speed of the JSON serialization, could explain in another article. Finally, I simply implemented a ParseJson/StringIfy.

reference

Three ways to implement json. parse with handwritten JavaScript code