Best way to serialize/unserialize objects in JavaScript?

前端 未结 8 1373
忘掉有多难
忘掉有多难 2020-11-27 13:53

I have many JavaScript objects in my application, something like:

function Person(age) {
    this.age = age;
    this.isOld = function (){
        return thi         


        
相关标签:
8条回答
  • 2020-11-27 13:59

    I wrote serialijse because I faced the same problem as you.

    you can find it at https://github.com/erossignon/serialijse

    It can be used in nodejs or in a browser and can serve to serialize and deserialize a complex set of objects from one context (nodejs) to the other (browser) or vice-versa.

    var s = require("serialijse");
    
    
    var assert = require("assert");
    
    
    // testing serialization of a simple javascript object with date
    function testing_javascript_serialization_object_with_date() {
    
        var o = {
            date: new Date(),
            name: "foo"
        };
        console.log(o.name, o.date.toISOString());
    
        // JSON will fail as JSON doesn't preserve dates
        try {
            var jstr = JSON.stringify(o);
            var jo = JSON.parse(jstr);
            console.log(jo.name, jo.date.toISOString());
        } catch (err) {
            console.log(" JSON has failed to preserve Date during stringify/parse ");
            console.log("  and has generated the following error message", err.message);
        }
        console.log("");
    
    
    
        var str = s.serialize(o);
        var so = s.deserialize(str);
        console.log(" However Serialijse knows how to preserve date during serialization/deserialization :");
        console.log(so.name, so.date.toISOString());
        console.log("");
    }
    testing_javascript_serialization_object_with_date();
    
    
    // serializing a instance of a class
    function testing_javascript_serialization_instance_of_a_class() {
    
        function Person() {
            this.firstName = "Joe";
            this.lastName = "Doe";
            this.age = 42;
        }
    
        Person.prototype.fullName = function () {
            return this.firstName + " " + this.lastName;
        };
    
    
        // testing serialization using  JSON.stringify/JSON.parse
        var o = new Person();
        console.log(o.fullName(), " age=", o.age);
    
        try {
            var jstr = JSON.stringify(o);
            var jo = JSON.parse(jstr);
            console.log(jo.fullName(), " age=", jo.age);
    
        } catch (err) {
            console.log(" JSON has failed to preserve the object class ");
            console.log("  and has generated the following error message", err.message);
        }
        console.log("");
    
        // now testing serialization using serialijse  serialize/deserialize
        s.declarePersistable(Person);
        var str = s.serialize(o);
        var so = s.deserialize(str);
    
        console.log(" However Serialijse knows how to preserve object classes serialization/deserialization :");
        console.log(so.fullName(), " age=", so.age);
    }
    testing_javascript_serialization_instance_of_a_class();
    
    
    // serializing an object with cyclic dependencies
    function testing_javascript_serialization_objects_with_cyclic_dependencies() {
    
        var Mary = { name: "Mary", friends: [] };
        var Bob = { name: "Bob", friends: [] };
    
        Mary.friends.push(Bob);
        Bob.friends.push(Mary);
    
        var group = [ Mary, Bob];
        console.log(group);
    
        // testing serialization using  JSON.stringify/JSON.parse
        try {
            var jstr = JSON.stringify(group);
            var jo = JSON.parse(jstr);
            console.log(jo);
    
        } catch (err) {
            console.log(" JSON has failed to manage object with cyclic deps");
            console.log("  and has generated the following error message", err.message);
        }
    
        // now testing serialization using serialijse  serialize/deserialize
        var str = s.serialize(group);
        var so = s.deserialize(str);
        console.log(" However Serialijse knows to manage object with cyclic deps !");
        console.log(so);
        assert(so[0].friends[0] == so[1]); // Mary's friend is Bob
    }
    testing_javascript_serialization_objects_with_cyclic_dependencies();
    
    0 讨论(0)
  • 2020-11-27 13:59

    I am the author of https://github.com/joonhocho/seri.

    Seri is JSON + custom (nested) class support.

    You simply need to provide toJSON and fromJSON to serialize and deserialize any class instances.

    Here's an example with nested class objects:

    import seri from 'seri';
    
    class Item {
      static fromJSON = (name) => new Item(name)
    
      constructor(name) {
        this.name = name;
      }
    
      toJSON() {
        return this.name;
      }
    }
    
    class Bag {
      static fromJSON = (itemsJson) => new Bag(seri.parse(itemsJson))
    
      constructor(items) {
        this.items = items;
      }
    
      toJSON() {
        return seri.stringify(this.items);
      }
    }
    
    // register classes
    seri.addClass(Item);
    seri.addClass(Bag);
    
    
    const bag = new Bag([
      new Item('apple'),
      new Item('orange'),
    ]);
    
    
    const bagClone = seri.parse(seri.stringify(bag));
    
    
    // validate
    bagClone instanceof Bag;
    
    bagClone.items[0] instanceof Item;
    bagClone.items[0].name === 'apple';
    
    bagClone.items[1] instanceof Item;
    bagClone.items[1].name === 'orange';
    

    Hope it helps address your problem.

    0 讨论(0)
  • 2020-11-27 14:03

    I had a similar problem and since I couldn't find a sufficient solution, I also created a serialization library for javascript: https://github.com/wavesoft/jbb (as a matter of fact it's a bit more, since it's mainly intended for bundling resources)

    It is close to Binary-JSON but it adds a couple of additional features, such as metadata for the objects being encoded and some extra optimizations like data de-duplication, cross-referencing to other bundles and structure-level compression.

    However there is a catch: In order to keep the bundle size small there are no type information in the bundle. Such information are provided in a separate "profile" that describes your objects for encoding and decoding. For optimization reasons this information is given in a form of script.

    But you can make your life easier using the gulp-jbb-profile (https://github.com/wavesoft/gulp-jbb-profile) utility for generating the encodeing/decoding scripts from simple YAML object specifications like this:

    # The 'Person' object has the 'age' and 'isOld'
    # properties
    Person:
      properties:
        - age
        - isOld
    

    For example you can have a look on the jbb-profile-three profile. When you have your profile ready, you can use JBB like this:

    var JBBEncoder = require('jbb/encode');
    var MyEncodeProfile = require('profile/profile-encode');
    
    // Create a new bundle
    var bundle = new JBBEncoder( 'path/to/bundle.jbb' );
    
    // Add one or more profile(s) in order for JBB
    // to understand your custom objects
    bundle.addProfile(MyEncodeProfile);
    
    // Encode your object(s) - They can be any valid
    // javascript object, or objects described in
    // the profiles you added previously.
    
    var p1 = new Person(77);
    bundle.encode( p1, 'person' );
    
    var people = [
            new Person(45),
            new Person(77),
            ...
        ];
    bundle.encode( people, 'people' );
    
    // Close the bundle when you are done
    bundle.close();
    

    And you can read it back like this:

    var JBBDecoder = require('jbb/decode');
    var MyDecodeProfile = require('profile/profile-decode');
    
    // Instantiate a new binary decoder
    var binaryLoader = new JBBDecoder( 'path/to/bundle' );
    
    // Add your decoding profile
    binaryLoader.addProfile( MyDecodeProfile );
    
    // Add one or more bundles to load
    binaryLoader.add( 'bundle.jbb' );
    
    // Load and callback when ready
    binaryLoader.load(function( error, database ) {
    
        // Your objects are in the database
        // and ready to use!
        var people = database['people'];
    
    });
    
    0 讨论(0)
  • The browser's native JSON API may not give you back your idOld function after you call JSON.stringify, however, if can stringify your JSON yourself (maybe use Crockford's json2.js instead of browser's API), then if you have a string of JSON e.g.

    var person_json = "{ \"age:\" : 20, \"isOld:\": false, isOld: function() { return this.age > 60; } }";
    

    then you can call

    eval("(" + person + ")") 
    

    , and you will get back your function in the json object.

    0 讨论(0)
  • 2020-11-27 14:07

    I had the exact same problem, and written a small tool to do the mixing of data and model. See https://github.com/khayll/jsmix

    This is how you would do it:

    //model object (or whatever you'd like the implementation to be)
    var Person = function() {}
    Person.prototype.isOld = function() {
        return this.age > RETIREMENT_AGE;
    }
    
    //then you could say:
    var result = JSMix(jsonData).withObject(Person.prototype, "persons").build();
    
    //and use
    console.log(result.persons[3].isOld());
    

    It can handle complex objects, like nested collections recursively as well.

    As for serializing JS functions, I wouldn't do such thing because of security reasons.

    0 讨论(0)
  • 2020-11-27 14:12

    JSON has no functions as data types. You can only serialize strings, numbers, objects, arrays, and booleans (and null)

    You could create your own toJson method, only passing the data that really has to be serialized:

    Person.prototype.toJson = function() {
        return JSON.stringify({age: this.age});
    };
    

    Similar for deserializing:

    Person.fromJson = function(json) {
        var data = JSON.parse(json); // Parsing the json string.
        return new Person(data.age);
    };
    

    The usage would be:

    var serialize = p1.toJson();
    var _p1 = Person.fromJson(serialize);
    alert("Is old: " + _p1.isOld());
    

    To reduce the amount of work, you could consider to store all the data that needs to be serialized in a special "data" property for each Person instance. For example:

    function Person(age) {
        this.data = {
            age: age
        };
        this.isOld = function (){
            return this.data.age > 60 ? true : false;
        }
    }
    

    then serializing and deserializing is merely calling JSON.stringify(this.data) and setting the data of an instance would be instance.data = JSON.parse(json).

    This would keep the toJson and fromJson methods simple but you'd have to adjust your other functions.


    Side note:

    You should add the isOld method to the prototype of the function:

    Person.prototype.isOld = function() {}
    

    Otherwise, every instance has it's own instance of that function which also increases memory.

    0 讨论(0)
提交回复
热议问题