11

The requirement is simple. Here's a class...

class myobj {

    constructor(var1, var2) {
        this.var1 = var1;
        this.var2 = var2;
    }

    addThemUp() {
        return this.var1 + this.var2;
    }

}

Now I make one of these...

var myInstance = new MyObj(3, 7);

Lovely. But now I need to serialize this so that I can save it and load it up later. Something like...

serialise(myInstance);
// Save to a file
// ... do some other stuff ...
// load from file
myInstance = unserialise(fileData);
// - or - 
unserialise(fileData, myInstace);
// etc...

How? I don't care what form the representation takes as it will be loaded by the same page. If values-only are saved, a generic way of loading them back into an object would be fine because the same page has the class 'template' to find the definitions for the functions.

The object is large and changing, so manually copying the values from JSON in order to preserve the functions wouldn't be practically maintainable. But a solution that could do that with any number and typeof variables would be useful.

I've also got to stick to vanilla, single-file javascript, albeit in modern browsers.

TL;DR: How can I serialise and unserialise an object without losing the functions?

IamPancakeMan
  • 199
  • 1
  • 10
  • 1
    Related, probably a dupe: [Serializing an ES6 class object as JSON](https://stackoverflow.com/questions/40201589/serializing-an-es6-class-object-as-json) – ggorlen Feb 16 '23 at 07:45

7 Answers7

17

You can use JSON.stringify, but be aware that it only serialize properties, not methods. So to unserialize an object, just create a dummy instance and use Object.assign to update the properties using the retrieved object:

function serialize(instance) {
    var str = JSON.stringify(instance);
    // save str or whatever
}

function unserialize(str, theClass) {
    var instance = new theClass();                  // NOTE: if your constructor checks for unpassed arguments, then just pass dummy ones to prevent throwing an error
    var serializedObject = JSON.parse(str);
    Object.assign(instance, serializedObject);
    return instance;
}

Example:

function serialize(obj) {
    var str = JSON.stringify(obj);
    return str;
}

function unserialize(str, theClass) {
    var instance = new theClass();
    var serializedObject = JSON.parse(str);
    Object.assign(instance, serializedObject);
    return instance;
}

// TEST CLASS

class TestClass {
    constructor(a, b) {
        this.a = a;
        this.b = b;
    }

    sum() {
        return this.a + this.b;
    }
}

// USAGE

var instance = new TestClass(5, 7);

var str = serialize(instance);

var retrievedInstance = unserialize(str, TestClass);

console.log(retrievedInstance.sum());
ibrahim mahrir
  • 31,174
  • 5
  • 48
  • 73
  • Using Object.assign alongside JSON.stringify definitely does the job for an object with native type variables, so thank you very much for this. Is it possible for these to work recursively in objects within this object that also have functions, or will I have to go through the owned objects myself and assign each? It's not the end of the world if I do, at least I'm not doing it for every variables therein! – IamPancakeMan Jul 22 '18 at 11:08
  • @IamPancakeMan I've made an npm module named esserializer which can recursively deserialize object instance, with all types/functions information retained. I'll post an answer for this solution here soon. – shaochuancs Feb 26 '21 at 07:40
2

When you serialize your object in JSON form, your class information will be lost. To prevent it from happening, you have to add a property manually to preserve your original class information; like class name. Then you can use that information bit while deserializing, and restore all information to original class object. Here is a general way to bring JSON back to its original class object:

class Foo{
    constructor(var1, var2){
        // save your class name as property
        this.classname = this.constructor.name;

        this.var1 = var1;
        this.var2 = var2;
    }

    hoo(var3, var4){
        this.var3 = var3;
        this.var4 = var4;
    }

    serialize(){
        return JSON.stringify(this);
    }
}

let foo = new Foo(1, 2);
foo.hoo(3, 4);

//Now we have JSON string:
//'{"classname":"Foo","var1":1,"var2":2,"var3":3,"var4":4}'
let json = foo.serialize();

function deserialize(json){
    //o is [Object object], but it contains every state of the original object
    let o = JSON.parse(json);
    //Get original class instance
    let original_class = eval(o.classname);

    //Create new object of the original class, then restore back property values
    //NOTE: You may not need to pass any parameter to the constructor, since
    //every state of the original object will be restored from o.
    return Object.assign(new original_class(), o);
}

let _foo = deserialize(json);
console.log(_foo instanceof Foo); // returns true;

I also created a small module that works with Node.js and handle nested objects as well, so if you are interested please check the code as well.

1

There is no way to serialize functions.

Can set up constructor to always take an object and use Object.assign() to assign all the input to instance

Stringify the enumerable properties to json, then when you need it again parse the json and pass that stored object to new MyObj again.

class MyObj {

  constructor(obj) {
    const defaults = {var1: 10,var2: 10};
    Object.assign(this, obj || defaults);
  }

  addThemUp() {
    return this.var1 + this.var2 + (this.var3 || 0) + (this.var4 || 0);
  }

  addSomeProps() {
    if (!this.var3 && !this.var4) {
      this.var3 = 10;
      this.var4 = 10
    }
  }

  serialize() {
    return JSON.stringify(this)
  }

}

const myInstance = new MyObj();
console.log('myInstance before more props added', myInstance)
myInstance.addSomeProps()
///stringify the instance object
const json = myInstance.serialize();
// same variables needed to pass to constructor
const storedObj = JSON.parse(json)
// create new instance
const instance2 = new MyObj(storedObj);

console.log('instance 2', instance2)
// try out the methods 
console.log('add in instance 2:', instance2.addThemUp())
charlietfl
  • 170,828
  • 13
  • 121
  • 150
1

It is a bit of a blunt answer to the question, ...

but valid :)

