Parsing String JSON menjadi Prototipe Objek Tertentu dalam JavaScript

173

Saya tahu cara mengurai String JSON dan mengubahnya menjadi Obyek JavaScript. Anda dapat menggunakan JSON.parse()di browser modern (dan IE9 +).

Itu hebat, tetapi bagaimana saya bisa mengambil Objek JavaScript itu dan mengubahnya menjadi Objek JavaScript tertentu (yaitu dengan prototipe tertentu)?

Misalnya, anggap Anda memiliki:

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

Sekali lagi, saya tidak bertanya-tanya bagaimana cara mengubah string JSON menjadi Obyek JavaScript umum. Saya ingin tahu cara mengubah string JSON menjadi Obyek "Foo". Artinya, Objek saya sekarang harus memiliki fungsi 'tes' dan properti 'a' dan 'b'.

PEMBARUAN Setelah melakukan riset, saya memikirkan hal ini ...

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);

Apakah itu akan berhasil?

UPDATE Mei, 2017 : Cara "modern" untuk melakukan ini, adalah melalui Object.assign, tetapi fungsi ini tidak tersedia di IE 11 atau lebih lama browser Android.

BMiner
sumber

Jawaban:

124

Jawaban saat ini berisi banyak linting tangan atau kode perpustakaan. Ini tidak perlu.

  1. Gunakan JSON.parse('{"a":1}')untuk membuat objek polos.

  2. Gunakan salah satu fungsi standar untuk mengatur prototipe:

    • Object.assign(new Foo, { a: 1 })
    • Object.setPrototypeOf({ a: 1 }, Foo.prototype)
Erik van Velzen
sumber
2
Object.assign tidak tersedia di browser lama termasuk IE dan browser Android lama. kangax.github.io/compat-table/es6/…
BMiner
5
Ada juga peringatan besar untuk tidak menggunakannya Object.setPrototypeOf(...). developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
christo8989
@SimonEpskamp Kode itu tidak berfungsi. Periksa url Anda, parameter kedua setPrototypeOfadalah deskriptor properti.
Erik van Velzen
6
Solusi dengan pengaturan prototipe tidak berfungsi jika ada beberapa properti yang juga perlu memiliki prototipe. Dengan kata lain: itu hanya memecahkan level pertama hirarki data.
Vojta
2
lihat solusi saya di bawah ini yang menggunakan Object.assign (..) secara rekursif yang dapat secara otomatis menyelesaikan properti (dengan sedikit informasi yang disediakan sebelumnya)
vir us
73

Lihat contoh di bawah ini (contoh ini menggunakan objek JSON asli). Perubahan saya dikomentari dalam MODAL:

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
Oliver Moran
sumber
Saya kira Anda bisa melakukan "kebalikan" dari ini, juga. Buat Obyek Foo kosong dan salin properti dari fooJSON ke Objek Foo baru. Akhirnya, atur fooJSON untuk menunjuk ke Objek Foo.
BMiner
8
Ini sangat berbahaya. Jika obj memiliki atribut yang tidak dalam definisi Foo, Anda akan membuat objek Foo dengan properti tersembunyi tambahan yang Anda tidak tahu namanya ... Alih-alih loop saya hanya akan melakukan: this.a = obj. a dan this.b = obj.b. Atau secara langsung saya akan memberikan "a" dan "b" sebagai parameter: new Foo (obj.a, obj.b)
Gabriel Llamas
2
Saran GagleKas patut didengarkan. (Meskipun "sangat berbahaya" adalah OTT kecil.) Contoh di atas hanya untuk memberi Anda ide. Implementasi yang benar akan tergantung pada aplikasi Anda.
Oliver Moran
11
Anda mungkin ingin melindungi diri dari properti prototipe. for (var prop in obj) {if (obj.hasOwnProperty(prop)) {this[prop] = obj[prop];}}
Romain Vergnory
3
@RomainVergnory Untuk lebih aman, saya sifat hanya initialize dibuat dalam constructor, ini bukan obj: for (var prop in obj) {if (this.hasOwnProperty(prop)) {this[prop] = obj[prop];}}. Ini mengasumsikan Anda mengharapkan server untuk mengisi semua properti, IMO juga harus membuang jika obj.hasOwnProperty () gagal ...
tekHedd
42

