簡體   English   中英

你如何 JSON.stringify ES6 Map?

[英]How do you JSON.stringify an ES6 Map?

我想開始使用ES6 Map而不是 JS 對象,但我被阻止了,因為我無法弄清楚如何JSON.stringify() a Map 我的鍵保證是字符串,我的值將始終列出。 我真的必須編寫一個包裝器方法來序列化嗎?

JSON.stringifyJSON.parse支持第二個參數。 分別是replacerreviver 使用下面的替換器和恢復器,可以添加對本機 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.stingify來完成。 這是我們將使用的示例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"]]'

使用spread sytax Map 可以在一行中序列化:

JSON.stringify([...new Map()]);

並將其反序列化:

let map = new Map(JSON.parse(map));

鑒於您的示例是一個簡單的用例,其中鍵將是簡單類型,我認為這是 JSON 字符串化 Map 的最簡單方法。

JSON.stringify(Object.fromEntries(map));

我認為 Map 底層數據結構的方式是作為鍵值對的數組(作為數組本身)。 所以,像這樣:

const myMap = new Map([
     ["key1", "value1"],
     ["key2", "value2"],
     ["key3", "value3"]
]);

因為底層數據結構是我們在 Object.entries 中找到的,所以我們可以像在 Array 上一樣在 Map 上使用Object.fromEntries()的原生 JavaScript 方法:

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"]]

即使您有嵌套的地圖,以下解決方案也有效

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);
}

您不能在MapSet上調用JSON.stringify

您將需要轉換:

  • 使用Object.fromEntriesMap轉換為原語Object ,或
  • Set為原始Array ,使用擴展運算符[...]

…在調用JSON.stringify之前

Map

 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; }

toJSON方法

如果要在JSON.stringify上調用 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
  ]
}

盡管在某些情況下,如果您是地圖的創建者,您會將代碼編寫在單獨的“src”文件中,並將副本另存為 .txt 文件,如果編寫得足夠簡潔,則可以輕松讀入、破譯,並添加到服務器端。

然后,新文件將保存為 .js 並從服務器發回對它的引用。 一旦作為 JS 讀回,該文件就會完美地重建自己。 美妙之處在於重建不需要 hacky 迭代或解析。

很簡單的方法。

  const map = new Map();
  map.set('Key1', "Value1");
  map.set('Key2', "Value2");
  console.log(Object.fromEntries(map));

` 輸出:-

{"Key1": "Value1","Key2": "Value2"}

接受的答案真的很可愛。 但是,它不能處理帶有dataType屬性的 object 正確傳遞給 function 的邊緣情況。

const originalValue = { dataType: "Map" }; // Edge case input
const str = JSON.stringify(originalValue, replacer);
const newValue = JSON.parse(str, reviver);
console.log(originalValue, str, newValue); 
// bug: newValue ends up being a Map

改進型

因此,為了我的目的,我編寫了這個改進的版本。 它使用_meta而不是dataType來減少沖突,如果確實發生了沖突,它實際上會處理它。

// 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;
}

最后,一個小測試用例試圖盡可能地打破。

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));

我喜歡

public static map: Map<string, boolean> = new Map<string, boolean>();

// 然后創建一個方法

public static getJSONObj():any{
    const json = JSON.stringify(Object.fromEntries(map));
    return json;
}

// 並使用這個方法

我真的不知道為什么這里有那么多長的aweser。 這個簡短的版本解決了我的問題:

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 } }

按照此鏈接查看操作方法: https://2ality.com/2015/08/es6-map-json.html

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM