January 17, 2020 Updated this article code Demo: github.com/stelalae/js… , YARN start:debug View the console output.

JSON (JavaScript Object Notation) is a lightweight, language-independent data interchange format. At present, it is widely used in front and back end data interaction. In JavaScript application everywhere, flexibility, scalability, readability is also the strongest! The corresponding json. parse and json. stringify can be seen as object serialization and deserialization, converting objects to and from strings.

Serialization and deserialization

The emergence of the Internet brings the demand of communication between machines, and the two sides of the Internet communication need to adopt the agreed protocol, serialization and deserialization are part of the communication protocol. Communication protocols often adopt layered models with different functional definitions and granularity of each layer. For example, TCP/IP protocol is a four-layer protocol, while OSI model is a seven-layer protocol model. The main function of the Presentation Layer in the OSI 7-layer protocol model is to convert an application-layer object into a sequence of binary strings, or vice versa — serialization and deserialization. Generally speaking, the TCP/IP application layer corresponds to the OSI seven-layer application layer, the presentation layer and the session layer, so the serialization protocol is part of the TCP/IP application layer. The serialization protocol in this article is mainly based on the OSI seven-layer protocol model.

  • Serialization: The process of converting data structures or objects into binary strings.
  • Deserialization: The process of converting binary strings generated during serialization into data structures or objects.

Simply put, serialization is the process of converting an object into a byte stream, while deserialization is the process of converting a byte stream back into an object. The relationship between the two is as follows:

Data structures, objects, and binary strings are represented differently in different computer languages. For example, Java/JavaScript uses objects, which come from instantiation of classes. C, on the other hand, uses structs to represent data deconstruction, or reading data in memory based on the offset of Pointers. C++ is either Java or C, because C++ enhances the concept of class more than C.

From the perspective of the development history of computer languages, serialization protocols are designed from the following common protocols in terms of versatility, robustness, readability, scalability, security and performance:

  • Early protocols: COM, CORBA.
  • XML&SOAP: XML (Extensible Markup Language) is essentially a description Language with self-describing properties. SOAP (Simple Object Access Protocol) is a structured message delivery protocol based on XML as a serialization and deserialization protocol. It is widely used in the early Stage of the Internet, and the corresponding solution is called Web Service.
  • JSON: Derived from JavaScript, it has caught the mobile wave and is now widely used in a variety of business scenarios.
  • Thrift: Lightweight RPC service framework of Facebook open source. It is widely used in large-data, distributed, cross-language, and cross-platform servers.
  • Protobuf: From Google, it is criticized for improving XML and JSON, with low space overhead, high parsing performance and high language support. Many companies regard Protobuf as the preferred solution for communication between back-end and object persistence. On terminals, IM services are also widely used.
  • Avro: A subproject of Apache Hadoop that provides two serialization formats: JSON and Binary. (Personally, it is an improved version of JSON in transmission)

Benchmark of the serialization protocol above please search and query by yourself. The selection also depends on the specific business scenario. Personal suggestions: JSON is suitable for ① front-end participation ② small projects, Protobuf is suitable for ① high performance ② T-level data.

Parse JSON with Stringify

Let’s start with the introduction of MDN: json.stringify (), json.parse.

  • JSON.stringify(value[, replacer [, space]]): Converts a JavaScript value (object or array) to a JSON string, optionally replacing the value if replacer is specified as a function, or optionally containing only the properties specified by the array if replacer is specified.
  • JSON.parse(text[, reviver]): Used to parse JSON strings and construct JavaScript values or objects described by strings. Provides optional reviver functions to perform transformations (operations) on the resulting object before returning.

You may have noticed that JSON stringify has one more paragraph than parse:

  • Convert values if there is a toJSON() method, which defines what values will be serialized.
  • Properties of non-array objects are not guaranteed to appear in a serialized string in a particular order.
  • Booleans, numbers, and string wrapper objects are automatically converted to their original values during serialization.
  • Undefined, arbitrary functions, and symbol values are ignored during serialization (when appearing in an attribute value of a non-array object) or converted to NULL (when appearing in an array). Function/undefined returns undefined when converted separately, such as json.stringify (function(){}) or json.stringify (undefined).
  • Executing this method on objects that contain circular references (objects that refer to each other in an infinite loop) throws an error.
  • All properties with symbol as the property key are completely ignored, even if they are mandatory in the replacer parameter.
  • Date the Date is converted to a string by calling toJSON() (same as date.toisostring ()), so it is treated as a string.
  • Values and nulls in NaN and Infinity formats are treated as null.
  • Other types of objects, including Map/Set/weakMap/weakSet, serialize only enumerable properties.

