实现一个JSON.stringify(前篇)

544 阅读6分钟

来源

 developer.mozilla.org/zh-CN/docs/…

segmentfault.com/a/119000002…

在实现JSON.stringify之前,此篇主要是介绍JSON.stringify方法。

例1.1

 console.log({ name: 'jack' })
 console.log(['a', 'b', 'c'])
 console.log(JSON.stringify({ name: 'jack' }))  
 console.log(JSON.stringify(['a', 'b', 'c']))   


由例1.1,我们可以看到JSON.stringify()方法将上述的对象和数组转换为了一个JSON字符串。

语法 JSON.stringify(value[, replacer [, space]]) ([]里的代表非必要参数)

value : 值。

replacer:替换。

space : 空格。

下面,通过例子,具体介绍参数的作用。

JSON.stringify特性

例1.2

console.log(JSON.stringify({}))
console.log(JSON.stringify(true))
console.log(JSON.stringify("foo"))
console.log(JSON.stringify([1, "false", false]))
console.log(JSON.stringify({ x: 5 }))
console.log(JSON.stringify({ x: 5, y: 6 }))


特性1:布尔值、字符串、数字的包装对象在序列化过程中会自动转换成对应的原始值。

例1.3

console.log(JSON.stringify(
    { x: undefined, y: Object, z: Symbol("") }
))
console.log(JSON.stringify(
    [undefined, Object, Symbol("")]
))


特性2:undefined、任意函数以及Symbol值序列化时,在非数组对象的属性值中会被忽略掉,在数组中被转换成null。

console.log(JSON.stringify(
    { a: 1, x: undefined, y: Object, z: Symbol(""), b: 3 }
))


特性3:序列化时会忽略一些特殊的值,非数组对象的属性不能保证以特定的顺序出现在序列化后的字符串中。

console.log(JSON.stringify(
    function () { }
))
console.log(JSON.stringify(
    undefined
))


特性4:函数,undefined被单独转换时,返回undefined。

const obj = {
    name: 'loopObj'
};
const loopObj = {
    obj
};
obj.loopObj = loopObj;
console.log('obj-->', obj)
console.log('loopObj-->', loopObj)
console.log(JSON.stringify(obj))




特性5:包含循环引用的对象(对象之间相互引用,形成无限循环),执行时会抛出错误。

console.log(JSON.stringify(
    { [Symbol("foo")]: "foo" }
))
console.log(JSON.stringify(
    { [Symbol.for("foo")]: "foo" }, [Symbol.for("foo")]
))


特性6:所有以symbol为属性键的属性,都会被完全忽略掉。即便replacer参数强制指定包含了它们。

console.log(JSON.stringify(
    new Date()
))
console.log(
    new Date().toISOString()
)


特性7:Date对象内部部署了toJSON()将其转换为了string字符串(同Date.toISOString())。

console.log(JSON.stringify(
    NaN
))
console.log(JSON.stringify(
    null
))
console.log(JSON.stringify(
    Infinity
))


特性8:NaN,Infinity格式的数值以及null,都会被当作null。

console.log(JSON.stringify(
    Object.create(null,
        {
            x: { value: 'x', enumerable: false },
            y: { value: 'y', enumerable: true }
        })
))


特性9:其他类型的对象,包括Map/Set/WeakMap/WeakSet,仅会序列化可枚举的属性。

var obj = {
    foo: 'foo',
    toJSON: function () {
        return 'bar'
    }
}
console.log(JSON.stringify(
    obj
))
console.log(JSON.stringify(
    { x: obj }
))


特性10:被序列化的对象中如果有toJSON方法,会覆盖该对象默认的序列化,而是调用toJSON方法后的返回值会被序列化。


replacer参数

function replacer(key, value) {
    console.log('key -->', key)
    console.log('value -->', value)
    if (typeof value == 'string') {
        return undefined
    }
    return value;
}
var foo = { name: 'jack', age: 18, hobby: 'swimming' };
console.log(JSON.stringify(
    foo, replacer
))


特性1:replacer参数作为函数,有两个参数,键key和值value,都会被序列化。

特性2:

在开始时,key值传入一个空字符串,value是整个对象的键值对。

随后,每个对象或数组的属性和属性值会被key和value对应依次传入。

特性3:

返回undefined,该属性值会被忽略,返回函数和Symbol同理。

返回布尔值、字符串、数字,被作为属性值添加到JSON字符串中。

function replacer(key, value) {
    console.log('key -->', key)
    console.log('value -->', value)
    if (typeof value == 'string') {
        return undefined
    }
    return value;
}
var foo = {
    name: 'jack', age: 18, hobby: 'swimming',
    data:
        { friend: 'tom', age: 19, function() { } }
};
console.log(JSON.stringify(
    foo, replacer
))


特性4:

返回任何其他对象,该对象递归地序列化成JSON字符串,对每个属性调用replacer方法。

除非该对象是函数,不会被序列化成JSON字符串。

