Разобрать строку JSON в прототип конкретного объекта в JavaScript

173

Я знаю, как разобрать строку JSON и превратить ее в объект JavaScript. Вы можете использовать JSON.parse()в современных браузерах (и IE9 +).

Это здорово, но как я могу взять этот объект JavaScript и превратить его в определенный объект JavaScript (то есть с определенным прототипом)?

Например, предположим, у вас есть:

function Foo()
{
   this.a = 3;
   this.b = 2;
   this.test = function() {return this.a*this.b;};
}
var fooObj = new Foo();
alert(fooObj.test() ); //Prints 6
var fooJSON = JSON.parse({"a":4, "b": 3});
//Something to convert fooJSON into a Foo Object
//....... (this is what I am missing)
alert(fooJSON.test() ); //Prints 12

Опять же, мне не интересно, как преобразовать строку JSON в общий объект JavaScript. Я хочу знать, как преобразовать строку JSON в объект "Foo". То есть мой объект теперь должен иметь функцию 'test' и свойства 'a' и 'b'.

ОБНОВЛЕНИЕ После некоторых исследований, я подумал об этом ...

Object.cast = function cast(rawObj, constructor)
{
    var obj = new constructor();
    for(var i in rawObj)
        obj[i] = rawObj[i];
    return obj;
}
var fooJSON = Object.cast({"a":4, "b": 3}, Foo);

Будет ли это работать?

ОБНОВЛЕНИЕ Май 2017 : «Современный» способ сделать это - через Object.assign, но эта функция недоступна в IE 11 или более старых браузерах Android.

BMiner
источник

Ответы:

124

Текущие ответы содержат много ручного или библиотечного кода. Это не обязательно.

  1. Используйте JSON.parse('{"a":1}')для создания простого объекта.

  2. Используйте одну из стандартизированных функций для установки прототипа:

    • Object.assign(new Foo, { a: 1 })
    • Object.setPrototypeOf({ a: 1 }, Foo.prototype)
Эрик ван Вельзен
источник
2
Object.assign недоступен в старых браузерах, включая IE и в старых браузерах Android. kangax.github.io/compat-table/es6/…
BMiner
5
Есть также большое предупреждение против использования Object.setPrototypeOf(...). developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
christo8989
@SimonEpskamp Этот код не работает. Проверьте ваш URL, вторым параметром setPrototypeOfявляются дескрипторы свойств.
Эрик ван
6
Решение с установкой прототипа не работает, если есть свойство, которое также должно иметь прототип. Другими словами: он решает только первый уровень иерархии данных.
Войта
2
посмотрите мое решение ниже, которое применяет Object.assign (..) рекурсивно, который может автоматически разрешать свойства (с небольшим количеством информации, предоставленной заранее)
vir us
73

См. Пример ниже (в этом примере используется собственный объект JSON). Мои изменения прокомментированы ЗАГЛАВНЫМИ БУКВАМИ:

function Foo(obj) // CONSTRUCTOR CAN BE OVERLOADED WITH AN OBJECT
{
    this.a = 3;
    this.b = 2;
    this.test = function() {return this.a*this.b;};

    // IF AN OBJECT WAS PASSED THEN INITIALISE PROPERTIES FROM THAT OBJECT
    for (var prop in obj) this[prop] = obj[prop];
}

var fooObj = new Foo();
alert(fooObj.test() ); //Prints 6

// INITIALISE A NEW FOO AND PASS THE PARSED JSON OBJECT TO IT
var fooJSON = new Foo(JSON.parse('{"a":4,"b":3}'));

