JavaScriptでJSON文字列を特定のオブジェクトプロトタイプに解析する


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年5月更新:これを行う「モダンな」方法はを使用する方法ですObject.assignが、この機能はIE 11以前のAndroidブラウザーでは使用できません。


回答:


124

現在の回答には、手作業で作成されたコードやライブラリコードが多数含まれています。これは必要ありません。

  1. JSON.parse('{"a":1}')プレーンオブジェクトを作成するために使用します。

  2. プロトタイプを設定するには、標準化された関数の1つを使用します。

    • 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そのコードは機能しません。setPrototypeOfプロパティ記述子である2番目のパラメーターであるURLを確認してください。
Erik van Velzen 2017

6
プロトタイプも必要なプロパティがある場合、プロトタイプを設定するソリューションは機能しません。つまり、データ階層の最初のレベルのみを解決します。
ヴォイタ2017

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
これは非常に危険です。objにFoo定義にない属性がある場合は、名前がわからない追加の非表示プロパティを持つFooオブジェクトを作成します...ループの代わりに、単純にthis.a = objを実行します。 aおよびthis.b =obj.b。または直接「a」と「b」をパラメーターとして渡します:new Foo(obj.a、obj.b)
ガブリエルラマ

2
GagleKasのアドバイスは聞く価値があります。(ただし、「非常に危険」は少しOTTです。)上記の例は、アイデアを与えるためのものです。正しい実装は、アプリケーションによって異なります。
オリバーモラン

11
プロトタイププロパティから自分を保護する必要がある場合があります。for (var prop in obj) {if (obj.hasOwnProperty(prop)) {this[prop] = obj[prop];}}
Romain Vergnory 2014

3
@RomainVergnoryさらに安全のために、コンストラクタで作成されたプロパティのみを初期化しますfor (var prop in obj) {if (this.hasOwnProperty(prop)) {this[prop] = obj[prop];}}。これはobj:ではありません。これは、サーバーがすべてのプロパティを設定すると想定しているため、obj.hasOwnProperty()が失敗した場合にもIMOがスローする必要があります...
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.titlethis.authorなどによってvar titlevar authorなどとUMLの定義を達成するためにそれらにゲッターを追加します。


4
同意する。この実装は確実に機能し、すばらしいです...少しだけ説明が豊富で、Bookオブジェクトに固有です。私見、JSの力はプロトタイプと、必要に応じていくつかの追加プロパティを持つ能力に由来します。私が言っているのはそれだけです。私は本当に一行を探していました:x .__ proto__ = X.prototype; (現時点ではIEブラウザーに対応していません)
BMiner

4
toJson()メソッドは、個々のプロパティがハードコーディングされているか、for eachを使用しているかに関係なく、各文字列プロパティに含まれる可能性がある一部の文字にバックスラッシュエスケープコードを追加する必要があることを忘れないでください。(たとえば、本のタイトルには引用符が付いている場合があります。)
nnnnnn

1
はい、私は知っている、私の答えは、私は他人を助ける私の時間を無駄になぜ私にはわからない...例および質問に対する最良の答えでしたが、...ではないとしても、正のポイント
ガブリエル・ラマ

7
最近では、JSON.stringify()自分でtoJSon()を書く代わりに使用します。最近のすべてのブラウザーでサポートされているので、ホイールを作り直す必要はありません。

2
@skypecakesに同意します。プロパティのサブセットのみをシリアル化する場合は、シリアル化可能なプロパティの定数を作成します。serializable = ['title', 'author', ...]JSON.stringify(serializable.reduce((obj, prop) => {...obj, [prop]: this[prop]}, {}))
Atticus

18

役立つブログ記事: JavaScriptプロトタイプについて

オブジェクトの__proto__プロパティをいじることができます。

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

これにより、fooJSONはFooプロトタイプを継承できます。

これはIEでは機能しないと思いますが、少なくとも私が読んだことからは。


2
実はそのようなものが最初の本能でした。
Oliver Moran

14
__proto__長い間非推奨になっていることに注意してください。さらに、パフォーマンス上の理由から、すでに設定されているオブジェクトの[[Prototype]]内部プロパティを(設定__proto__またはその他の方法で)変更することはお勧めしません。
浅草優

1
悲しいかな、実際には廃止されていないソリューションはどれもこれほど複雑ではありません…
Wim Leers

変更のパフォーマンスのテストをいくつか行いましたが[[prototype]]、Chromeではそれは無関係のようです。Firefoxでは、newの呼び出しはプロトタイプを使用するよりも遅く、Object.createが最も高速です。FFの問題は、最初のテストが最後のテストよりも遅く、実行の順序だけが問題であることだと思います。クロムでは、すべてがほぼ同じ速度で実行されます。プロパティへのアクセスとメソッドの呼び出しを意味します。新しいとクレアチンは高速ですが、それはそれほど重要ではありません。参照: jsperf.com/prototype-change-test-8874874/1およびjsperf.com/prototype-changed-method-call
Bogdan Mart

4
最近でObject.setPrototypeOf(fooJSON, Foo.prototype)は、設定する代わりに電話をかけるでしょうfooJSON.__proto__...
stakx-2017年

11

質問に何か欠けているのですか、それともなぜ2011年以降のreviverパラメータについて誰も言及しなかったのJSON.parseですか?

これが機能するソリューションの単純なコードです: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。最初の引数としてプロトタイプを渡し、2番目の引数として記述子にプロパティ名のマップを渡します。

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

完全を期すために、私が最後に作成した簡単な1行を示します(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

見つけたソリューションを組み合わせて、カスタムオブジェクトとそのすべてのフィールドを再帰的に解析できる汎用的なソリューションにコンパイルしたので、逆シリアル化後にプロトタイプメソッドを使用できます。

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"

Personクラスに解析する必要があるjson文字列がある場合は、次のようにします。

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.