你如何JSON.stringify一个ES6地图
How do you JSON.stringify an ES6 Map?
我想开始使用 ES6 Map 而不是 JS 对象,但我被阻止了,因为我无法弄清楚如何JSON.stringify()
Map
。 我的键保证是字符串,我的值将始终列出。 我真的必须编写一个包装方法来序列化吗?
JSON.stringify
和JSON.parse
都支持第二个参数。 分别replacer
和reviver
。使用下面的替换器和齐磊,可以添加对本机Map对象的支持,包括深度嵌套的值
function replacer(key, value) {
if(value instanceof Map) {
return {
dataType: 'Map',
value: Array.from(value.entries()), // or with spread: value: [...value]
};
} else {
return value;
}
}
function reviver(key, value) {
if(typeof value === 'object' && value !== null) {
if (value.dataType === 'Map') {
return new Map(value.value);
}
}
return value;
}
用法:
const originalValue = new Map([['a', 1]]);
const str = JSON.stringify(originalValue, replacer);
const newValue = JSON.parse(str, reviver);
console.log(originalValue, newValue);
深度嵌套与数组、对象和贴图的组合
const originalValue = [
new Map([['a', {
b: {
c: new Map([['d', 'text']])
}
}]])
];
const str = JSON.stringify(originalValue, replacer);
const newValue = JSON.parse(str, reviver);
console.log(originalValue, newValue);
您不能直接字符串化Map
实例,因为它没有任何属性,但您可以将其转换为元组数组:
jsonText = JSON.stringify(Array.from(map.entries()));
反之,请使用
map = new Map(JSON.parse(jsonText));
。
地图的键可以是任何东西,包括对象。但 JSON 语法只允许字符串作为键。所以在一般情况下是不可能的。
我的键保证是字符串,我的值将始终是列表
在这种情况下,您可以使用普通对象。它将具有以下优点:
- 它将能够字符串化为 JSON。
- 它将在较旧的浏览器上运行。
- 它可能会更快。
虽然 ecmascript 还没有提供任何方法,但如果将Map
映射到 JavaScript 原语,仍然可以使用 JSON.stringify
来完成此操作。下面是我们将使用的示例Map
。
const map = new Map();
map.set('foo', 'bar');
map.set('baz', 'quz');
转到 JavaScript 对象
您可以使用以下帮助程序函数转换为 JavaScript 对象文本。
const mapToObj = m => {
return Array.from(m).reduce((obj, [key, value]) => {
obj[key] = value;
return obj;
}, {});
};
JSON.stringify(mapToObj(map)); // '{"foo":"bar","baz":"quz"}'
转到 JavaScript 对象数组
这个的辅助功能会更紧凑
const mapToAoO = m => {
return Array.from(m).map( ([k,v]) => {return {[k]:v}} );
};
JSON.stringify(mapToAoO(map)); // '[{"foo":"bar"},{"baz":"quz"}]'
转到数组数组
这甚至更容易,您可以使用
JSON.stringify( Array.from(map) ); // '[["foo","bar"],["baz","quz"]]'
使用点差 sytax 地图可以在一行中序列化:
JSON.stringify([...new Map()]);
并通过以下方式反序列化它:
let map = new Map(JSON.parse(map));
鉴于您的示例是一个简单的用例,其中键将是简单类型,我认为这是将 Map 字符串化的 JSON 的最简单方法。
JSON.stringify(Object.fromEntries(map));
我对 Map 底层数据结构的看法是作为键值对数组(作为数组本身)。所以,像这样:
const myMap = new Map([
["key1", "value1"],
["key2", "value2"],
["key3", "value3"]
]);
因为底层数据结构是我们在 Object.entries 中找到的,所以我们可以利用原生 JavaScript 方法在 Map 上Object.fromEntries()
,就像我们在数组上一样:
Object.fromEntries(myMap);
/*
{
key1: "value1",
key2: "value2",
key3: "value3"
}
*/
然后你剩下的就是使用JSON.stringify()来获得结果。
更好的解决方案
// somewhere...
class Klass extends Map {
toJSON() {
var object = { };
for (let [key, value] of this) object[key] = value;
return object;
}
}
// somewhere else...
import { Klass as Map } from '@core/utilities/ds/map'; // <--wherever "somewhere" is
var map = new Map();
map.set('a', 1);
map.set('b', { datum: true });
map.set('c', [ 1,2,3 ]);
map.set( 'd', new Map([ ['e', true] ]) );
var json = JSON.stringify(map, null, ''t');
console.log('>', json);
输出
> {
"a": 1,
"b": {
"datum": true
},
"c": [
1,
2,
3
],
"d": {
"e": true
}
}
希望这不像上面的答案那么令人畏缩。
字符串化Map
实例(对象作为键是可以的):
JSON.stringify([...map])
或
JSON.stringify(Array.from(map))
或
JSON.stringify(Array.from(map.entries()))
输出格式:
// [["key1","value1"],["key2","value2"]]
正确的往返序列化
只需复制它并使用它。或者使用 npm 包。
const serialize = (value) => JSON.stringify(value, stringifyReplacer);
const deserialize = (text) => JSON.parse(text, parseReviver);
// License: CC0
function stringifyReplacer(key, value) {
if (typeof value === "object" && value !== null) {
if (value instanceof Map) {
return {
_meta: { type: "map" },
value: Array.from(value.entries()),
};
} else if (value instanceof Set) { // bonus feature!
return {
_meta: { type: "set" },
value: Array.from(value.values()),
};
} else if ("_meta" in value) {
// Escape "_meta" properties
return {
...value,
_meta: {
type: "escaped-meta",
value: value["_meta"],
},
};
}
}
return value;
}
function parseReviver(key, value) {
if (typeof value === "object" && value !== null) {
if ("_meta" in value) {
if (value._meta.type === "map") {
return new Map(value.value);
} else if (value._meta.type === "set") {
return new Set(value.value);
} else if (value._meta.type === "escaped-meta") {
// Un-escape the "_meta" property
return {
...value,
_meta: value._meta.value,
};
} else {
console.warn("Unexpected meta", value._meta);
}
}
}
return value;
}
为什么这很难?
应该可以输入任何类型的数据,获取有效的 JSON,并从那里正确重建输入。
这意味着处理
- 将对象作为键的映射
new Map([ [{cat:1}, "value"] ])
.这意味着任何使用Object.fromEntries
的答案都可能是错误的。 - 具有嵌套地图的地图
new Map([ ["key", new Map([ ["nested key", "nested value"] ])] ])
.很多答案都回避了这一点,只回答问题,不处理除此之外的任何事情。 - 混合对象和地图
{"key": new Map([ ["nested key", "nested value"] ]) }
.
除了这些困难之外,序列化格式必须明确。否则,人们不能总是重建输入。顶部答案有一个失败的测试用例,请参见下文。
因此,我写了这个改进版本。它使用_meta
而不是dataType
,使冲突更加罕见,如果确实发生冲突,它实际上会毫不含糊地处理它。希望代码也足够简单,可以轻松扩展以处理其他容器。
但是,我的回答并没有尝试处理极其诅咒的情况,例如具有对象属性的映射。
我的答案的测试用例,演示了一些边缘情况
const originalValue = [
new Map([['a', {
b: {
_meta: { __meta: "cat" },
c: new Map([['d', 'text']])
}
}]]),
{ _meta: { type: "map" }}
];
console.log(originalValue);
let text = JSON.stringify(originalValue, stringifyReplacer);
console.log(text);
console.log(JSON.parse(text, parseReviver));
接受的答案不是往返
接受的答案真的很可爱。但是,当具有 dataType
属性的对象被传递给它时,它不会往返。在某些情况下使用可能会很危险,例如
-
JSON.stringify(data, acceptedAnswerReplacer)
并通过网络发送。 - 朴素网络处理程序会自动对它进行 JSON 解码。从现在开始,您无法安全地将接受的答案与解码数据一起使用,因为这样做会导致很多偷偷摸摸的问题。
此答案使用稍微复杂的方案来解决此类问题。
// Test case for the accepted answer
const originalValue = { dataType: "Map" };
const str = JSON.stringify(originalValue, replacer);
const newValue = JSON.parse(str, reviver);
console.log(originalValue, str, newValue);
// > Object { dataType: "Map" } , Map(0)
// Notice how the input was changed into something different
非常简单的方法。
const map = new Map();
map.set('Key1', "Value1");
map.set('Key2', "Value2");
console.log(Object.fromEntries(map));
'输出:-
{"Key1": "Value1","Key2": "Value2"}
即使您有嵌套的地图,下面的解决方案也有效
function stringifyMap(myMap) {
function selfIterator(map) {
return Array.from(map).reduce((acc, [key, value]) => {
if (value instanceof Map) {
acc[key] = selfIterator(value);
} else {
acc[key] = value;
}
return acc;
}, {})
}
const res = selfIterator(myMap)
return JSON.stringify(res);
}
<</div>
div class="answers"> 您不能在Map
或Set
上调用JSON.stringify
。
您将需要转换:
Map
成基元Object
,使用Object.fromEntries
或Set
成一个基元Array
,使用扩散运算符[...]
。在致电JSON.stringify
之前
地图
const
obj = { 'Key1': 'Value1', 'Key2': 'Value2' },
map = new Map(Object.entries(obj));
map.set('Key3', 'Value3'); // Add a new entry
// Does NOT show the key-value pairs
console.log('Map:', JSON.stringify(map));
// Shows the key-value pairs
console.log(JSON.stringify(Object.fromEntries(map), null, 2));
.as-console-wrapper { top: 0; max-height: 100% !important; }
设置
const
arr = ['Value1', 'Value2'],
set = new Set(arr);
set.add('Value3'); // Add a new item
// Does NOT show the values
console.log('Set:', JSON.stringify(set));
// Show the values
console.log(JSON.stringify([...set], null, 2));
.as-console-wrapper { top: 0; max-height: 100% !important; }
到JSON方法
如果要对类对象调用JSON.stringify
,则需要重写 toJSON
方法以返回实例数据。
class Cat {
constructor(options = {}) {
this.name = options.name ?? '';
this.age = options.age ?? 0;
}
toString() {
return `[Cat name="${this.name}", age="${this.age}"]`
}
toJSON() {
return { name: this.name, age: this.age };
}
static fromObject(obj) {
const { name, age } = obj ?? {};
return new Cat({ name, age });
}
}
/*
* JSON Set adds the missing methods:
* - toJSON
* - toString
*/
class JSONSet extends Set {
constructor(values) {
super(values)
}
toString() {
return super
.toString()
.replace(']', ` ${[...this].map(v => v.toString())
.join(', ')}]`);
}
toJSON() {
return [...this];
}
}
const cats = new JSONSet([
Cat.fromObject({ name: 'Furball', age: 2 }),
Cat.fromObject({ name: 'Artemis', age: 5 })
]);
console.log(cats.toString());
console.log(JSON.stringify(cats, null, 2));
.as-console-wrapper { top: 0; max-height: 100% !important; }
只想分享我的版本,只用于 Map 和 Set JSON.stringify。我正在对它们进行排序,对调试很有用...
function replacer(key, value) {
if (value instanceof Map) {
const reducer = (obj, mapKey) => {
obj[mapKey] = value.get(mapKey);
return obj;
};
return [...value.keys()].sort().reduce(reducer, {});
} else if (value instanceof Set) {
return [...value].sort();
}
return value;
}
用法:
const map = new Map();
const numbers= new Set()
numbers.add(3);
numbers.add(2);
numbers.add(3);
numbers.add(1);
const chars= new Set()
chars.add('b')
chars.add('a')
chars.add('a')
map.set("numbers",numbers)
map.set("chars",chars)
console.log(JSON.stringify(map, replacer, 2));
结果:
{
"chars": [
"a",
"b"
],
"numbers": [
1,
2,
3
]
}
以下方法会将 Map 转换为 JSON 字符串:
public static getJSONObj(): string {
return JSON.stringify(Object.fromEntries(map));
}
例:
const x = new Map();
x.set("SomeBool", true);
x.set("number1", 1);
x.set("anObj", { name: "joe", age: 22, isAlive: true });
const json = getJSONObj(x);
// Output:
// '{"SomeBool":true,"number1":1,"anObj":{"name":"joe","age":222,"isAlive":true}}'
我真的不知道为什么这里有这么多长长的敬畏者。这个简短的版本解决了我的问题:
const data = new Map()
data.set('visible', true)
data.set('child', new Map())
data.get('child').set('visible', false)
const str = JSON.stringify(data, (_, v) => v instanceof Map ? Object.fromEntries(v) : v)
// '{"visible":true,"child":{"visible":false}}'
const recovered = JSON.parse(str, (_, v) => typeof v === 'object' ? new Map(Object.entries(v)) : v)
// Map(2) { 'visible' => true, 'child' => Map(1) { 'visible' => false } }
尽管在某些情况下,如果您是地图的创建者,您将代码编写在单独的"src"文件中,并将副本保存为.txt文件,如果编写得足够简洁,可以很容易地读入、破译并添加到服务器端。
然后,新文件将被保存为.js并从服务器发回对它的引用。然后,一旦以 JS 的形式读回,该文件将完美地重建自身。美妙之处在于重建不需要黑客迭代或解析。
- 在一个表达式中加载并使用ES6模块
- 创建一个类,该类使用es6类语法将Function对象创建为实例
- 你如何JSON.stringify一个ES6地图
- 如何使用ES6将两个具有函数的对象合并为一个新对象
- ES6类:在另一个函数中包装方法实现
- 当使用Angular1+ES6时,控制器函数中未定义依赖注入,控制器是一个类
- 节点 ES6:类不是一个函数
- JavaScript es6 从另一个类调用静态函数
- 如何在单文件组件中扩展另一个 VueJS 组件?(ES6 vue-loader)
- 由 webpack 构建的 react(es6) 在浏览器中有一个未捕获的类型错误
- ES6 类在通过系统导入时不是一个函数
- ES6/Redux:返回一个函数来删除事件侦听器
- ES6 类扩展了另一个类并多次调用超级
- 使用 Sinon.js 测试一个函数是否调用 ES6 模块中的另一个函数
- 浅拷贝对象在 ES6/ES7 中省略一个或多个属性
- ES6 从另一个方法调用一个方法
- 静态方法在 ES6 类中未定义,在 reactjs 中有一个装饰器
- 是否有一个es6模块作用域等同于' window ' ?
- 如何实例化一个es6类,当它被webpack捆绑到一个函数中时
- 创建一个(ES6)承诺而不开始解决它