alert(fooJSON.test() ); //Prints 12
Оливер Моран
источник
Я полагаю, что вы могли бы сделать "противоположность", а также. Создайте пустой объект Foo и скопируйте свойства из fooJSON в новый объект Foo. Наконец, установите fooJSON так, чтобы он указывал на объект Foo.
BMiner
8
Это очень опасно. Если объект имеет атрибут, которого нет в определении Foo, вы создадите объект Foo с дополнительным скрытым свойством, имя которого вы не знаете ... Вместо цикла я просто сделаю: this.a = obj. a и this.b = obj.b. Или непосредственно я передал бы «a» и «b» в качестве параметров: новый Foo (obj.a, obj.b)
Габриэль Ламас
2
Совет GagleKas стоит прислушаться. (Хотя «очень опасно» - это небольшой ОТТ.) Приведенный выше пример просто для того, чтобы дать вам представление. Правильная реализация будет зависеть от вашего приложения.
Оливер Моран
11
Возможно, вы захотите защитить себя от свойств прототипа. for (var prop in obj) {if (obj.hasOwnProperty(prop)) {this[prop] = obj[prop];}}
Romain Vergnory
3
@RomainVergnory Для еще больше безопасности, я только инициализировать свойства , созданные в конструкторе, это вместо OBJ: for (var prop in obj) {if (this.hasOwnProperty(prop)) {this[prop] = obj[prop];}}. Это предполагает, что вы ожидаете, что сервер заполнит все свойства, IMO также должен выдать, если obj.hasOwnProperty () завершится ошибкой ...
tekHedd
42

Хотите добавить функциональность сериализации / десериализации JSON, верно? Тогда посмотрите на это:

вы хотите добиться этого:

UML

toJson () - нормальный метод.
fromJson () - статический метод.

Реализация :

var Book = function (title, author, isbn, price, stock){
    this.title = title;
    this.author = author;
    this.isbn = isbn;
    this.price = price;
    this.stock = stock;

    this.toJson = function (){
        return ("{" +
            "\"title\":\"" + this.title + "\"," +
            "\"author\":\"" + this.author + "\"," +
            "\"isbn\":\"" + this.isbn + "\"," +
            "\"price\":" + this.price + "," +
            "\"stock\":" + this.stock +
        "}");
    };
};

Book.fromJson = function (json){
    var obj = JSON.parse (json);
    return new Book (obj.title, obj.author, obj.isbn, obj.price, obj.stock);
};

Использование :

var book = new Book ("t", "a", "i", 10, 10);
var json = book.toJson ();
alert (json); //prints: {"title":"t","author":"a","isbn":"i","price":10,"stock":10}

var book = Book.fromJson (json);
alert (book.title); //prints: t

Примечание: Если вы хотите , вы можете изменить все определения свойств , таких как this.title, this.authorи т.д. путем var title, var authorи т.д. , и добавить к ним добытчиками , чтобы выполнить определение UML.

Габриэль Ламас
источник
4
Я согласен. Эта реализация определенно будет работать, и она великолепна ... просто немного словесна и специфична для объекта Book. ИМХО, сила JS исходит от прототипов и способности иметь некоторые дополнительные свойства, если вы хотите. Это все, что я говорю. Я действительно искал однострочник: x .__ proto__ = X.prototype; (хотя это не браузер, совместимый с IE в настоящее время)
BMiner
4
Не забывайте, что ваш toJson()метод - независимо от того, имеет ли он отдельные свойства, жестко закодированные или использует для каждого из них - должен будет добавить escape-коды обратной косой черты для некоторых символов, которые могут быть в каждом строковом свойстве. (Например, название книги может иметь кавычки.)
nnnnnn
1
Да, я знаю, мой ответ был примером и лучшим ответом на вопрос, но ... даже не положительным моментом ... Я не знаю, почему я трачу свое время, помогая другим
Габриэль Ламас
7
В эти дни я бы использовал JSON.stringify()вместо того, чтобы писать самому jSon (). Не нужно изобретать велосипед сейчас, когда все современные браузеры его поддерживают.
Камень
2
Согласился с @skypecakes. Если вы хотите сериализовать только подмножество свойств, создайте константу сериализуемых свойств. serializable = ['title', 'author', ...], JSON.stringify(serializable.reduce((obj, prop) => {...obj, [prop]: this[prop]}, {}))
Аттик
19

Сообщение в блоге, которое я нашел полезным: Понимание прототипов JavaScript

Вы можете связываться со свойством __proto__ объекта.

var fooJSON = jQuery.parseJSON({"a":4, "b": 3});
fooJSON.__proto__ = Foo.prototype;

