4
Author: BlackLivesMatter
Translator: Frontend Xiaozhi
Source: devinduct
There are dreams, dry goods, WeChat search [Da Qian World] Pay attention to this Shuawanzhi who is still doing dishes in the early morning.
This article GitHub https://github.com/qq449245884/xiaozhi has been included, the first-line interview complete test site, information and my series of articles.

JSON.stringify is a method we often use. Its main function is to convert JavaScript values and objects into strings. like:

JSON.stringify({ foo: "bar" });
// => '{"foo":"bar"}'

JSON.stringify(123);
// => '123'

But JS has problems in many places, and this function is no exception. We might imagine a function called "stringify" always returns a string... but it doesn't!

For example, if you try to stringify undefined , it returns undefined instead of a string.

JSON.stringify(undefined);
// => undefined

Next, I will talk about it in two parts:

  • List JSON.stringify does not return a string
  • How will we avoid these pitfalls

When does JSON.stringify not return a string?

undefined , any function, and the symbol will be ignored during serialization (when it appears in the property value of a non-array object) or converted to null (when it appears in an array). When the function, undefined is converted separately, it will return undefined .

Executing this method on objects that contain circular references (referring to each other to form an infinite loop) will throw an error

I think JSON.stringify can return something other than a string is quite surprised. But in 6 cases, it can return undefined :

  1. Attempting to serialize undefined at the top level undefined .
JSON.stringify(undefined);
// => undefined
  1. Trying to serialize the function will also return undefined . This is true for regular functions, arrow functions, asynchronous functions, and generator functions.
JSON.stringify(function foo() {});
// => undefined

JSON.stringify(() => {});
// => undefined

function bar() {}
bar.someProperty = 123;
JSON.stringify(bar);
// => undefined
  1. Trying to serialize the symbol will also return undefined .
JSON.stringify(Symbol("computers were a mistake"));
// => undefined
  1. In the browser, trying to serialize the obsolete document.all will also return undefined .
// => undefined

This only affects the browser, because document.all is not available in other environments, such as Node.

  1. Objects with the toJSON will be run instead of trying to serialize them normally. But if toJSON returns one of the above values, trying to serialize at the top level will cause JSON.stringify to return undefined .
JSON.stringify({ toJSON: () => undefined });
// => undefined

JSON.stringify({ ignored: true, toJSON: () => undefined });
// => undefined

JSON.stringify({ toJSON: () => Symbol("heya") });
// => undefined
  1. You can pass the second parameter, called "replacer", which can change the logic of serialization. If this function returns one of the above values for the top level, JSON.stringify will return undefined .
JSON.stringify({ ignored: true }, () => undefined);
// => undefined

JSON.stringify(["ignored"], () => Symbol("hello"));
// => undefined

It should be noted that many of these things actually only affect the serialization of the top level. For example, JSON.stringify({foo: undefined}) returns the string "{}" , which is not surprising.

I also want to mention that the type definition of TypeScript is incorrect here. For example, the following code type verification can pass:

const result: string = JSON.stringify(undefined);

In Part 2, we will discuss how to update the TypeScript definition to ensure its correctness.

JSON.stringify may also encounter a problem, causing it to throw an error. Under normal circumstances, four situations will occur:

  1. Circular references will cause a type error to be thrown.
const b = { a };
a.b = b;

JSON.stringify(a);
// => TypeError: cyclic object value

Note that these error messages may be different in different browsers. For example, the error message of Firefox is different from that of Chrome.

  1. BigInts cannot JSON.stringify , these will also cause a TypeError.
JSON.stringify(12345678987654321n);
// => TypeError: BigInt value can't be serialized in JSON

JSON.stringify({ foo: 456n });
// => TypeError: BigInt value can't be serialized in JSON
  1. The object with the toJSON will be run. If these functions throw errors, it will bubble up to the caller.
const obj = {
  foo: "ignored",
  toJSON() {
    throw new Error("Oh no!");
  },
};

JSON.stringify(obj);
// => Error: Oh no!
  1. You can pass the second parameter, called replacer . If this function throws an error, it will bubble up.
JSON.stringify({}, () => {
  throw new Error("Uh oh!");
});
// => Error: Uh oh!

Now that we have seen that JSON.stringify does not return a string, let's take a look at how to avoid these problems.

How to avoid these problems

There is no general method on how to solve these deficiencies, so here are only some common situations.

Handling circular references

According to personal experience, JSON.stringify is most prone to errors when passing circular references. If this is a common problem for you, I recommend the json-stringify-safe package, which can handle this situation well.

const stringifySafe = require("json-stringify-safe");

const a = {};
const b = { a };
a.b = b;

JSON.stringify(a);
// => TypeError: cyclic object value

stringifySafe(a);
// => '{"b":{"a":"[Circular ~]"}}'

Encapsulation

You may want to encapsulate JSON.stringify with your own custom function. You can decide what you want it to do. Should the error come up? What should I do JSON.stringify returns undefined

For example, Signal Desktop has a , which always returns a string for debugging. like this

function reallyJsonStringify(value) {
  let result;
  try {
    result = JSON.stringify(value);
  } catch (_err) {
    // If there's any error, treat it like `undefined`.
    result = undefined;
  }

  if (typeof result === "string") {
    // It's a string, so we're good.
    return result;
  } else {
    // Convert it to a string.
    return Object.prototype.toString.call(value);
  }
}

A note about TypeScript types

If you are already using TypeScript, you may be surprised to find that TypeScript JSON.stringify is incorrect here. They actually look like this:

// Note: 这里面简化过
interface JSON {
  // ...
  stringify(value: any): string;
}

Unfortunately, this is a long-standing problem , not a perfect solution.

You can try to fix JSON.stringify , but each solution has certain disadvantages. I recommend using a custom type to define your own wrapper and. For example, the template of reallyJsonStringify

function reallyJsonStringify(value: unknown): string {
  // ...

Summarize

  • JSON.stringify sometimes returns undefined instead of a string
  • JSON.stringify sometimes throws an error
  • We can solve this problem by wrapping the function in different ways

Hope this article gives you a more comprehensive understanding JSON.stringify

I'm the wise man who encourages me to go home and set up a street stall after retirement. See you next time.


code is deployed, the possible bugs cannot be known in real time. In order to solve these bugs afterwards, a lot of time was spent on log debugging. By the way, I would like to recommend a useful BUG monitoring tool Fundebug .

Original: https://evanhahn.com/when-stringify-doesnt-return-a-string

comminicate

If you have dreams and dry goods, search for [Moving to the World] Follow this brushing wit who is still doing dishes in the early morning.

This article GitHub https://github.com/qq449245884/xiaozhi has been included, the first-line interview complete test site, information and my series of articles.


王大冶
68k 声望104.9k 粉丝