web-dev-qa-db-fra.com

Le meilleur moyen de sérialiser/désérialiser des objets en JavaScript?

J'ai plusieurs objets JavaScript dans mon application, quelque chose comme:

function Person(age) {
    this.age = age;
    this.isOld = function (){
        return this.age > 60;
    }
}
// before serialize, ok
var p1 = new Person(77);
alert("Is old: " + p1.isOld());

// after, got error Object #<Object> has no method 'isOld'
var serialize = JSON.stringify(p1);
var _p1 = JSON.parse(serialize);
alert("Is old: " + _p1.isOld());

Voir dans JS Fiddle .

Ma question est la suivante: existe-t-il une bonne pratique/modèle/conseil pour récupérer mon objet dans le même type qu'il était avant la sérialisation (instances de la classe Person, dans ce cas)?

Exigences que j'ai:

  • Optimiser l'utilisation du disque: j'ai un grand arbre d'objets en mémoire. Donc, je ne veux pas stocker de fonctions.
  • La solution peut utiliser jQuery et une autre bibliothèque pour sérialiser/désérialiser.
52
Topera

JSON n'a aucune fonction en tant que type de données. Vous pouvez uniquement sérialiser des chaînes, des nombres, des objets, des tableaux et des booléens (et null)

Vous pouvez créer votre propre méthode toJson en ne transmettant que les données devant être sérialisées:

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

Similaire pour la désérialisation: 

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

L'utilisation serait:

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

Pour réduire la charge de travail, vous pouvez envisager de stocker toutes les données devant être sérialisées dans une propriété spéciale "data" pour chaque instance Person. Par exemple:

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

alors la sérialisation et la désérialisation appellent simplement JSON.stringify(this.data) et la définition des données d'une instance serait instance.data = JSON.parse(json).

Cela garderait les méthodes toJson et fromJson simples, mais vous auriez à ajuster vos autres fonctions.


Note de côté:

Vous devriez ajouter la méthode isOld au prototype de la fonction:

Person.prototype.isOld = function() {}

Sinon, chaque instance a sa propre instance de cette fonction, ce qui augmente également la mémoire.

54
Felix Kling

J'ai écrit serialijse parce que je rencontrais le même problème que vous.

vous pouvez le trouver à https://github.com/erossignon/serialijse

Il peut être utilisé dans nodejs ou dans un navigateur et peut servir à sérialiser et à désérialiser un ensemble complexe d'objets d'un contexte (nodejs) à un autre (navigateur) ou inversement.

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();
6
Etienne

L’API JSON native du navigateur peut ne pas vous restituer votre fonction idOld après l’appel de JSON.stringify, cependant, si vous pouvez configurer vous-même votre code JSON (utilisez éventuellement/ json2.js de Crockford à la place de l’API du navigateur), si vous avez une chaîne de JSON par exemple.

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

alors vous pouvez appeler 

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

et vous retrouverez votre fonction dans l’objet json.

4
T. Webster

Je suis l'auteur de https://github.com/joonhocho/seri .

Seri est un support de classe JSON + personnalisé (imbriqué).

Vous devez simplement fournir toJSON et fromJSON pour sérialiser et désérialiser toutes les instances de classe.

Voici un exemple avec des objets de classe imbriqués:

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';

J'espère que cela vous aidera à résoudre votre problème.

2
Joon

J'avais un problème similaire et comme je ne trouvais pas de solution suffisante, j'ai également créé une bibliothèque de sérialisation pour javascript: https://github.com/wavesoft/jbb (en fait, c'est un peu plus , car il est principalement destiné au groupement de ressources)

Il se rapproche de Binary-JSON, mais ajoute quelques fonctionnalités supplémentaires, telles que les métadonnées des objets encodés et certaines optimisations supplémentaires telles que la déduplication des données, les références croisées avec d'autres bundles et la compression au niveau de la structure.

Cependant, il y a un problème: pour que la taille du paquet reste petite, il n'y a pas d'informations de type dans le paquet. Ces informations sont fournies dans un "profil" séparé qui décrit vos objets pour le codage et le décodage. Pour des raisons d'optimisation, ces informations sont fournies sous forme de script.

Mais vous pouvez vous simplifier la vie en utilisant l'utilitaire gulp-jbb-profile ( https://github.com/wavesoft/gulp-jbb-profile ) pour générer les scripts d'encodage/décodage à partir de spécifications d'objet YAML simples comme ceci:

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

Par exemple, vous pouvez consulter le profil jbb-profile-three . Lorsque votre profil est prêt, vous pouvez utiliser JBB comme ceci:

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

Et vous pouvez le relire comme ceci:

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'];

});
1
Wavey

J'ai eu exactement le même problème et j'ai écrit un petit outil permettant de mélanger les données et le modèle. Voir https://github.com/khayll/jsmix

Voici comment vous le feriez:

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

Il peut également gérer des objets complexes, tels que des collections imbriquées, de manière récursive.

Pour ce qui est de la sérialisation des fonctions JS, je ne le ferais pas pour des raisons de sécurité.

1
fishgen

J'ai ajouté un autre dépôt de sérialiseur JavaScript à GitHub.

Plutôt que de prendre l’approche de la sérialisation et de la désérialisation des objets JavaScript dans un format interne, l’approche consiste ici à sérialiser les objets JavaScript en JavaScript natif. Cela présente l’avantage que le format est totalement agnostique par rapport au sérialiseur et que l’objet peut être recréé simplement en appelant eval ().

https://github.com/iconico/JavaScript-Serializer

0
Nico Westerdale