Это позволяет fooJSON наследовать прототип Foo.

Я не думаю, что это работает в IE, хотя ... по крайней мере из того, что я прочитал.

BMiner
источник
2
На самом деле, что-то подобное было моим первым инстинктом.
Оливер Моран
14
Обратите внимание, что __proto__давно устарела . Более того, из соображений производительности не рекомендуется изменять внутреннее свойство [[Prototype]] уже созданного объекта (путем установки __proto__или любым другим способом).
Ю Асакуса
1
Увы, ни одно из действительно недревесных решений не является более сложным, чем это…
Вим Лирс
Я сделал несколько тестов производительности изменения, [[prototype]]и это, кажется, не имеет значения в Chrome. В Firefox вызов new медленнее, чем при использовании прототипа, а Object.create - самый быстрый. Я предполагаю, что проблема с FF заключается в том, что первый тест медленнее, чем последний, важен только порядок выполнения. В хроме все работает практически с одинаковой скоростью. Я имею в виду доступ к свойствам и вызов методов. Креатин быстрее с новым, но это не так важно. см: jsperf.com/prototype-change-test-8874874/1 и: jsperf.com/prototype-changed-method-call
Богдан Март
4
Полагаю, в наши дни можно было бы позвонить, Object.setPrototypeOf(fooJSON, Foo.prototype)а не устанавливать fooJSON.__proto__... верно?
stakx - больше не участвует
11

Я что-то упускаю в вопросе или почему еще никто не упомянул reviverпараметр JSON.parseс 2011 года?

Вот упрощенный код для решения, которое работает: https://jsfiddle.net/Ldr2utrr/

function Foo()
{
   this.a = 3;
   this.b = 2;
   this.test = function() {return this.a*this.b;};
}


var fooObj = new Foo();
alert(fooObj.test() ); //Prints 6
var fooJSON = JSON.parse(`{"a":4, "b": 3}`, function(key,value){
if(key!=="") return value; //logic of course should be more complex for handling nested objects etc.
  let res = new Foo();
  res.a = value.a;
  res.b = value.b;
  return res;
});
// Here you already get Foo object back
alert(fooJSON.test() ); //Prints 12

PS: Ваш вопрос сбивает с толку: >> Это здорово, но как я могу взять этот объект JavaScript и превратить его в определенный объект JavaScript (т.е. с определенным прототипом)? противоречит названию, где вы спрашиваете о разборе JSON, но в цитируемом абзаце спрашивается о замене прототипа объекта времени выполнения JS.

Филипп Мунин
источник
3

Альтернативный подход может быть использование Object.create. В качестве первого аргумента вы передаете прототип, а для второго вы передаете карту имен свойств дескрипторам:

function SomeConstructor() {
  
};

SomeConstructor.prototype = {
  doStuff: function() {
      console.log("Some stuff"); 
  }
};

var jsonText = '{ "text": "hello wrold" }';
var deserialized = JSON.parse(jsonText);

// This will build a property to descriptor map
// required for #2 argument of Object.create
var descriptors = Object.keys(deserialized)
  .reduce(function(result, property) {
    result[property] = Object.getOwnPropertyDescriptor(deserialized, property);
  }, {});

var obj = Object.create(SomeConstructor.prototype, descriptors);

Матиас Фидемрайзер
источник
3

Мне нравится добавлять необязательный аргумент в конструктор и вызывать Object.assign(this, obj), а затем обрабатывать любые свойства, которые являются объектами или массивами самих объектов:

constructor(obj) {
    if (obj != null) {
        Object.assign(this, obj);
        if (this.ingredients != null) {
            this.ingredients = this.ingredients.map(x => new Ingredient(x));
        }
    }
}
Джейсон Гомаат
источник
2

Для полноты, вот простая однострочная строка, с которой я закончил (мне не нужно было проверять не-Foo-свойства):

var Foo = function(){ this.bar = 1; };

// angular version
var foo = angular.extend(new Foo(), angular.fromJson('{ "bar" : 2 }'));