Apakah Anda ingin menambahkan fungsionalitas serialisasi / deserialisasi JSON, bukan? Kemudian lihat ini:

Anda ingin mencapai ini:

UML

toJson () adalah metode normal.
fromJson () adalah metode statis.

Implementasi :

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);
};

Penggunaan :

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

Catatan: Jika Anda ingin Anda dapat mengubah semua definisi properti seperti this.title, this.author, dll dengan var title, var author, dll dan menambahkan getter kepada mereka untuk mencapai definisi UML.

Gabriel Llamas
sumber
4
Saya setuju. Implementasi ini pasti akan berhasil, dan ini hebat ... hanya sedikit bertele-tele dan spesifik untuk Obyek Buku. IMHO, kekuatan JS berasal dari prototipe dan kemampuan untuk memiliki beberapa properti tambahan jika Anda mau. Itu saja yang saya katakan. Saya benar-benar mencari satu-baris: x .__ proto__ = X.prototype; (walaupun ini tidak kompatibel dengan browser IE saat ini)
BMiner
4
Jangan lupa bahwa toJson()metode Anda - terlepas dari apakah itu memiliki properti individual yang di-hardcode atau menggunakan masing-masing - perlu menambahkan kode escape backslash untuk beberapa karakter yang bisa berada di setiap properti string. (Judul buku mungkin memiliki tanda kutip, misalnya.)
nnnnnn
1
Ya, saya tahu, jawaban saya adalah contoh dan jawaban terbaik untuk pertanyaan itu, tetapi ... bahkan bukan poin positif ... Saya tidak tahu mengapa saya membuang waktu untuk membantu orang lain
Gabriel Llamas
7
Hari ini saya akan menggunakan JSON.stringify()alih-alih menulis ke JSon () sendiri. Tidak perlu menemukan kembali roda sekarang karena semua browser modern mendukungnya.
batu
2
Setuju dengan @skypecakes. Jika Anda hanya ingin membuat cerita bersambung suatu subset dari properti, buat konstanta dari properti yang dapat diserialisasi. serializable = ['title', 'author', ...]. JSON.stringify(serializable.reduce((obj, prop) => {...obj, [prop]: this[prop]}, {}))
Atticus
19

Posting blog yang menurut saya berguna: Memahami Prototipe JavaScript

Anda dapat mengacaukan properti __proto__ dari Object.

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

Ini memungkinkan fooJSON untuk mewarisi prototipe Foo.

Saya tidak berpikir ini bekerja di IE, meskipun ... setidaknya dari apa yang saya baca.

BMiner
sumber
2
Sebenarnya, hal seperti itu adalah insting pertamaku.
Oliver Moran
14
Catatan yang __proto__sudah lama ditinggalkan . Selain itu, untuk alasan kinerja, tidak disarankan untuk memodifikasi properti internal [[Prototipe]] dari objek yang sudah dibuat (dengan mengatur __proto__atau dengan cara lain).
Yu Asakusa
1
Sayangnya, tidak ada solusi yang sebenarnya tidak usang yang jauh lebih kompleks dari ini ...
Wim Leers
Saya telah melakukan beberapa pengujian kinerja perubahan [[prototype]]dan tampaknya tidak relevan di Chrome. Dalam firefox, panggilan baru lebih lambat daripada menggunakan prototipe, dan Object.create lebih cepat. Saya kira masalah dengan FF adalah bahwa tes pertama lebih lambat dari yang terakhir, hanya masalah eksekusi saja. Di chrome semuanya berjalan dengan kecepatan yang hampir sama. Maksud saya akses properti dan penempatan metode. Creatin lebih cepat dengan yang baru, tetapi itu tidak begitu penting. lihat: jsperf.com/prototype-change-test-8874874/1 dan: jsperf.com/prototype-changed-method-call
Bogdan Mart
4
Saya kira akhir-akhir ini, seseorang akan menelepon Object.setPrototypeOf(fooJSON, Foo.prototype)bukannya menetapkan fooJSON.__proto__... kan?
stakx - tidak lagi berkontribusi
11