This description explains which data is preserved, converted, and ignored when json.stringify is serialized. So my front-end project will definitely block undefined and NULL in the interface request layer:

const body = JSON.stringify(params, (k, v) => {
    if(v ! = =null&& v ! = =undefined) {
      returnv; }});Copy the code

Undefined and NULL in response data after axios requests a response:

import axios from 'axios';
import { ResponseData } from '.. /defines';

// axios.defaults.timeout = 10000;

const parseJSON = (response: any) = > {
  // Serialize Object first, then conditional deserialization
  const dataString = JSON.stringify(response);
  const dataObj = JSON.parse(dataString, (k: any, v: any) => {
    if (v === null) {
      return undefined;
    }
    return v;
  });
  return dataObj;
};

const parseResponse = (response: any) = > {
  if (response.status >= 200 && response.status < 300) {
    return response.data;
  }
  return {};
};

export const request = async (options: any): Promise<ResponseData> => {
  try {
    const resp = await axios(options);
    const data = await parseResponse(resp);
    return parseJSON(data);
  } catch (err) {
    return Promise.resolve({
      code: 999.msg: 'Network timeout'}); }};Copy the code

Improved deconstruction assignment

Json. stringify, json. parse are redundant and wasteful of performance. Now let’s do the talking in code.

// types.ts
export class OrderBase {
  creatTime: string;
  payTime: string;
  // Other omissions
}

export class OrderDetail {
  orderNo: string;
  userNo: string;
  businessCode: string;
  imgList: string[];
  base: OrderBase;
}
Copy the code

Since TS is becoming more and more popular and used in medium and large projects, I will introduce TS here without prompting ts errors.

// order.ts
function test() {
  const orderinfo: OrderDetail = JSON.parse('{}');
  const { imgList, base } = orderinfo;

  let imgurl = [];
  if (imgList && imgList.length > 0) {
    imgurl = imgList.map(item= > (item += '? xxx'));
  }
  const { payTime } = base || {};

  let showpay = false;
  if (payTime && payTime.length > 0) {
    showpay = true;
  }
}
test();
Copy the code

ES6 writing is widely used in modern JavaScript projects, and if you are familiar with ES6 writing, especially destructing assignments, this code will definitely make you uncomfortable!

function test() {
  const orderinfo: OrderDetail = JSON.parse('{}');
  const { imgList = [], base: { payTime = ' ' } = {} } = orderinfo;
  const imgurl = imgList.map(item= > (item += '? xxx'));
  constshowpay = payTime ! = =' ';
}
test();
Copy the code

Yes, this is standard ES6 writing, where default values for destructuring assignments, nested destructions, and so on are often used, and nulls-valued attributes must be removed from the object by deserialization at sources such as parsing interface responses.

Top 10 Most common Mistakes in JavaScript projects.

I think the advent of deconstructed assignment and deconstructed defaults has taken at least 10% of the code out of the front end!! The advent of TS, along with the release of ES6, ES2017, AND ES2020, and the proper use of new features, has gradually freed us from low-level bugs.

No matter how well the interface specification is defined, don’t trust the interface data to do exactly what you want it to do. — The interface of the front and back end for daily wrangling

Refactoring deconstructs assignment

The above example briefly illustrates the benefits of deconstructing assignments and defaults, which is to simplify the code logic and reduce the number of bugs. But you might also realize that if you’re going to destruct a lot of things, don’t you have to write defaults? ! It seems so.

So ideally, once you set the default values for an object in one place, you can use the default values for other places like nested deconstruction, JSON serialization, deserialization, and so on! Start recording the transformation process.

TypeScript projects always start with type definitions, which define default values when objects are declared. Since TS has type inference, some attributes with default values are shown to remove type declarations. Note that since default values are added, only class can be used, and declaration files are not annotated, and interface cannot be used.

export class OrderBase {
  creatTime: string = ' ';
  payTime: string = ' ';
}

export class OrderDetail {
  orderNo: string = ' ';
  userNo = ' ';  // It is inferred from the default that userNo is of type string
  imgList: string[] = [];
  base: OrderBase;
}
Copy the code

UserNo is undefined, meaning that the default value in the type declaration is not in effect.

function test() {
  const orderinfo: OrderDetail = JSON.parse('{}');
  const { base: { payTime = '123' } = {}, userNo } = orderinfo;
  console.log(payTime, userNo); // Print: 123 undefined
}
test();
Copy the code

Because this is a TS project, the actual running code is compiled code, not written code.

// tsconfig.json "target": "es2017"
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
function test() {
    const orderinfo = JSON.parse('{}');
    const { base: { payTime = '123' } = {}, userNo } = orderinfo;
    console.log(payTime, userNo);
}
test();