// jquery version
var foo = jQuery.extend(new Foo(), jQuery.parseJSON('{ "bar" : 3 }'));
обкрадывать
источник
2

Я создал пакет под названием json-dry . Он поддерживает (круговые) ссылки, а также экземпляры классов.

Вы должны определить 2 новых метода в вашем классе ( toDryна прототипе и unDryв качестве статического метода), зарегистрировать class ( Dry.registerClass) и все готово.

skerit
источник
1

Хотя технически это не то, что вам нужно, если вы заранее знаете тип объекта, который хотите обработать, вы можете использовать методы call / apply прототипа вашего известного объекта.

Вы можете изменить это

alert(fooJSON.test() ); //Prints 12

к этому

alert(Foo.prototype.test.call(fooJSON); //Prints 12
Рем
источник
1

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

Одно из предположений заключается в том, что вы определили специальное поле, которое указывает его тип для каждого объекта, к которому вы хотите применить его тип автоматически ( this.__typeв примере).

function Msg(data) {
    //... your init code
    this.data = data //can be another object or an array of objects of custom types. 
                     //If those objects defines `this.__type', their types will be assigned automatically as well
    this.__type = "Msg"; // <- store the object's type to assign it automatically
}

Msg.prototype = {
    createErrorMsg: function(errorMsg){
        return new Msg(0, null, errorMsg)
    },
    isSuccess: function(){
        return this.errorMsg == null;
    }
}

использование:

var responseMsg = //json string of Msg object received;
responseMsg = assignType(responseMsg);

if(responseMsg.isSuccess()){ // isSuccess() is now available
      //furhter logic
      //...
}

Функция присваивания типов (она работает рекурсивно для назначения типов любым вложенным объектам; она также перебирает массивы для поиска любых подходящих объектов):

function assignType(object){
    if(object && typeof(object) === 'object' && window[object.__type]) {
        object = assignTypeRecursion(object.__type, object);
    }
    return object;
}

function assignTypeRecursion(type, object){
    for (var key in object) {
        if (object.hasOwnProperty(key)) {
            var obj = object[key];
            if(Array.isArray(obj)){
                 for(var i = 0; i < obj.length; ++i){
                     var arrItem = obj[i];
                     if(arrItem && typeof(arrItem) === 'object' && window[arrItem.__type]) {
                         obj[i] = assignTypeRecursion(arrItem.__type, arrItem);
                     }
                 }
            } else  if(obj && typeof(obj) === 'object' && window[obj.__type]) {
                object[key] = assignTypeRecursion(obj.__type, obj);
            }
        }
    }
    return Object.assign(new window[type](), object);
}
вирус
источник
0

В настоящее время принятый ответ не работает для меня. Вам необходимо правильно использовать Object.assign ():

class Person {
    constructor(name, age){
        this.name = name;
        this.age = age;
    }

    greet(){
        return `hello my name is ${ this.name } and i am ${ this.age } years old`;
    }
}

Вы создаете объекты этого класса нормально:

let matt = new Person('matt', 12);
console.log(matt.greet()); // prints "hello my name is matt and i am 12 years old"

Если у вас есть строка json, которую вам нужно проанализировать в классе Person, сделайте это так:

let str = '{"name": "john", "age": 15}';
let john = JSON.parse(str); // parses string into normal Object type

console.log(john.greet()); // error!!

john = Object.assign(Person.prototype, john); // now john is a Person type
console.log(john.greet()); // now this works
Люк
источник
-3

Ответы Оливерса очень ясны, но если вы ищете решение в angular js, я написал хороший модуль под названием Angular-jsClass, который облегчает эту задачу: объекты, определенные в литеральной нотации, всегда плохи, когда вы стремитесь к большому проекту. но, говоря о том, что разработчики сталкиваются с проблемой, о которой говорит BMiner, как сериализовать json для прототипа или конструктора нотационных объектов

var jone = new Student();
jone.populate(jsonString); // populate Student class with Json string
console.log(jone.getName()); // Student Object is ready to use

https://github.com/imalhasaranga/Angular-JSClass

Имал Хасаранга Перера
источник