将 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/jsonizer
:https://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
个实例,它们将是 String
s
现在,让我们使用 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);
当然可以省略toSerialize
和toDeserialize
。 :-)
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/jsonizer
:https://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
个实例,它们将是 String
s
现在,让我们使用 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);
当然可以省略toSerialize
和toDeserialize
。 :-)