【问题标题】:Serializing an ES6 class object as JSON将 ES6 类对象序列化为 JSON
【发布时间】:2017-03-05 05:41:52
【问题描述】:
class MyClass {
  constructor() {
    this.foo = 3
  }
}

var myClass = new MyClass()

我想将myClass 对象序列化为 json。

我能想到的一种简单方法是,因为每个成员实际上都是 javascript 对象(数组等)。我想我可以维护一个变量来保存成员变量。

this.prop.foo = this.foo 等等。

我希望为类对象找到一个 toJSON/fromJSON 库,因为我将它们与其他语言(如 swift/java)一起使用,但找不到用于 javascript 的库。

也许类结构太新,或者我的要求可以在没有库的情况下以某种方式轻松实现。

【问题讨论】:

  • 你听说过JSON.stringify()吗?这符合你的要求吗?
  • 您有这样做的理由吗?
  • @Pineda 是的,我想将可序列化的数据存储在 react redux 存储中。但是,答案似乎表明我可以将我的类对象存储在 redux 中,因为它已经是可序列化的。

标签: javascript json ecmascript-6 es6-class


【解决方案1】:

与您想在 JS 中字符串化的任何其他对象一样,您可以使用JSON.stringify

JSON.stringify(yourObject);

class MyClass {
  constructor() {
    this.foo = 3
  }
}

var myClass = new MyClass()

console.log(JSON.stringify(myClass));

另外值得注意的是,您可以自定义stringify 如何序列化您的对象,方法是给它一个toJSON method。用于在结果 JSON 字符串中表示您的对象的值将是对该对象调用 toJSON 方法的结果。

【讨论】:

  • 如何反序列化它。
  • 谢谢。 JSON.parse(string) 返回一个 json 对象。不是 MyClass 的实例。
  • @IWuZhuo 为此,我建议您在类上创建一个静态方法,该方法接受该对象并通过创建具有 JSON 中属性的对象来返回该类的实例。开箱即用的 Javascript 无法做到这一点,但我会说自己编写很容易
  • 要反序列化,它并不是那么简单,尤其是在涉及复杂的嵌套对象时,但是有一个轻量级库可以做到这一点。详情请查看我的回答。
【解决方案2】:

我知道这个问题已经很老了,但我一直在睁大眼睛,直到我写了一个紧凑的、真实的、“安全的”解决方案。

反序列化返回仍然具有附加工作方法的对象。

您唯一需要做的就是在序列化器的构造函数中注册要使用的类。


class Serializer{
    constructor(types){this.types = types;}
    serialize(object) {
        let idx = this.types.findIndex((e)=> {return e.name == object.constructor.name});
        if (idx == -1) throw "type  '" + object.constructor.name + "' not initialized";
        return JSON.stringify([idx, Object.entries(object)]);
    }
    deserialize(jstring) {
        let array = JSON.parse(jstring);
        let object = new this.types[array[0]]();
        array[1].map(e=>{object[e[0]] = e[1];});
        return object;
    }
}

class MyClass {
    constructor(foo) {this.foo = foo;}
    getFoo(){return this.foo;}
}

var serializer = new Serializer([MyClass]);

console.log(serializer.serialize(new MyClass(42)));
//[0,[["foo",42]]]

console.log(serializer.deserialize('[0,[["foo",42]]]').getFoo());
//42

以上内容应该足以让您继续前进,但可以找到更多详细信息和缩小版here

【讨论】:

  • 此示例不处理递归对象初始化。如果类Person包含成员Address,反序列化后你将无法调用Address的方法。
【解决方案3】:

我遇到过这个库,它可以对复杂对象(包括嵌套对象和数组)进行序列化和反序列化:

https://github.com/typestack/class-transformer

它至少有两种方法:

plainToClass() -> json obj to class
classToPlain() -> class to json obj