Apakah saya melewatkan sesuatu dalam pertanyaan atau mengapa tidak ada yang menyebutkan reviverparameterJSON.parse sejak 2011?

Berikut adalah kode sederhana untuk solusi yang berfungsi: 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: Pertanyaan Anda membingungkan: >> Itu bagus, tapi bagaimana saya bisa mengambil Objek JavaScript itu dan mengubahnya menjadi Obyek JavaScript tertentu (yaitu dengan prototipe tertentu)? bertentangan dengan judul, di mana Anda bertanya tentang parsing JSON, tetapi paragraf yang dikutip menanyakan tentang penggantian prototipe objek runtime JS.

Philipp Munin
sumber
3

Pendekatan alternatif bisa digunakan Object.create. Sebagai argumen pertama, Anda meneruskan prototipe, dan untuk argumen kedua Anda meneruskan peta nama properti ke deskriptor:

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);

Fidemraizer Matías
sumber
3

Saya suka menambahkan argumen opsional ke konstruktor dan panggilan Object.assign(this, obj), lalu menangani properti apa pun yang merupakan objek atau array objek itu sendiri:

constructor(obj) {
    if (obj != null) {
        Object.assign(this, obj);
        if (this.ingredients != null) {
            this.ingredients = this.ingredients.map(x => new Ingredient(x));
        }
    }
}
Jason Goemaat
sumber
2

Demi kelengkapan, berikut ini adalah satu-liner sederhana yang saya dapatkan (saya tidak perlu memeriksa properti non-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 }'));
rampok
sumber
2

Saya membuat paket yang disebut json-dry . Ini mendukung referensi (melingkar) dan juga instance kelas.

Anda harus mendefinisikan 2 metode baru di kelas Anda ( toDrypada prototipe dan unDrysebagai metode statis), daftarkan kelas ( Dry.registerClass), dan pergilah.

skerit
sumber
1

Sementara, ini bukan apa yang Anda inginkan secara teknis, jika Anda tahu sebelumnya jenis objek yang ingin Anda tangani, Anda dapat menggunakan metode panggilan / terapkan dari prototipe objek yang diketahui.

Anda bisa mengubahnya

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

untuk ini

alert(Foo.prototype.test.call(fooJSON); //Prints 12
Remus
sumber
1

Saya telah menggabungkan solusi yang saya dapat menemukan dan mengkompilasinya menjadi generik yang secara otomatis dapat mem-parsing objek kustom dan semua bidang itu secara rekursif sehingga Anda dapat menggunakan metode prototipe setelah deserialization.

Salah satu anggapan adalah bahwa Anda mendefinisikan suatu arsip khusus yang menunjukkan jenisnya di setiap objek yang ingin Anda terapkan jenisnya secara otomatis ( this.__typedalam contoh).

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;
    }
}

pemakaian:

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

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

Jenis fungsi penugasan (ini bekerja secara rekursif untuk menetapkan jenis ke objek bersarang; ia juga beralih melalui array untuk menemukan objek yang cocok):

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);
}
virus
sumber
0

Jawaban yang diterima saat ini tidak berfungsi untuk saya. Anda harus menggunakan Object.assign () dengan benar:

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`;
    }
}

Anda membuat objek dari kelas ini secara normal:

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

Jika Anda memiliki string json yang perlu Anda parsing ke dalam kelas Person, lakukan seperti ini:

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
Luke
sumber
-3

Jawaban Olivers sangat jelas, tetapi jika Anda mencari solusi dalam js angular, saya telah menulis modul yang bagus yang disebut Angular-jsClass yang memudahkan ini, memiliki objek yang didefinisikan dalam notasi litaral selalu buruk ketika Anda bertujuan untuk proyek besar tetapi mengatakan bahwa pengembang menghadapi masalah yang persis kata BMiner, bagaimana membuat serial json ke prototipe atau objek notasi konstruktor

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

imal hasaranga perera
sumber