// tsconfig.json "target": es5
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
function test() {
    var orderinfo = JSON.parse('{}');
    var _a = orderinfo.base, _b = (_a === void 0 ? {} : _a).payTime, payTime = _b === void 0 ? '123' : _b, userNo = orderinfo.userNo;
    console.log(payTime, userNo);
}
test();
Copy the code

When compiled, the code in Test does not seem to be in the declaration file, let alone use the default values defined. Think about why!

The reason is:

  1. Class is used as a type to declare variables in the same way as interface, and the corresponding code is annotated after compilation.
  2. A variable with a type declaration is added without changing the data type, rather than becoming an instance of the class.

The correct way to do this is to change the variable from a JSON object to a class instance. Once instantiated by a class, you can use all of the class’s features and custom functions.

Properties of backfill

const a = new OrderDetail();
console.log(a.userNo); // Print: ''
a.userNo = 'abc';
console.log(a.userNo); // Print: 'ABC'
Copy the code

It’s too broad!

Object.assign

const obj = {
  userNo: 'a1'.base: { creatTime: 'the 2020-01-13 18:32:58'}};const a = Object.assign(new OrderDetail(), obj);
console.log(a.userNo); // Print: 'a1'
a.userNo = 'abc';
console.log(a.userNo); // Print: 'ABC'
console.log(a.base.creatTime); // Print: '2020-01-13 18:32:58'
console.log(a.base.payTime); // Print: undefined
Copy the code

Instead of backfilling properties, you should be more comfortable with instantiation via assign. Also, assign can only be one deep copy, so don’t think about operations on the prototype chain.

immutable record

Record is a data structure that records only the registered members of a JS object, and supports default values.

A record is similar to a JS object, but enforces a specific set of allowed string keys, and has default values.

const { Record } = require('immutable')
const ABRecord = Record({ a: 1.b: 2 })
const myRecord = ABRecord({ b: 3.x: 10 })     // error in ts

myRecord.size / / 2
myRecord.get('a') / / 1
myRecord.get('b') / / 3
const myRecordWithoutB = myRecord.remove('b')   // remove, b will be reset to 2 instead of undefined
myRecordWithoutB.get('b') / / 2
myRecordWithoutB.size / / 2
myRecord.get('x') // undefined
Copy the code

Immutable record records only keys that are registered at initialization, and guarantees the existence of your key-value forever. Compared with Object. Assign, the data structure of the Record is more powerful, and its own method will provide brilliant possibilities for your data management design.

// Method 1: have a type declaration
type PersonProps = {name: string, age: number};
const defaultValues: PersonProps = {name: 'Aristotle'.age: 2400};
const PersonRecord = Record(defaultValues);
class Person extends PersonRecord<PersonProps> {
  getName(): string {
    return this.get('name')
  }

  setName(name: string): this {
    return this.set('name', name); }}// Method 2: No type declaration
class ABRecord extends Record({ a: 1.b: 2 }) {
  getAB() {
    return this.a + this.b; }}var myRecord = new ABRecord({b: 3})
myRecord.getAB() / / 4
Copy the code

The difference between the two approaches is whether or not TypeScript is integrated. Combined with Record documents, it will be found that Record is actually a factory model of an application. The factory is first registered with default values, and then some custom functions are internally bridged. (I have not studied the Record source code, if there is any wrong please point out)

Choosing Records vs plain JavaScript Objects

  • Runtime immutability: Runtime immutability, which is consistent with the immutable principle that data is read-only.
  • Value equality: Value equality, that is, Record provides equals to determine whether two objects are strictly equal.
  • API methods: Additional API methods such as getIn, Equals, hashCode, ToJS-deep, Toobject-shallow, ToJson-shallow, etc.
  • Default values: indicates the Default value. Provide default values for each key, immediately after remove.
  • Serialization: Serialization. It provides serialization and deserialization between TOJS-deep, Toobjt-shallow, ToJSON-shallow, and JSON objects.

So I’ve been using IMmutable in the React project, designing many optimizations for data management and Render.

Class instantiation of a data object

The purpose of refactoring assignment is to convert a JavaScript JSON Object into a Class Object. As the title says, “alternative” means that the original JSON string is interchanged with JavaScript values (objects or arrays). Turns it into a JSON string or JavaScript value (object or array) that interchanges with Class. Then extend the class, such as encapsulation, inheritance, overloading, and so on, to make JavaScript a stronger language!

In MVC model, compared with utils function, String/Number extension function and other methods, using class management is easier to reduce the internal coupling degree of the project, and more convenient to expand and maintain. Like gender:

