Розбір рядка 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). Мої зміни коментуються в CAPITALS:

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 Object. Нарешті, встановіть fooJSON, щоб вказати на об'єкт Foo.
BMiner

8
Це дуже небезпечно. Якщо у obj є атрибут, який відсутній у визначенні 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];}}
Ромен Вернорі

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
Я згоден. Ця реалізація, безумовно, спрацює, і це чудово ... лише трохи висловлюється та характерно для «Книжного об’єкта». IMHO, потужність JS походить від прототипів і можливість мати деякі додаткові властивості, якщо ви хочете. Це все, що я говорю. Я дуже шукав однолінійку: x .__ proto__ = X.prototype; (хоча
наразі

4
Не забувайте, що ваш toJson()метод - незалежно від того, чи він має окремі властивості жорстко кодований, або використовує для кожного - потрібно буде додати коди евакуації зворотної косої риси для деяких символів, які можуть бути у кожному властивості рядка. (Назви книги можуть мати лапки, наприклад.)
nnnnnn

1
Так, я знаю, моя відповідь була прикладом і найкращою відповіддю на питання, але ... навіть не позитивний момент ... Я не знаю, на що я витрачаю свій час, допомагаючи іншим
Габріель Ламас

7
Цими днями я б використовував JSON.stringify()замість того, щоб писати toJSon () сам. Немає необхідності винаходити колесо зараз, коли всі сучасні браузери його підтримують.
камінь

2
Погоджено з @skypecakes. Якщо ви хочете серіалізувати лише підмножину властивостей, створіть константу серіалізаційних властивостей. serializable = ['title', 'author', ...]. JSON.stringify(serializable.reduce((obj, prop) => {...obj, [prop]: this[prop]}, {}))
Atticus

19

Повідомлення в блозі, яке мені здалося корисним: Розуміння прототипів JavaScript

Ви можете возитися із властивістю __proto__ об'єкта.

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

Це дозволяє fooJSON успадкувати прототип Foo.

Я не думаю, що це працює в IE, хоча ... принаймні з того, що я прочитав.


2
Власне, щось подібне було моїм першим інстинктом.
Олівер Моран

14
Зауважте, що __proto__давно застаріло . Більше того, з міркувань продуктивності не рекомендується змінювати внутрішню властивість [[прототипу]] вже створеного об'єкта (шляхом встановлення __proto__або будь-яким іншим способом).
Ю. Асакуса

1
На жаль, жодне з насправді не застарілих рішень набагато складніше, ніж це…
Вім Лерс,

Я зробив кілька тестів працездатності на зміну, [[prototype]]і це, здається, не має ніякого значення в Chrome. У Firefox виклик нового відбувається повільніше, ніж використання прототипу, а 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як статичний метод), зареєструвати клас ( Dry.registerClass) та вийти.


1

Хоча технічно це не те, що ви хочете, якщо ви знаєте заздалегідь тип об'єкта, яким ви хочете обробити, ви можете використовувати методи виклику / застосування прототипу вашого відомого об'єкта.

ви можете це змінити

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

Відповіді Оліверса дуже зрозумілі, але якщо ви шукаєте рішення в кутовому 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.