คุณ JSON.stringify ES6 Map ได้อย่างไร


113

ฉันต้องการเริ่มใช้ES6 Mapแทนออบเจ็กต์ JS แต่ฉันถูกระงับเพราะฉันไม่สามารถหาวิธี JSON.stringify () แผนที่ได้ คีย์ของฉันได้รับการรับรองว่าเป็นสตริงและค่าของฉันจะแสดงอยู่เสมอ ฉันต้องเขียนวิธีการห่อเพื่อทำให้เป็นอนุกรมหรือไม่?


1
บทความที่น่าสนใจในหัวข้อ2ality.com/2015/08/es6-map-json.html
David Chase

ฉันสามารถทำงานนี้ได้ ผลลัพธ์ที่ได้จะอยู่บน Plunkr ที่embed.plnkr.co/oNlQQBDyJUiIQlgWUPVP วิธีแก้ปัญหาใช้ JSON.stringify (obj, replacerFunction) ซึ่งตรวจสอบว่ามีการส่งผ่านวัตถุแผนที่หรือไม่และแปลงวัตถุแผนที่เป็นวัตถุ Javascript (ที่ JSON.stringify) จะแปลงเป็นสตริง
PatS

1
หากคีย์ของคุณได้รับการรับรองว่าเป็นสตริง (หรือตัวเลข) และอาร์เรย์ค่าของคุณคุณสามารถทำสิ่งต่างๆเช่น[...someMap.entries()].join(';'); สำหรับสิ่งที่ซับซ้อนมากขึ้นคุณสามารถลองสิ่งที่คล้ายกันโดยใช้สิ่งที่ต้องการ[...someMap.entries()].reduce((acc, cur) => acc + `${cur[0]}:${/* do something to stringify cur[1] */ }`, '')
user56reinstatemonica8

@Oriol จะเป็นอย่างไรหากเป็นไปได้ที่ชื่อคีย์จะเหมือนกับคุณสมบัติเริ่มต้น obj[key]อาจทำให้คุณได้รับสิ่งที่ไม่คาดคิด if (!obj[key]) obj[key] = newList; else obj[key].mergeWith(newList);พิจารณากรณีที่
Franklin Yu

คำตอบ:


80

ทั้งสองJSON.stringifyและJSON.parseสนับสนุนข้อโต้แย้งที่สอง replacerและreviverตามลำดับ ด้วยตัวเปลี่ยนและรีเวอร์ด้านล่างคุณสามารถเพิ่มการรองรับสำหรับอ็อบเจ็กต์แผนที่ดั้งเดิมรวมถึงค่าที่ซ้อนกันอย่างลึกซึ้ง

function replacer(key, value) {
  const originalObject = this[key];
  if(originalObject instanceof Map) {
    return {
      dataType: 'Map',
      value: Array.from(originalObject.entries()), // or with spread: value: [...originalObject]
    };
  } 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);

9
นี่เป็นการตอบสนองที่ดีที่สุดจนถึงตอนนี้
Manuel Vera Silvestre

1
คำตอบที่ดีที่สุดที่นี่แน่นอน
JavaRunner

เพียงทำเครื่องหมายว่าถูกต้อง แม้ว่าฉันจะไม่ชอบที่คุณต้อง "ทำให้ข้อมูลสกปรก" ในสายด้วยวิธีที่ไม่ได้มาตรฐานdataTypeแต่ฉันก็คิดวิธีที่สะอาดกว่านี้ไม่ได้ ขอบคุณ.
rynop

@rynop ใช่มันเป็นเหมือนโปรแกรมเล็ก ๆ ที่มีรูปแบบการจัดเก็บข้อมูลของตัวเองมากกว่าการใช้ฟังก์ชันดั้งเดิมล้วนๆ
Pawel

77

คุณไม่สามารถสตริงMapอินสแตนซ์ได้โดยตรงเนื่องจากไม่มีคุณสมบัติใด ๆ แต่คุณสามารถแปลงเป็นอาร์เรย์ของสิ่งต่อไปนี้

jsonText = JSON.stringify(Array.from(map.entries()));

สำหรับการย้อนกลับให้ใช้

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

6
สิ่งนี้ไม่ได้แปลงเป็นออบเจ็กต์ JSON แต่เป็น Array ของอาร์เรย์แทน ไม่ใช่สิ่งเดียวกัน. ดูคำตอบของ Evan Carroll ด้านล่างสำหรับคำตอบที่สมบูรณ์ยิ่งขึ้น
. Thiru

