Разобрать строку 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.


Ответы:


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, хотя ... по крайней мере из того, что я прочитал.


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) и все готово.


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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.