use TypeScript -> https://github.com/typestack/class-transformer

Tchakabam
  • 494
  • 5
  • 11
0

you can use JSON.stringify(myInstance); (to serialize the object) and use JSON.parse(mymyInstance) to get it back as an object

kirito
  • 221
  • 2
  • 8
0

I've made an npm module named esserializer to solve this problem: save JavaScript class object values during serialization, and later on recursively deserialize object instance, with all types/functions information retained.

In your case, the code would be pretty easy:

var ESSerializer = require('esserializer');
var MyObj = require('MyObj');

var myInstance = new MyObj(3, 7);
var serializedText = ESSerializer.serialize(myInstance);
//...do something, or send the above serializedText to another JavaScript environment.
var deserializedObj = ESSerializer.deserialize(serializedText, [MyObj]);

the deserializedObj is a MyObj instance, which contains all values/functions/superclasses information.

This module is an open source project. If you are interested, you can check the GitHub repo and see how it works.

shaochuancs
  • 15,342
  • 3
  • 54
  • 62
  • maybe this project can be of interest also for you: https://github.com/typestack/class-transformer – Tchakabam May 31 '21 at 18:58
  • also wondering, is this project above addressing some of the concerns with JS/TS serialization described by this article here? https://medium.com/@aems/one-mans-struggle-with-typescript-class-serialization-478d4bbb5826 – Tchakabam May 31 '21 at 19:01
0

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

https://github.com/denostack/superserial

class User {
  articles: Article[] = [];

  constructor(
    public name: string,
  ) {
  }

  addArticle(title: string) {
    this.articles.push(new Article(this, title));
  }
}

class Article {
  constructor(
    public user: User,
    public title: string,
  ) {
  }
}
import { Serializer } from 'superserial'

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

const user = new User("wan2land");

user.addArticle("Hello World 1");
user.addArticle("Hello World 2");

const serialized = serializer.serialize(user);


console.log(serialized);

serialized string:

User{"name":"wan2land","articles":$1};[$2,$3];Article{"user":$0,"title":"Hello World 1"};Article{"user":$0,"title":"Hello World 2"}
const deserialized = serializer.deserialize<User>(serialized);

console.log(deserialized);

output:

<ref *1> User {
  name: "wan2land",
  articles: [
    Article { user: [Circular *1], title: "Hello World 1" },
    Article { user: [Circular *1], title: "Hello World 2" }
  ]
}

The class structure can be perfectly restored, and the circular reference is also possible.

wan2land
  • 74
  • 4