JavaScript中的JSON用法

一、JSON.stringify()

JSON.stringify()方法将一个JavaScript对象或值转换为JSON字符串,如果指定了一个replacer 函数,则可以选择性地替换值,或者指定的replacer是数组,则可选择性地仅包含数组指定的属性。

JSON.stringify(value[, replacer [, space]]);
/*
 value:
     将要序列化成一个JSON字符串的值。
 replacer【可选】:
     如果该参数是一个函数,则在序列化过程中,被序列化的值的每个属性都会经过该函数的转换和处理;
     如果该参数是一个数组,则只有包含在这个数组中的属性名才会被序列化到最终的JSON字符串中;
     如果该参数为null或者未提供,则对象所有的属性都会被序列化。
 space【可选】:
     指定缩进用的空白字符串,用于美化输出(pretty-print);
     如果参数是个数字,它代表有多少的空格;上限为10。该值若小于1,则意味着没有空格;
     如果该参数为字符串(当字符串长度超过10个字母,取其前10个字母),该字符串将被作为空格;
     如果该参数没有提供(或者为null),将没有空格。
 返回值:
     一个表示给定值的JSON字符串。
*/

/*
 1、布尔值、数字、字符串的包装对象在序列化过程中会自动转换成对应的原始值。
 2、undefined、任意的函数以及symbol值,在序列化过程中会被忽略(出现在非数组对象的属性值中时)或者被转换成null(出现在数组中时)。
    函数、undefined被单独转换时,会返回undefined,如JSON.stringify(function(){}) or JSON.stringify(undefined)。
    所有以symbol为属性键的属性都会被完全忽略掉,即便replacer参数中强制指定包含了它们。
 3、其他类型的对象,包括Map/Set/WeakMap/WeakSet,仅会序列化可枚举的属性。
*/

JSON.stringify({}); // '{}'。
JSON.stringify(true); // 'true'。
JSON.stringify("foo"); // '"foo"'。
JSON.stringify([1, "false", false]); // '[1,"false",false]'。
JSON.stringify({ x: 5 }); // '{"x":5}'。

JSON.stringify({ x: 5, y: 6 });
// "{"x":5,"y":6}"。

JSON.stringify([new Number(1), new String("false"), new Boolean(false)]);
// '[1,"false",false]'。

JSON.stringify({ x: undefined, y: Object, z: Symbol("") });
// '{}'。

JSON.stringify([undefined, Object, Symbol("")]);
// '[null,null,null]'。

JSON.stringify({ [Symbol("foo")]: "foo" });
// '{}'。

JSON.stringify({ [Symbol.for("foo")]: "foo" }, [Symbol.for("foo")]);
// '{}'。

JSON.stringify({ [Symbol.for("foo")]: "foo" }, function (k, v) {
  if (typeof k === "symbol") {
    return "a symbol";
  }
});
// undefined。

const obj = {
  [Symbol.for("global")]: "global",
  normal: "ok",
  name: {
    age: 456,
  },
};
JSON.stringify(obj, (key, value) => {
  console.log("Replacer called with:", { key, value });
  return value;
});
/*
 Replacer called with: {
   key: '',
   value: { normal: 'ok', name: { age: 456 }, Symbol(global): 'global' }
 }
 Replacer called with: { key: 'normal', value: 'ok' }
 Replacer called with: { key: 'name', value: { age: 456 } }
 Replacer called with: { key: 'age', value: 456 }

 分析:
 1、JSON.stringify会对对象进行递归的深度优先遍历,
    每个键值对(包括根对象)都会调用一次replacer。
    注意:遍历只针对“字符串键”的可枚举自有属性(Symbol键、不可枚举属性、原型链属性会被忽略)。
 2、为什么key===""时,值能看到Symbol属性?
    当key===""时,replacer接收到的是原始对象引用,但后续序列化会过滤Symbol。
        key = ""(根对象)。
        value = obj(就是你传入的原始对象)。
        此时obj确实包含Symbol属性,所以console.log显示了它。
        但这只是“传入replacer的值”,不代表它会被序列化!
    注意:你无法专门处理key为空字符串的情况,因为JSON对象也可能包含空字符串的键。
 3、replacer遍历顺序是怎样的?
    replacer是在“进入节点时”被调用的,而不是“离开节点时”,
    也就是说,JSON.stringify的遍历逻辑是:前序调用replacer,后序构造JSON。
    具体执行流程如下:
        1. 开始处理根对象obj。
            立即调用replacer("", obj) → 所以根对象的日志最先出现。
            得到返回值(比如还是obj)。
        2. 然后遍历这个返回值的所有字符串键属性(normal, name)。
            对每个属性值:
                如果是对象(如{ age: 456 }),就递归进入该对象。
                    调用replacer("name", { age: 456 })。
                    然后遍历它的属性age。
                        调用replacer("age", 456)。
        3. 所有子节点处理完后,才把结果组装成JSON字符串。
    所以:
        replacer的调用顺序是:前序(pre-order)。
        JSON字符串的构造顺序是:后序(post-order)。
    ------------------------------------------------------------------------------------
    调用replacer	  前序遍历(Pre-order)	先访问父节点 → 再访问子节点 → 所以key: ""最先被打印。
    生成最终JSON	  后序遍历(Post-order)	先序列化子节点 → 再拼接父节点。
    ------------------------------------------------------------------------------------
    根据ECMAScript规范§24.5.2 JSON.stringify:
        对输入值value,先调用replacer("", value)得到newVal。
        如果newVal是对象,则对其每个字符串键的可枚举属性:
            获取属性值propValue。
            递归调用内部序列化过程(其中会调用replacer(key, propValue))。
    所以“根replacer”必须最先调用,才能确定后续遍历的对象是什么!
*/