【讨论】:

    【解决方案4】:

    我制作了一个模块esserializer 来解决这个问题。它是一个序列化 JavaScript 类实例的实用程序,并将“序列化文本”反序列化为实例对象,并保留所有类/属性/方法等。

    要序列化一个实例,只需调用serialize() 方法:

    const ESSerializer = require('esserializer');
    let serializedString = ESSerializer.serialize(anObject);
    

    serialize()的内部机制是:将实例的属性及其类名信息递归保存到字符串中。

    要从字符串反序列化,只需调用deserialize() 方法,将所有涉及的类作为参数传递:

    const ESSerializer = require('esserializer');
    const ClassA = require('./ClassA');
    const ClassB = require('./ClassB');
    const ClassC = require('./ClassC');
    
    let deserializedObj = ESSerializer.deserialize(serializedString, [ClassA, ClassB, ClassC]);
    

    deserialize()的内部机制是:手动组合对象及其原型信息,递归。

    【讨论】:

    【解决方案5】:

    如果您不介意将类定义传递给解码,这很容易。

    // the code
    const encode = (object) => JSON.stringify(Object.entries(object))
    
    const decode = (string, T) => {
      const object = new T()
      JSON.parse(string).map(([key, value]) => (object[key] = value))
      return object
    }
    
    // test the code
    class A {
      constructor(n) {
        this.n = n
      }
    
      inc(n) {
        this.n += n
      }
    }
    
    const a = new A(1)
    const encoded = encode(a)
    const decoded = decode(encoded, A)
    decoded.inc(2)
    console.log(decoded)

    【讨论】:

    【解决方案6】:

    不是一个新话题,但有一个新的解决方案:现代方法(2021 年 12 月)是使用 @badcafe/jsonizer : https://badcafe.github.io/jsonizer

    • 与其他解决方案不同,它不会通过注入的类名污染您的数据,
    • 它重新定义了预期的数据层次结构。
    • 以下是 Typescript 中的一些示例,但在 JS 中也同样有效

    在展示一个类的例子之前,让我们从一个简单的数据结构开始:

    const person = {
        name: 'Bob',
        birthDate: new Date('1998-10-21'),
        hobbies: [
            {   hobby: 'programming',
                startDate: new Date('2021-01-01'),
            },
            {   hobby: 'cooking',
                startDate: new Date('2020-12-31'),
            },
        ]
    }
    const personJson = JSON.stringify(person);
    // {
    //     "name": "Bob",
    //     "birthDate": "1998-10-21T00:00:00.000Z",
    //     "hobbies": [
    //         {
    //             "hobby": "programming",
    //             "startDate": "2021-01-01T00:00:00.000Z"
    //         },
    //         {
    //             "hobby": "cooking",
    //             "startDate": "2020-12-31T00:00:00.000Z"
    //         }
    //     ]
    // }
    // store or send the data
    

    请注意,日期被序列化为字符串,如果您解析该 JSON,日期将不会是 Date 实例,它们将是 Strings

    现在,让我们使用 Jsonizer ?

    // in Jsonizer, a reviver is made of field mappers :
    const personReviver = Jsonizer.reviver<typeof person>({
        birthDate: Date,
        hobbies: {
            '*': {
                startDate: Date
            }
        }
    });
    const personFromJson = JSON.parse(personJson, personReviver);
    

    JSON 文本中的每个日期字符串都已映射到解析结果中的 Date 对象。

    Jsonizer 可以用递归嵌套的自定义类、第三方类、内置类或子 JSON 结构(数组、对象)无差别地恢复 JSON 数据结构(数组、对象)或类实例。

    现在,让我们改用一个类:

    // in Jsonizer, a class reviver is made of field mappers + an instance builder :
    @Reviver<Person>({ // ?  bind the reviver to the class
        '.': ({name, birthDate, hobbies}) => new Person(name, birthDate, hobbies), // ?  instance builder
        birthDate: Date,
        hobbies: {
            '*': {
                startDate: Date
            }
        }
    })
    class Person {
        constructor( // all fields are passed as arguments to the constructor
            public name: string,
            public birthDate: Date
            public hobbies: Hobby[]
        ) {}
    }
    interface Hobby {
        hobby: string,
        startDate: Date
    }
    
    const person = new Person(
        'Bob',
        new Date('1998-10-21'),
        [
            {   hobby: 'programming',
                startDate: new Date('2021-01-01'),
            },
            {   hobby: 'cooking',
                startDate: new Date('2020-12-31'),
            },
        ]
    );
    const personJson = JSON.stringify(person);
    
    const personReviver = Reviver.get(Person); // ?  extract the reviver from the class
    const personFromJson = JSON.parse(personJson, personReviver);
    

    最后,让我们使用 2 个类:

    @Reviver<Hobby>({
        '.': ({hobby, startDate}) => new Hobby(hobby, startDate), // ?  instance builder
        startDate: Date
    })
    class Hobby {
        constructor (
            public hobby: string,
            public startDate: Date
        ) {}
    }
    
    @Reviver<Person>({
        '.': ({name, birthDate, hobbies}) => new Person(name, birthDate, hobbies), // ?  instance builder
        birthDate: Date,
        hobbies: {
            '*': Hobby  // ?  we can refer a class decorated with @Reviver
        }
    })
    class Person {
        constructor(
            public name: string,
            public birthDate: Date,
            public hobbies: Hobby[]
        ) {}
    }
    
    const person = new Person(
        'Bob',
        new Date('1998-10-21'),
        [
            new Hobby('programming', new Date('2021-01-01')),
            new Hobby('cooking', new Date('2020-12-31')
        ]
    );
    const personJson = JSON.stringify(person);
    
    const personReviver = Reviver.get(Person); // ?  extract the reviver from the class
    const personFromJson = JSON.parse(personJson, personReviver);
    

    【讨论】:

      【解决方案7】:

      您需要能够递归地重新初始化对象。有一个无参数的构造函数不是必须的,你可以不用它。

      这是我执行深层复制的方式:

      class Serializer
      {
        constructor(types){
          this.types = types;
        }
      
        markRecursive(object)
        {
          // anoint each object with a type index
          let idx = this.types.findIndex(t => {
            return t.name === object.constructor.name;
          });
          if (idx !== -1)
          {
            object['typeIndex'] = idx;
      
            for (let key in object)
            {
              if (object.hasOwnProperty(key) && object[key] != null)
                this.markRecursive(object[key]);
            }
          }
        }
      
        cleanUp(object)
        {
          if (object.hasOwnProperty('typeIndex')) {
            delete object.typeIndex;
            for (let key in object) {
              if (object.hasOwnProperty(key) && object[key] != null) {
                console.log(key);
                this.cleanUp(object[key]);
              }
            }
          }
        }
      
        reconstructRecursive(object)
        {
          if (object.hasOwnProperty('typeIndex'))
          {
            let type = this.types[object.typeIndex];
            let obj = new type();
            for (let key in object)
            {
              if (object.hasOwnProperty(key) && object[key] != null) {
                obj[key] = this.reconstructRecursive(object[key]);
              }
            }
            delete obj.typeIndex;
            return obj;
          }
          return object;
        }
      
        clone(object)
        {
          this.markRecursive(object);
          let copy = JSON.parse(JSON.stringify(object));
          this.cleanUp(object);
          return this.reconstructRecursive(copy);
        }
      }
      

      这个想法很简单:在序列化时,每个已知 类型(this.types 中的类型)的成员都被一个名为typeIndex 的成员涂抹。反序列化后,我们递归地初始化每个有typeIndex的子结构,然后去掉它以避免污染结构。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-10-08
        • 1970-01-01
        • 2020-07-19
        • 2017-12-19
        相关资源
        最近更新 更多