将 ES6 class 对象序列化为 JSON

Serializing an ES6 class object as JSON

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

var myClass = new MyClass()

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

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

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

我希望为 class 对象找到一个 toJSON/fromJSON 库,因为我将它们用于其他语言,例如 swift/java,但找不到 javascript .

也许 class 结构太新了,或者我要问的东西可以在没有图书馆的情况下以某种方式轻松实现。

与你想在 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 方法的结果。

我知道这个问题很老,但我一直在努力,直到我写了一个紧凑的真实 "safe" 解决方案。

反序列化 returns 个仍然附加有工作方法的对象。

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


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

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

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

它至少有两种方法:

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

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

下面是我执行深拷贝的方法:

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 的类型)都被赋予了一个名为 [=12= 的成员].反序列化后,我们递归初始化每个具有 typeIndex 的子结构,然后将其删除以避免污染结构。

我做了一个模块esserializer来解决这个问题。它是一个实用程序,用于序列化 JavaScript class 实例,并将 "serialized-text" 反序列化为实例对象,并保留所有 Class/Property/Method 等。

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

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

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

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

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()的内部机制是:递归地手动组合对象及其原型信息。

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

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

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

  • 与其他解决方案不同,它不会用注入的 class 名称污染您的数据,
  • 它具体化了预期的数据层次结构。
  • 以下是 Typescript 中的一些示例,但它在 JS 中也同样有效

在显示带有 class 的示例之前,让我们从一个简单的数据结构开始:

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 数据结构(数组、对象)或具有递归嵌套自定义 classes、第三方 classes、构建的 class 实例-in classes, or sub JSON structures (arrays, objects).

现在,让我们使用 class 代替:

// 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 classes :

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

我也需要class连载,所以做了一个库

https://github.com/denostack/superserial

我觉得你期望的toJSON/fromJSON功能可以通过toSerialize/toDeserialize来实现。

import { Serializer, toDeserialize, toSerialize } from "superserial";

class User {
  static [toDeserialize](data: { serializedBirth: number }) {
    return new User(data.serializedBirth);
  }

  #birth: number;

  constructor(
    birth: number,
  ) {
    this.#birth = birth;
  }

  get age() {
    return new Date().getFullYear() - this.#birth;
  }

  [toSerialize]() {
    return {
      serializedBirth: this.#birth,
    };
  }
}

const serializer = new Serializer({
  classes: {
    User, // Define the class to use for deserialization here
  },
});

然后,序列化,

const serialized = serializer.serialize(new User(2002));

序列化字符串:

MyClass{"name":"wan2land","serializedBirth":2000}

反序列化,

const user = serializer.deserialize<User>(serialized);

当然可以省略toSerializetoDeserialize。 :-)