Подтвердить что ты не робот

Превращение строк JSON в объекты с помощью методов

У меня есть приложение, которое позволяет пользователям создавать объекты и хранить их (в таблице MySQL, как строки) для последующего использования. Объект может быть:

function Obj() {
    this.label = "new object";
}

Obj.prototype.setLabel = function(newLabel) {
    this.label = newLabel;
}

Если я использую JSON.stringify для этого объекта, я получу только информацию о Obj.label (строковый объект будет иметь строку типа {label: "new object"}. Если я сохраню эту строку и хочу разрешить пользователю загружать объект позже, метод setLabel будет потерян.

Итак, мой вопрос: как я могу повторно создать объект, чтобы он сохранял свойства, сохраненные благодаря JSON.stringify, но также возвращает разные методы, которые должны принадлежать его прототипу. Как бы Вы это сделали? Я думал о чем-то, "создав пустой объект" и "объединив его с сохраненными свойствами", но я не могу заставить его работать.

4b9b3361

Ответ 1

Чтобы сделать это, вы захотите использовать функцию "reviver" при разборе строки JSON (и функции "replacer" или функции toJSON на вашем прототипе конструктора при его создании). См. Раздел 15.12.2 и 15.12.3 спецификации. Если ваша среда еще не поддерживает собственный разбор JSON, вы можете использовать один из парсеров Crockford (Crockford является изобретателем JSON), который также поддерживает функции "reviver".

Вот простой пример, который работает с браузерами, совместимыми с ES5 (или библиотеками, которые эмулируют поведение ES5) (live copy, запускается в Chrome или Firefox или аналогичном) но обратите внимание на пример для более обобщенного решения.

// Our constructor function
function Foo(val) {
  this.value = val;
}
Foo.prototype.nifty = "I'm the nifty inherited property.";
Foo.prototype.toJSON = function() {
  return "/Foo(" + this.value + ")/";
};

// An object with a property, `foo`, referencing an instance
// created by that constructor function, and another `bar`
// which is just a string
var obj = {
  foo: new Foo(42),
  bar: "I'm bar"
};

// Use it
display("obj.foo.value = " + obj.foo.value);
display("obj.foo.nifty = " + obj.foo.nifty);
display("obj.bar = " + obj.bar);

// Stringify it with a replacer:
var str = JSON.stringify(obj);

// Show that
display("The string: " + str);

// Re-create it with use of a "reviver" function
var obj2 = JSON.parse(str, function(key, value) {
  if (typeof value === "string" &&
      value.substring(0, 5) === "/Foo(" &&
      value.substr(-2) == ")/"
     ) {
    return new Foo(value.substring(5, value.length - 2));
  }
  return value;
});

// Use the result
display("obj2.foo.value = " + obj2.foo.value);
display("obj2.foo.nifty = " + obj2.foo.nifty);
display("obj2.bar = " + obj2.bar);

Обратите внимание на toJSON на Foo.prototype, а затем на функцию JSON.parse.

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

Здесь приведен пример обобщенного редактора, который ищет свойство ctor и свойство data, и вызывает ctor.fromJSON, если он найден, передавая полученное полное значение (живой пример):

// A generic "smart reviver" function.
// Looks for object values with a `ctor` property and
// a `data` property. If it finds them, and finds a matching
// constructor that has a `fromJSON` property on it, it hands
// off to that `fromJSON` fuunction, passing in the value.
function Reviver(key, value) {
  var ctor;

  if (typeof value === "object" &&
      typeof value.ctor === "string" &&
      typeof value.data !== "undefined") {
    ctor = Reviver.constructors[value.ctor] || window[value.ctor];
    if (typeof ctor === "function" &&
        typeof ctor.fromJSON === "function") {
      return ctor.fromJSON(value);
    }
  }
  return value;
}
Reviver.constructors = {}; // A list of constructors the smart reviver should know about  

Чтобы избежать повторения общей логики в функциях toJSON и fromJSON, вы могли бы иметь общие версии:

// A generic "toJSON" function that creates the data expected
// by Reviver.
// `ctorName`  The name of the constructor to use to revive it
// `obj`       The object being serialized
// `keys`      (Optional) Array of the properties to serialize,
//             if not given then all of the objects "own" properties
//             that don't have function values will be serialized.
//             (Note: If you list a property in `keys`, it will be serialized
//             regardless of whether it an "own" property.)
// Returns:    The structure (which will then be turned into a string
//             as part of the JSON.stringify algorithm)
function Generic_toJSON(ctorName, obj, keys) {
  var data, index, key;

  if (!keys) {
    keys = Object.keys(obj); // Only "own" properties are included
  }

  data = {};
  for (index = 0; index < keys.length; ++index) {
    key = keys[index];
    data[key] = obj[key];
  }
  return {ctor: ctorName, data: data};
}

// A generic "fromJSON" function for use with Reviver: Just calls the
// constructor function with no arguments, then applies all of the
// key/value pairs from the raw data to the instance. Only useful for
// constructors that can be reasonably called without arguments!
// `ctor`      The constructor to call
// `data`      The data to apply
// Returns:    The object
function Generic_fromJSON(ctor, data) {
  var obj, name;

  obj = new ctor();
  for (name in data) {
    obj[name] = data[name];
  }
  return obj;
}

Преимущество в том, что вы откладываете реализацию определенного типа (из-за отсутствия лучшего термина) для того, как он сериализуется и десериализуется. Таким образом, у вас может быть "тип", который просто использует дженерики:

// `Foo` is a constructor function that integrates with Reviver
// but doesn't need anything but the generic handling.
function Foo() {
}
Foo.prototype.nifty = "I'm the nifty inherited property.";
Foo.prototype.spiffy = "I'm the spiffy inherited property.";
Foo.prototype.toJSON = function() {
  return Generic_toJSON("Foo", this);
};
Foo.fromJSON = function(value) {
  return Generic_fromJSON(Foo, value.data);
};
Reviver.constructors.Foo = Foo;

... или тот, который по какой-то причине должен сделать что-то более обычное:

// `Bar` is a constructor function that integrates with Reviver
// but has its own custom JSON handling for whatever reason.
function Bar(value, count) {
  this.value = value;
  this.count = count;
}
Bar.prototype.nifty = "I'm the nifty inherited property.";
Bar.prototype.spiffy = "I'm the spiffy inherited property.";
Bar.prototype.toJSON = function() {
  // Bar custom handling *only* serializes the `value` property
  // and the `spiffy` or `nifty` props if necessary.
  var rv = {
    ctor: "Bar",
    data: {
      value: this.value,
      count: this.count
    }
  };
  if (this.hasOwnProperty("nifty")) {
    rv.data.nifty = this.nifty;
  }
  if (this.hasOwnProperty("spiffy")) {
    rv.data.spiffy = this.spiffy;
  }
  return rv;
};
Bar.fromJSON = function(value) {
  // Again custom handling, for whatever reason Bar doesn't
  // want to serialize/deserialize properties it doesn't know
  // about.
  var d = value.data;
      b = new Bar(d.value, d.count);
  if (d.spiffy) {
    b.spiffy = d.spiffy;
  }
  if (d.nifty) {
    b.nifty = d.nifty;
  }
  return b;
};
Reviver.constructors.Bar = Bar;

И как мы можем проверить, что Foo и Bar работают как ожидалось (live copy):

// An object with `foo` and `bar` properties:
var before = {
  foo: new Foo(),
  bar: new Bar("testing", 42)
};
before.foo.custom = "I'm a custom property";
before.foo.nifty = "Updated nifty";
before.bar.custom = "I'm a custom property"; // Won't get serialized!
before.bar.spiffy = "Updated spiffy";

// Use it
display("before.foo.nifty = " + before.foo.nifty);
display("before.foo.spiffy = " + before.foo.spiffy);
display("before.foo.custom = " + before.foo.custom + " (" + typeof before.foo.custom + ")");
display("before.bar.value = " + before.bar.value + " (" + typeof before.bar.value + ")");
display("before.bar.count = " + before.bar.count + " (" + typeof before.bar.count + ")");
display("before.bar.nifty = " + before.bar.nifty);
display("before.bar.spiffy = " + before.bar.spiffy);
display("before.bar.custom = " + before.bar.custom + " (" + typeof before.bar.custom + ")");

// Stringify it with a replacer:
var str = JSON.stringify(before);

// Show that
display("The string: " + str);

// Re-create it with use of a "reviver" function
var after = JSON.parse(str, Reviver);

// Use the result
display("after.foo.nifty = " + after.foo.nifty);
display("after.foo.spiffy = " + after.foo.spiffy);
display("after.foo.custom = " + after.foo.custom + " (" + typeof after.foo.custom + ")");
display("after.bar.value = " + after.bar.value + " (" + typeof after.bar.value + ")");
display("after.bar.count = " + after.bar.count + " (" + typeof after.bar.count + ")");
display("after.bar.nifty = " + after.bar.nifty);
display("after.bar.spiffy = " + after.bar.spiffy);
display("after.bar.custom = " + after.bar.custom + " (" + typeof after.bar.custom + ")");

display("(Note that after.bar.custom is undefined because <code>Bar</code> specifically leaves it out.)");

Ответ 2

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

У вас были некоторые ошибки (function ... = function(...), а JSON требует, чтобы клавиши были окружены ").

http://jsfiddle.net/sc8NU/1/

var data = '{"label": "new object"}';  // JSON
var inst = new Obj;                    // empty instance
jQuery.extend(inst, JSON.parse(data)); // merge

Обратите внимание, что слияние подобно этому задает свойства напрямую, поэтому если setLabel выполняет некоторые проверки, это не будет сделано таким образом.

Ответ 3

Насколько я знаю, это означает переход от JSON; вы теперь настраиваете его, и поэтому вы берете на себя все потенциальные головные боли, которые влекут за собой. Идея JSON заключается в том, чтобы включать только данные, а не код, чтобы избежать всех проблем безопасности, возникающих при включении кода. Разрешение кода означает, что вы должны использовать eval для запуска этого кода, а eval - зло.

Ответ 4

Если вы хотите использовать сеттеры Obj:

Obj.createFromJSON = function(json){
   if(typeof json === "string") // if json is a string
      json = JSON.parse(json); // we convert it to an object
   var obj = new Obj(), setter; // we declare the object we will return
   for(var key in json){ // for all properties
      setter = "set"+key[0].toUpperCase()+key.substr(1); // we get the name of the setter for that property (e.g. : key=property => setter=setProperty
      // following the OP comment, we check if the setter exists :
      if(setter in obj){
         obj[setter](json[key]); // we call the setter
      }
      else{ // if not, we set it directly
         obj[key] = json[key];
      }
   }
   return obj; // we finally return the instance
};

Это требует, чтобы ваш класс имел сеттеры для всех своих свойств. Этот метод является статическим, поэтому вы можете использовать это:

var instance = Obj.createFromJSON({"label":"MyLabel"});
var instance2 = Obj.createFromJSON('{"label":"MyLabel"}');

Ответ 5

Попробуйте использовать toString для метода.

Update:

Переходите к методам в obj и сохраните их как строку, а затем создайте их с помощью новой функции.

storedFunc = Obj.prototype.setLabel.toString();
Obj2.prototype['setLabel'] = new Function("return (" + storedFunc + ")")();

Ответ 6

Вам нужно будет написать собственный метод stringify, который сохраняет функции как свойства, преобразовывая их в строки с помощью метода toString.

Ответ 7

JavaScript - это прототип языка программирования, который является бесклассовым языком, где объектная ориентация достигается путем клонирования существующих объектов, которые служат прототипами.

Сериализация JSON будет рассматривать любые методы, например, если у вас есть объект

var x = {
    a: 4
    getText: function() {
       return x.a;
    }
};

Вы получите только { a:4 }, где метод getText пропускается сериализатором.

Я столкнулся с этой же проблемой год назад, и мне пришлось поддерживать отдельный класс-помощник для каждого моего объекта домена и использовать $.extend() его к моему десериализованному объекту, когда это необходимо, просто больше похоже на использование методов базовому классу для объектов домена.