将 ES6 类对象序列化为 JSON

Posted

技术标签:

【中文标题】将 ES6 类对象序列化为 JSON【英文标题】:Serializing an ES6 class object as 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 中,因为它已经是可序列化的。 相关:反序列化:***.com/questions/38922990/… 【参考方案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 方法的结果。

【讨论】:

如何反序列化它。 @IWuZuo JSON.parse( string ) 谢谢。 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()的内部机制是:手动组合对象及其原型信息,递归。

【讨论】:

replit.com/@deanc1/SickOblongMicroinstruction#index.js 这适用于一级深层嵌套对象 - 谢谢@shaochuancs【参考方案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)

【讨论】:

如果您不使用嵌套对象,这是一个很好的解决方案。但是,当您尝试这样做时,您会看到失败:replit.com/@deanc1/PerfumedBustlingAngle#index.js【参考方案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的子结构,然后去掉它以避免污染结构。

【讨论】:

以上是关于将 ES6 类对象序列化为 JSON的主要内容,如果未能解决你的问题,请参考以下文章

csharp 将C#类对象写入/序列化为JSON对象

如何将具有不同值的相同 JSON 对象反序列化为 java 类

如何将具有嵌套属性的 JSON 对象反序列化为 Symfony 实体?

将 JSON 反序列化为对象

将 JSON 反序列化为对象

在Python 中如何将类对象序列化为JSON?