3
@SatThiru อาร์เรย์ของสิ่งทอคือการแสดงตามธรรมเนียมของMaps มันเข้ากันได้ดีกับตัวสร้างและตัววนซ้ำ นอกจากนี้ยังเป็นเพียงการนำเสนอแผนที่ที่เหมาะสมซึ่งมีคีย์ที่ไม่ใช่สตริงและอ็อบเจ็กต์จะไม่ทำงานที่นั่น
Bergi

Bergi โปรดทราบว่า OP กล่าวว่า "รับประกันกุญแจของฉันเป็นสตริง"
. Thiru

4
@SatThiru ในกรณีนั้นให้ใช้JSON.stringify(Object.fromEntries(map.entries()))และnew Map(Object.entries(JSON.parse(jsonText)))
Bergi

@Bergi Stringify ใช้ไม่ได้ถ้าkeyเป็นวัตถุเช่น"{"[object Object]":{"b":2}}"- ปุ่มวัตถุเป็นหนึ่งในคุณสมบัติหลักของ Maps
Drenai

66

คุณทำไม่ได้

กุญแจของแผนที่อาจเป็นอะไรก็ได้รวมทั้งวัตถุ แต่ไวยากรณ์ JSON อนุญาตให้ใช้สตริงเป็นคีย์เท่านั้น ดังนั้นจึงเป็นไปไม่ได้ในกรณีทั่วไป

คีย์ของฉันได้รับการประกันว่าเป็นสตริงและค่าของฉันจะเป็นรายการเสมอ

ในกรณีนี้คุณสามารถใช้วัตถุธรรมดา จะมีข้อดีเหล่านี้:

  • จะสามารถทำให้สตริงเป็น JSON ได้
  • มันจะทำงานบนเบราว์เซอร์รุ่นเก่า
  • มันอาจเร็วกว่านี้

33
สำหรับผู้ที่อยากรู้อยากเห็นในโครเมี่ยมล่าสุดแผนที่ใด ๆ ที่ต่อเนื่องกันเป็น "{}"
Capaj

ฉันได้อธิบายที่นี่ว่าฉันหมายถึงอะไรเมื่อฉันพูดว่า "คุณทำไม่ได้"
Oriol

8
"มันอาจจะเร็วกว่านี้" - คุณมีแหล่งที่มาหรือไม่? ฉันกำลังจินตนาการว่าแผนที่แฮชธรรมดา ๆ จะต้องเร็วกว่าวัตถุที่ระเบิดเต็มรูปแบบ แต่ฉันไม่มีข้อพิสูจน์ :)
Lilleman

1
@Xplouder hasOwnPropertyทดสอบที่ใช้มีราคาแพง หากไม่มีเช่นนั้น Firefox จะวนซ้ำวัตถุเร็วกว่าแผนที่มาก อย่างไรก็ตามแผนที่ยังเร็วกว่าบน Chrome jsperf.com/es6-map-vs-object-properties/95
Oriol

1
จริงอยู่ดูเหมือนว่า Firefox 45v จะวนซ้ำวัตถุเร็วกว่า Chrome + 49v อย่างไรก็ตามแผนที่ยังคงชนะเทียบกับวัตถุใน Chrome
Xplouder

17

แม้ว่า ecmascript จะยังไม่มีวิธีการใด ๆ แต่ก็ยังสามารถทำได้โดยใช้JSON.stingifyหากคุณแมปMapกับ JavaScript ดั้งเดิม นี่คือตัวอย่างที่Mapเราจะใช้

const map = new Map();
map.set('foo', 'bar');
map.set('baz', 'quz');

ไปที่วัตถุ JavaScript

คุณสามารถแปลงเป็น JavaScript Object literal ได้ด้วยฟังก์ชันตัวช่วยดังต่อไปนี้

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 Array of Objects

ฟังก์ชั่นตัวช่วยสำหรับตัวนี้จะกะทัดรัดยิ่งขึ้น

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

14

การใช้แผนที่sytax กระจายสามารถทำให้เป็นอนุกรมได้ในหนึ่งบรรทัด:

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

และ deserialize ด้วย:

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

สิ่งนี้ใช้ได้กับแผนที่หนึ่งมิติ แต่ใช้ไม่ได้กับแผนที่ n มิติ
mattsven

7