// 不可枚举的属性默认会被忽略:
JSON.stringify(
  Object.create(null, {
    x: { value: "x", enumerable: false },
    y: { value: "y", enumerable: true },
  }),
);
// "{"y":"y"}"。

“toJSON”方法:

如果一个被序列化的对象拥有toJSON方法,那么该toJSON方法就会覆盖该对象默认的序列化行为:不是该对象被序列化,而是调用toJSON方法后的返回值会被序列化,例如:

var obj = {
  foo: "foo",
  toJSON: function () {
    return "bar";
  },
};
JSON.stringify(obj); // '"bar"'。
JSON.stringify({ x: obj }); // '{"x":"bar"}'。
// ----------------------------------------------------------------
const obj = {
  [Symbol.for("global")]: "global",
  normal: "ok",
  name: {
    age: 456,
  },
  toJSON: function() {
    return this[Symbol.for("global")];
  }
};
console.log(JSON.stringify(obj, null, 2)); // "global"。
// ----------------------------------------------------------------
const obj = {
  [Symbol.for("global")]: "global",
  normal: "ok",
  name: {
    age: 456,
  },
  toJSON: function() {
    return { [Symbol.for("global")] : this[Symbol.for("global")]};
  }
};
console.log(JSON.stringify(obj, null, 2)); // {}。


二、JSON.parse()

静态方法JSON.parse()用来解析JSON字符串,构造由字符串描述的JavaScript值或对象。可以提供一个可选的reviver函数,用于在返回结果对象之前对其进行转换。

JSON.parse(text);
JSON.parse(text, reviver);
/*
 text:
 要被解析成JavaScript值的字符串。
 reviver【可选】:
 如果是函数,则规定了最初由解析产生的每个值在返回前的转换方式。不可调用的值将被忽略。调用该函数时需要输入以下参数:
 key:
 与值相关的键。
 value:
 解析产生的值。
 context【可选】:
 上下文对象,用于保存与当前正在恢复的表达式相关的状态。每次调用reviver函数时,它都是一个新对象。只有在恢复原始值时才会传递该对象,而当value是对象或数组时则不会传递。
 它仅包含以下属性:
     source:
     代表此值的原始JSON字符串。
 返回值:
 与给定的JSON text相对应的Object、Array、string、number、boolean或者null值。
*/

JSON.parse(
  '{"p": 5}',
  (key, value) =>
    typeof value === "number"
      ? value * 2 // 为数值返回value * 2。
      : value, // 其他值返回原值。
);
// { p: 10 }。

JSON.parse('{"1": 1, "2": 2, "3": {"4": 4, "5": {"6": 6}}}', (key, value) => {
  console.log(key);
  return value;
});
// 1
// 2
// 4
// 6
// 5
// 3
// ""
/*
 与JSON.stringify()的replacer参数类似,
 对于数组和对象,reviver最后一次调用的是以空字符串作为key、以根对象作为value的根值。
*/

/*
  注意:
     1、你无法专门处理key为空字符串的情况,因为JSON对象也可能包含空字符串的键。
        在实现reviver时,需要非常精确地知道每个键需要进行哪种转换。
     2、reviver是在解析数值后运行的。
        因此,举例来说,JSON文本中的数字已经被转换为JavaScript数字,在此过程中可能会丢失精度。
        一种不损失精度地传输大型数字的方法是将其序列化为字符串,然后将其还原为BigInt,或其他适当的任意精度格式。
*/
const bigJSON = '{"gross_gdp": 12345678901234567890}';
const bigObj = JSON.parse(bigJSON, (key, value, context) => {
  if (key === "gross_gdp") {
    // 忽略value,它已经损失了精度。
    console.log(value); // 输出:12345678901234567000。
    console.log(BigInt(context.source)); // 输出:12345678901234567890n。

    return BigInt(context.source);
  }
  return value;
});

三、JSON函数搭配使用举例

在与JSON.stringify()的replacer配对时使用reviver:

// Map通常被序列化为没有属性的对象。
// 我们可以使用替换器来指定要序列化的条目。
const map = new Map([
  [1, "one"],
  [2, "two"],
  [3, "three"],
]);

const jsonText = JSON.stringify(map, (key, value) =>
  value instanceof Map ? Array.from(value.entries()) : value,
);

console.log(jsonText);
// [[1,"one"],[2,"two"],[3,"three"]]

const map2 = JSON.parse(jsonText, (key, value) =>
  Array.isArray(value) && value.every(Array.isArray) ? new Map(value) : value,
);

console.log(map2);
// Map { 1 => "one", 2 => "two", 3 => "three" }。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值