function replacer(key, value) {
    console.log('key -->', key)
    console.log('value -->', value)
    if (typeof value == 'string') {
        return undefined
    }
    return value;
}
var foo = ['a', 'b', 18, function () { }, undefined,Symbol]
console.log(JSON.stringify(
    foo, replacer
))


特性5:

key对应索引号,value对应元素值,返回undefined、函数、symbol都会被null取代。

所以不能用replacer方法,从数组中移除值。

var foo = { name: 'jack', age: 18, hobby: 'swimming' };
console.log(JSON.stringify(
    foo, ['name', 'age']
))


特性6:replacer参数可以是一个数组,数组的值代表将被序列化的属性名。


利用replacer特性,实现类似map函数

const data = {
    a: 2,
    b: 3,
    c: 4,
    d: 5
}
const fn = (key, value) => {
    if (value % 2 == 0) {
        return value / 2
    }
    return value
}
const objMap = (obj, fn) => {
    if (typeof fn !== 'function') {
        throw new TypeError(`${fn} is not a function!`)
    }
    return JSON.parse(JSON.stringify(obj, fn))
}
console.log(objMap(data, fn))


space参数

console.log(JSON.stringify(
    { a: 1, b: { b1: 2 }, c: { c1: { c2: 3 } } }
))
console.log(JSON.stringify(
    { a: 1, b: { b1: 2 }, c: { c1: { c2: 3 } } }, null, 2
))


特性1:参数为数字时,表示每一级别会比上一级别多缩进这个数字值的空格(最多为10个空格),制表符\t也可以使用来缩进

console.log(JSON.stringify(
    { a: 1, b: { b1: 2 }, c: { c1: { c2: 3 } } }
))
console.log(JSON.stringify(
    { a: 1, b: { b1: 2 }, c: { c1: { c2: 3 } } }, null, 'oo'
))


特性2:参数为字符串时,表示每一级别会比上一级别多缩进这个字符串(字符串最多前10个字符)


JSON.stringify用作JavaScript

\u2028是行分隔符,\u2029是段落分隔符。在JSON中不需要省略,但在JavaScript中需要被省略。

function jsFriendlyJSONStringify(s) {
    return JSON.stringify(s).
        replace(/\u2028/g, '\\u2028').
        replace(/\u2029/g, '\\u2029');
}
var s = {
    a: String.fromCharCode(0x2028),
    b: String.fromCharCode(0x2029)
};
console.log(JSON.stringify(s))
try {
    eval('(' + JSON.stringify(s) + ')');
} catch (e) {
    console.log(e); // "SyntaxError: unterminated string literal"
}
// No need for a catch
eval('(' + jsFriendlyJSONStringify(s) + ')');
// console.log in Firefox unescapes the Unicode if
//   logged to console, so we use alert
alert(jsFriendlyJSONStringify(s)); // {"a":"\u2028","b":"\u2029"}



JSON.stringify结合localStorage

var session = {
    'screens': [1,2],
    'state':true
}
localStorage.setItem('session',JSON.stringify(session));
var restored = JSON.parse(localStorage.getItem('session');

 

总结:

JSON.stringify特性

  • 布尔值、字符串、数字的包装对象会自动转换成对应的原始值。
  • undefined、任意函数以及Symbol值,在非数组对象的属性值中会被忽略掉,在数组中被转换成null。
  • 序列化时会忽略一些特殊的值,非数组对象的属性不能保证以特定的顺序出现在序列化后的字符串中。
  • 函数,undefined被单独转换时,返回undefined。
  • 包含循环引用的对象(对象之间相互引用,形成无限循环),执行时会抛出错误。
  • 所有以symbol为属性键的属性,都会被完全忽略掉。即便replacer参数强制指定包含了它们。
  • Date对象内部部署了toJSON()将其转换为了string字符串(同Date.toISOString())。
  • NaN,Infinity格式的数值以及null,都会被当作null。
  • 其他类型的对象,包括Map/Set/WeakMap/WeakSet,仅会序列化可枚举的属性。
  • 被序列化的对象中如果有toJSON方法,会覆盖该对象默认的序列化,而是调用toJSON方法后的返回值会被序列化。

replacer参数

  • replacer参数可以作为函数或者数组,有两个参数,键key和值value,都会被序列化。
  • 在开始时,key值传入一个空字符串,value是整个对象的键值对。随后,每个对象或数组的属性和属性值会被key和value对应依次传入。
  • 返回undefined,该属性值会被忽略,返回函数和Symbol同理。返回布尔值、字符串、数字,被作为属性值添加到JSON字符串中。
  • 返回任何其他对象,该对象递归地序列化成JSON字符串,对每个属性调用replacer方法。除非该对象是函数,不会被序列化成JSON字符串。
  • 值为数值时,key对应索引号,value对应元素值,返回undefined、函数、symbol都会被null取代。所以不能用replacer方法,从数组中移除值。
  • replacer参数可以是一个数组,数组的值代表将被序列化的属性名。

space参数

  • 参数可以为数字或字符串,表示每一级别会比上一级别多缩进这个数字值的空格或者这个字符串(最多为10个空格或字符串最多前10个字符)


相关链接

Symbol

Object.create()

eval()

String.fromCharCode()