export enum SexType {
  none = 0,
  man = 1,
  woman = 2,}export class UserProfile {
  userNo: string = ' ';
  userName: string = ' ';
  sex: SexType = 0;

  static sexMap = {
    [SexType.none]: 'secret',
    [SexType.man]: 'male',
    [SexType.woman]: 'woman'}; public get sexT(): string {return UserProfile.sexMap[this.sex] || 'secret';
  }
  
  // Other custom functions
}

const myinfo = Object.assign(new UserProfile(), {
  userNo: '1'.userName: 'test'.sex: 1});console.log(myinfo.sex, myinfo.sexT); // Print: 1 'male'
myinfo.userName = '1'; // ok-username is modifiable
Copy the code

But what about immutable record?


// index.ts - declaration file
import { Record } from 'immutable';

export enum SexType {
  none = 0,
  man = 1,
  woman = 2,
}

interface IRecordInfo {
  createTime: string; updateTime? : string; }const IRecordInfoDefault: IRecordInfo = {
  createTime: ' '.updateTime: ' '};export class RecordInfo extends Record(IRecordInfoDefault) {} // It is recommended to use class, even without extended attributes
// export const RecordInfo = Record(IRecordInfoDefault); // Variable mode is not recommended, otherwise RecordInfo cannot be declared as a type

interface IUserProfile extends IRecordInfo {
  userNo: string;
  userName: string;
  sex: SexType;
}

const UserProfileDefault: IUserProfile = {
  userNo: ' '.userName: ' '.sex: SexType.none, ... IRecordInfoDefault, };export class UserProfile extends Record(UserProfileDefault) {
  sexMap = {
    [SexType.none]: 'secret',
    [SexType.man]: 'male',
    [SexType.woman]: 'woman'}; public get sexT(): string {return this.sexMap[this.sex] || 'secret';
  }

  // Other custom functions
}

export interface ITsTest {
  id: string;
}

const ITsTestDefault: ITsTest = {
  id: ' '};export const TsTest = Record(ITsTestDefault);

const myinfo = new UserProfile({ userNo: '1'.userName: 'test'.sex: 1 });
console.log(myinfo.sex, myinfo.sexT); // Print: 1 'male'

myinfo.userName = '1'; // Error-username is read-only and cannot be modified
const myinfonew = myinfo.set('userName'.'1'); // ok - Returns a new immutable object
Copy the code

You should note that introducing immutable records can add a bit of code to type declarations, but I think it’s worth it! The reasons are as follows:

  • Does not affect the declaration type, readable.
  • Strictly control the data model at source to prevent irrelevant data.
  • Reduce coupling in design from JSON objects to Class objects.

React is a data-driven page model, in which MVC automatically updates V by changing M. So introducing immutable record improves M and reduces logic in V and C. Do you see any optimizations in your React code? !


// index.ts - business file
import React from 'react';

import { RecordInfo, UserProfile, TsTest } from '.. /types';

interface DivBaseProps {
  data: RecordInfo;
}

const DivBase = (props: DivBaseProps) = > {
  const { data } = props;
  console.log(data, data.hashCode()); // When declaring the type above, it is recommended to use class mode
  return (
    <div>
      <p>{data.createTime}</p>
      <p>{data.updateTime}</p>
    </div>
  );
};

interface HomeState {
  detail: UserProfile;
  extra: RecordInfo;
}

export default class Home extends React.PureComponent<any.HomeState> {
  constructor(props: any) {
    super(props);
    this.state = {
      detail: new UserProfile(), // ok
      extra: new RecordInfo(), // ok
      // detail: new RecordInfo(), // error - Missing data
      // extra: new UserProfile(), // ok - because UserProfile inherited from RecordInfo
      // extra: new TsTest(), // error - Missing data
    };
  }

  componentDidMount() {
    setTimeout((a)= > {
      this.setState({ detail: new UserProfile({ sex: 1})}); },1000);
  }

  render() {
    const { detail } = this.state;
    return <DivBase data={new RecordInfo(detail)} ></DivBase>; // ok
    // return 
      ; // error}}Copy the code

conclusion

This paper first introduces the basic use of JavaScript JSON serialization and deserialization, with ES6 deconstruction assignment and default value, optimize the processing method of null value judgment. We then use class instantiation to further deserialize the JSON object, while using class for high cohesion and low coupling code management.

Because TypeScript provides the foundation for compiling, JavaScript is now coded in a stronger way. Running time is still the same. Expect TypeScript to output WebAssembly!

Resources: 1. serialization and deserialization 2. serialization and deserialization 3. Explore how to serialize an Error 4.record using json.stringify ()