Розбір рядка 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.