สตริงMapอินสแตนซ์(อ็อบเจ็กต์เป็นคีย์ได้) :

JSON.stringify([...map])

หรือ

JSON.stringify(Array.from(map))

หรือ

JSON.stringify(Array.from(map.entries()))

รูปแบบผลลัพธ์:

// [["key1","value1"],["key2","value2"]]

4

วิธีแก้ปัญหาด้านล่างใช้งานได้แม้ว่าคุณจะซ้อนแผนที่ไว้ก็ตาม

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

หากไม่ได้ทดสอบคำตอบของคุณฉันรู้สึกซาบซึ้งอยู่แล้วว่าการให้ความสำคัญกับปัญหาของแผนที่ที่ซ้อนกัน แม้ว่าคุณจะแปลงสิ่งนี้เป็น JSON ได้สำเร็จ แต่การแยกวิเคราะห์ใด ๆ ที่ทำในอนาคตจะต้องมีการรับรู้อย่างชัดเจนว่า JSON เดิมเป็นMapและ (แย่กว่านั้น) ว่าแต่ละแผนที่ย่อย (มีอยู่) เดิมเป็นแผนที่ด้วย มิฉะนั้นจะไม่มีทางแน่ใจarray of pairsได้ว่าไม่ได้ตั้งใจให้เป็นแบบนั้นทั้งหมดแทนที่จะเป็นแผนที่ ลำดับชั้นของออบเจ็กต์และอาร์เรย์ไม่ได้รับภาระนี้เมื่อแยกวิเคราะห์ การทำให้เป็นอนุกรมที่เหมาะสมใด ๆMapจะบ่งชี้อย่างชัดเจนว่าเป็นไฟล์Map.
Lonnie Best

เพิ่มเติมเกี่ยวกับที่นี่
Lonnie Best

4

ทางออกที่ดีกว่า

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

หวังว่าจะประจบประแจงน้อยกว่าคำตอบด้านบน


ฉันไม่แน่ใจว่าหลายคนจะพอใจกับการขยายคลาสแผนที่หลักเพียงเพื่อทำให้เป็นอนุกรมเป็น json ...
vasia

1
ไม่จำเป็นต้องเป็น แต่เป็นวิธีที่ง่ายกว่าในการทำ โดยเฉพาะสิ่งนี้สอดคล้องกับหลักการ LSP และ OCP ของ SOLID นั่นคือแผนที่ดั้งเดิมกำลังถูกขยายไม่ได้แก้ไขและยังสามารถใช้ Liskov Substitution (LSP) กับแผนที่ดั้งเดิมได้ จริงอยู่ที่มันเป็น OOP มากกว่ามือใหม่หลาย ๆ คนหรือการเขียนโปรแกรมฟังก์ชั่นที่แข็งขันที่ผู้คนต้องการ แต่อย่างน้อยมันก็ขึ้นอยู่กับพื้นฐานของหลักการออกแบบซอฟต์แวร์พื้นฐานที่พยายามและเป็นจริง หากคุณต้องการใช้ Interface Segregation Principle (ISP) ของ SOLID คุณสามารถมีIJSONAbleอินเทอร์เฟซขนาดเล็ก(แน่นอนว่าใช้ TypeScript)
Cody

3

จากตัวอย่างของคุณเป็นกรณีการใช้งานที่เรียบง่ายซึ่งคีย์จะเป็นประเภทธรรมดาฉันคิดว่านี่เป็นวิธีที่ง่ายที่สุดในการสร้าง JSON ให้กับแผนที่

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

วิธีที่ฉันคิดเกี่ยวกับโครงสร้างข้อมูลพื้นฐานของแผนที่คืออาร์เรย์ของคู่คีย์ - ค่า (เป็นอาร์เรย์เอง) ดังนั้นสิ่งนี้:

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

เนื่องจากโครงสร้างข้อมูลพื้นฐานนั้นเป็นสิ่งที่เราพบใน Object.entries เราจึงสามารถใช้วิธี JavaScript ดั้งเดิมของObject.fromEntries()บนแผนที่ได้เช่นเดียวกับที่เราทำใน Array:

Object.fromEntries(myMap);

/*
{
     key1: "value1",
     key2: "value2",
     key3: "value3"
}
*/

จากนั้นสิ่งที่คุณเหลืออยู่คือการใช้ JSON.stringify () จากผลลัพธ์ของสิ่งนั้น

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.