Лучший способ сериализации / десериализации объектов в JavaScript? - PullRequest
52 голосов
/ 27 июня 2011

В моем приложении много объектов JavaScript, что-то вроде:

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

См. В JS Fiddle .

У меня вопрос: есть ли лучшая практика / шаблон / совет для восстановления моего объекта в том же типе, который был до сериализации (в данном случае экземпляры класса Person)?

Требования, которые у меня есть:

  • Оптимизация использования диска: у меня в памяти большое дерево объектов. Поэтому я не хочу хранить функции.
  • Решение может использовать jQuery и другую библиотеку для сериализации / десериализации.

Ответы [ 7 ]

56 голосов
/ 27 июня 2011

JSON не имеет функций в качестве типов данных. Вы можете только сериализовать строки, числа, объекты, массивы и логические значения (и null)

Вы можете создать свой собственный метод toJson, только передавая данные, которые действительно должны быть сериализованы:

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

Аналогично для десериализации:

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

Использование будет:

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

Чтобы уменьшить объем работы, вы можете хранить все данные, которые необходимо сериализовать, в специальном свойстве «data» для каждого экземпляра Person. Например:

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

тогда сериализация и десериализация просто вызывает JSON.stringify(this.data) и установка данных экземпляра будет instance.data = JSON.parse(json).

Это позволит сохранить простые методы toJson и fromJson, но вам придется настроить другие функции.


Примечание:

Вы должны добавить метод isOld к прототипу функции:

Person.prototype.isOld = function() {}

В противном случае каждый экземпляр имеет собственный экземпляр этой функции, который также увеличивает память.

6 голосов
/ 24 октября 2014

Я написал serialijse , потому что столкнулся с той же проблемой, что и вы.

вы можете найти его в https://github.com/erossignon/serialijse

Он может использоваться в nodejs или вбраузер и может служить для сериализации и десериализации сложного набора объектов из одного контекста (nodejs) в другой (браузер) или наоборот.

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();
4 голосов
/ 27 июня 2011

Встроенный в браузер JSON API может не вернуть вам функцию idOld после вызова JSON.stringify, однако , если вы можете самостоятельно преобразовать свой JSON (возможно, используйте json2.js * Крокфорда вместо API браузера), тогда, если у вас есть строка JSON, например,

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

, тогда вы можете вызвать

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

, и вы вернете свою функцию в объект json.

2 голосов
/ 24 июня 2016

Я являюсь автором https://github.com/joonhocho/seri.

Seri - это поддержка JSON + пользовательских (вложенных) классов.

Вам просто нужно предоставить toJSON и fromJSON для сериализации и десериализациилюбые экземпляры классов.

Вот пример с вложенными объектами классов:

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

Надеюсь, это поможет решить вашу проблему.

1 голос
/ 02 июня 2016

У меня была похожая проблема, и так как я не мог найти достаточного решения, я также создал библиотеку сериализации для javascript: https://github.com/wavesoft/jbb (на самом деле это немного больше, поскольку она в основном предназначена дляресурсы комплектации)

Он близок к Binary-JSON, но добавляет несколько дополнительных функций, таких как метаданные для кодируемых объектов и некоторые дополнительные оптимизации, такие как дедупликация данных, перекрестные ссылки на другие пакеты исжатие на уровне структуры.

Однако здесь есть одна загвоздка: для того, чтобы сохранить небольшой размер пакета, в нем нет информации о типе.Такая информация предоставляется в отдельном «профиле», который описывает ваши объекты для кодирования и декодирования.По причинам оптимизации эта информация предоставляется в форме сценария.

Но вы можете упростить свою жизнь, используя утилиту gulp-jbb-profile (https://github.com/wavesoft/gulp-jbb-profile)) для генерации сценариев кодирования / декодирования из простого объекта YAMLследующие технические характеристики:

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

Например, вы можете посмотреть профиль jbb-profile-three. Когда ваш профиль готов, вы можете использовать JBB следующим образом:

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

И вы можете прочитать это обратно так:

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 голос
/ 07 сентября 2015

У меня была точно такая же проблема, и я написал небольшой инструмент для смешивания данных и модели.См. https://github.com/khayll/jsmix

Вот как вы это сделаете:

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

Он может обрабатывать сложные объекты, например, рекурсивные вложенные коллекции.

Что касается сериализации JSфункции, я бы не стал этого делать из соображений безопасности.

0 голосов
/ 09 января 2019

Я добавил еще один репозиторий сериализатора JavaScript в GitHub.

Вместо того, чтобы использовать сериализацию и десериализацию объектов JavaScript во внутреннем формате, подход здесь заключается в сериализации объектов JavaScript в собственный JavaScript. Преимущество этого заключается в том, что формат полностью независим от сериализатора, и объект можно воссоздать, просто вызвав eval ().

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...