50
class MyClass { constructor() { this.foo = 3 } } var myClass = new MyClass() 

I'd like to serialize myClass object to json.

One easy way I can think of is, since every member is actually javascript object (array, etc..) I guess I can maintain a variable to hold the member variables.

this.prop.foo = this.foo and so on.

I expected to find a toJSON/fromJSON library for class objects since I used them with other languages such as swift/java, but couldn't find one for javascript.

Maybe class construct is too new, or what I'm asking can be somehow easily achieved without a library.

4
  • Have you heard of JSON.stringify()? Does this do what you want? Commented Oct 23, 2016 at 9:51
  • Is there a reason why you want to do this? Commented Oct 23, 2016 at 9:57
  • @Pineda yeah, I wanted to store serializable data in react redux store. But then, answers seem to suggest I can store my class object in redux just fine, since it's already serializable. Commented Oct 24, 2016 at 1:24
  • related: de-serialize: stackoverflow.com/questions/38922990/… Commented Jan 18, 2020 at 13:45

8 Answers 8

45

As with any other object you want to stringify in JS, you can use JSON.stringify:

JSON.stringify(yourObject); 

class MyClass { constructor() { this.foo = 3 } } var myClass = new MyClass() console.log(JSON.stringify(myClass));

Also worth noting is that you can customize how stringify serializes your object by giving it a toJSON method. The value used to represent your object in the resulting JSON string will be the result of calling the toJSON method on that object.

Sign up to request clarification or add additional context in comments.

5 Comments

How to unserialize it .
Thanks. JSON.parse( string ) return a json object . not an instance of MyClass.
@IWuZhuo for that I would suggest that you create a static method on your class that takes in that object and returns an instance of the class by creating the object with the properties in the JSON. There is no way of doing this in Javascript out of the box, but I would say it's quite easy to write it yourself
to unserialize, it's not that straightforward especially when it comes to complex nested objects, but there is a lightweight library that does that. Checkout my answer below for details.
20

I know this question is old but I've been clawing my eyes out until I wrote a compact real, "safe", solution.

Deserialization returns objects that still have working methods attached to them.

The only thing you need to do is register the classes you want to use in the constructor of the serializer.


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

The above should be enough to get you going, but more details and minified version can be found here.

3 Comments

This example does not handle recursive object initialization. If a class Person contains a member Address, after deserialization you will be unable to call on Address's methods.
It appears to work if you give it both classes on initialization.
Serialization works if you give it all classes on initialization, but deserialize fails to add methods to anything buy the top level item var serializer = new Serializer([MyClass, Array]); console.log(serializer.serialize([new MyClass(42)])); //[1,[["0",{"foo":42}]]] console.log(serializer.deserialize('[1,[["0",[{"foo":42}]]]]')[0].getFoo()); //TypeError: serializer.deserialize(...)[0].getFoo is not a function
10

I've came across this library which does both serialization and deserialization of complex objects (including nested objects and arrays):

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

It has at least two methods:

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

Comments

5

I made a module esserializer to solve this issue. It is a utility to serialize JavaScript class instance, and deserialize the "serialized-text" into an instance object, with all Class/Property/Method etc. retained.

To serialize an instance, just invoke the serialize() method:

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

The internal mechanism of serialize() is: save the instance' property and its class name information into string, recursively.

To deserialize from string, just invoke the deserialize() method, passing all involved classes as parameter:

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

The internal mechanism of deserialize() is: manually compose the object with its prototype information, recursively.

2 Comments

replit.com/@deanc1/SickOblongMicroinstruction#index.js This works well on one-level deep nested objects - Thank you @shaochuancs
I have tested this as of 2024, and can confirm it works well. It adds a field to the JSON, with the class name, which means you should avoid having more than one class with the same name (which is a reasonable restriction). Good work!
3

It's easy if you don't mind passing the class definition into decode.

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

1 Comment

This is a great solution if you don't use nested objects. But as you can see this fails when you try to do that: replit.com/@deanc1/PerfumedBustlingAngle#index.js
2

I also need class serialization, so I made a library.

https://github.com/denostack/superserial

I think the toJSON/fromJSON function you expect can be implemented through 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 }, }); 

then, serialize,

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

serialized string:

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

deserialize,

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

Of course, toSerialize and toDeserialize can be omitted. :-)

Comments

0

You need to be able to reinitialize objects recursively. Having a parameterless constructor is not essential, you can get away without having one.

Here's how I perform deep copy:

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

The idea is simple: when serializing, a member of every known type (a type that's in this.types) is anointed with a member called typeIndex. After deserialization, we recursively initialize every substructure that has a typeIndex, then get rid of it to avoid polluting the structure.

Comments

-1

Not a new topic, but there is a new solution : the modern approach (in December 2021) is to use @badcafe/jsonizer : https://badcafe.github.io/jsonizer

  • Unlike other solutions, it doesn't pollute you data with injected class names,
  • and it reifies the expected data hierarchy.
  • below are some examples in Typescript, but it works as well in JS

Before showing an example with a class, let's start with a simple data structure :

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 

Notice that dates are serialized to strings, and if you parse that JSON, dates won't be Date instances, they will be Strings

Now, let's use 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); 

Every dates string in the JSON text have been mapped to Date objects in the parsed result.

Jsonizer can indifferently revive JSON data structures (arrays, objects) or class instances with recursively nested custom classes, third-party classes, built-in classes, or sub JSON structures (arrays, objects).

Now, let's use a class instead :

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

Finally, let's use 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); 

1 Comment

While this seems like an interesting library, can we refrain from using phrases like "the modern approach" when something very clearly is not an industry standard approach. As of this comment, this library has a single contributor, 0 stars on Github and 16 weekly downloads. npmjs.com/package/@badcafe/jsonizer

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.