Analysieren Sie JSON-Zeichenfolgen in einen bestimmten Objektprototyp in JavaScript

173

Ich kann einen JSON-String analysieren und in ein JavaScript-Objekt verwandeln. Sie können JSON.parse()in modernen Browsern (und IE9 +) verwenden.

Das ist großartig, aber wie kann ich dieses JavaScript-Objekt in ein bestimmtes JavaScript-Objekt verwandeln (dh mit einem bestimmten Prototyp)?

Angenommen, Sie haben:

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

Auch hier frage ich mich nicht, wie man eine JSON-Zeichenfolge in ein generisches JavaScript-Objekt konvertiert. Ich möchte wissen, wie man einen JSON-String in ein "Foo" -Objekt konvertiert. Das heißt, mein Objekt sollte jetzt eine Funktion 'test' und die Eigenschaften 'a' und 'b' haben.

UPDATE Nach einigen Recherchen habe ich darüber nachgedacht ...

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

Wird es funktionieren?

UPDATE Mai 2017 : Die "moderne" Art, dies zu tun, ist via Object.assign, aber diese Funktion ist in IE 11 oder älteren Android-Browsern nicht verfügbar.

BMiner
quelle

Antworten:

124

Die aktuellen Antworten enthalten viel handgerollten oder Bibliothekscode. Dies ist nicht erforderlich.

  1. Verwenden Sie JSON.parse('{"a":1}')diese Option , um ein einfaches Objekt zu erstellen.

  2. Verwenden Sie eine der standardisierten Funktionen, um den Prototyp festzulegen:

    • Object.assign(new Foo, { a: 1 })
    • Object.setPrototypeOf({ a: 1 }, Foo.prototype)
Erik van Velzen
quelle
2
Object.assign ist in älteren Browsern, einschließlich IE und älteren Android-Browsern, nicht verfügbar. kangax.github.io/compat-table/es6/…
BMiner
5
Es gibt auch eine große Warnung vor der Verwendung Object.setPrototypeOf(...). developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
christo8989
@ SimonEpskamp Dieser Code funktioniert nicht. Überprüfen Sie Ihre URL. Der zweite Parameter setPrototypeOfsind Eigenschaftsbeschreibungen.
Erik van Velzen
6
Die Lösung zum Festlegen des Prototyps funktioniert nicht, wenn eine Eigenschaft vorhanden ist, für die auch ein Prototyp erforderlich ist. Mit anderen Worten: Es wird nur die erste Ebene der Datenhierarchie gelöst.
Vojta
2
Schauen Sie sich meine Lösung unten an, die Object.assign (..) rekursiv anwendet und Eigenschaften automatisch auflösen kann (mit ein paar Informationen, die im Voraus bereitgestellt wurden)
vir us
73

Siehe ein Beispiel unten (in diesem Beispiel wird das native JSON-Objekt verwendet). Meine Änderungen sind in GROSSBUCHSTABEN kommentiert:

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
quelle
Ich nehme an, Sie könnten auch das "Gegenteil" davon tun. Erstellen Sie ein leeres Foo-Objekt und kopieren Sie die Eigenschaften von fooJSON in das neue Foo-Objekt. Stellen Sie abschließend fooJSON so ein, dass es auf das Foo-Objekt zeigt.
BMiner
8
Das ist sehr gefährlich. Wenn das Objekt ein Attribut hat, das nicht in der Foo-Definition enthalten ist, erstellen Sie ein Foo-Objekt mit einer zusätzlichen versteckten Eigenschaft, deren Namen Sie nicht kennen ... Anstelle einer Schleife mache ich einfach: this.a = obj. a und this.b = obj.b. Oder direkt würde ich "a" und "b" als Parameter übergeben: new Foo (obj.a, obj.b)
Gabriel Llamas
2
Der Rat von GagleKas ist hörenswert. (Obwohl "sehr gefährlich" ein wenig OTT ist.) Das obige Beispiel soll Ihnen nur eine Idee geben. Die richtige Implementierung hängt von Ihrer Anwendung ab.
Oliver Moran
11
Möglicherweise möchten Sie sich vor Prototypeneigenschaften schützen. for (var prop in obj) {if (obj.hasOwnProperty(prop)) {this[prop] = obj[prop];}}
Romain Vergnory
3
@RomainVergnory Für noch mehr Sicherheit initialisiere ich nur im Konstruktor erstellte Eigenschaften, dies anstelle von obj : for (var prop in obj) {if (this.hasOwnProperty(prop)) {this[prop] = obj[prop];}}. Dies setzt voraus, dass Sie erwarten, dass der Server alle Eigenschaften auffüllt. IMO sollte auch auslösen, wenn obj.hasOwnProperty () fehlschlägt ...
tekHedd
42

Möchten Sie JSON-Serialisierungs- / Deserialisierungsfunktionen hinzufügen? Dann schauen

Sie sich Folgendes an : Sie möchten dies erreichen:

UML

toJson () ist eine normale Methode.
fromJson () ist eine statische Methode.

Implementierung :

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

Verwendung :

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

Hinweis: Wenn Sie möchten , können Sie alle Eigenschaftsdefinitionen wie ändern this.title, this.authorusw. durch var title, var authorusw. und fügen Sie Getter ihnen die UML - Definition zu erreichen.

Gabriel Llamas
quelle
4
Genau. Diese Implementierung wird auf jeden Fall funktionieren und ist großartig ... nur ein wenig wortreich und spezifisch für das Buchobjekt. IMHO kommt die Kraft von JS von Prototypen und der Fähigkeit, einige zusätzliche Eigenschaften zu haben, wenn Sie möchten. Das ist alles was ich sage. Ich habe wirklich nach dem Einzeiler gesucht: x .__ proto__ = X.prototype; (obwohl es
derzeit
4
Vergessen Sie nicht, dass Ihre toJson()Methode - unabhängig davon, ob einzelne Eigenschaften fest codiert sind oder für jede eine verwendet - Backslash-Escape-Codes für einige Zeichen hinzufügen muss, die in jeder Zeichenfolgeeigenschaft enthalten sein können. (Ein
Buchtitel
1
Ja, ich weiß, meine Antwort war ein Beispiel und die beste Antwort auf die Frage, aber ... nicht einmal ein positiver Punkt ... Ich weiß nicht, warum ich meine Zeit damit verschwende, anderen zu helfen
Gabriel Llamas
7
JSON.stringify()Heutzutage würde ich verwenden, anstatt selbst an JSon () zu schreiben. Das Rad muss jetzt nicht neu erfunden werden, da alle modernen Browser es unterstützen.
Stein
2
Einverstanden mit @skypecakes. Wenn Sie nur eine Teilmenge von Eigenschaften serialisieren möchten, erstellen Sie eine Konstante von serialisierbaren Eigenschaften. serializable = ['title', 'author', ...]. JSON.stringify(serializable.reduce((obj, prop) => {...obj, [prop]: this[prop]}, {}))
Atticus
18

Ein Blog-Beitrag, den ich nützlich fand: Grundlegendes zu JavaScript-Prototypen

Sie können mit der Eigenschaft __proto__ des Objekts herumspielen.

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

Dadurch kann fooJSON den Foo-Prototyp erben.

Ich glaube nicht, dass dies im IE funktioniert ... zumindest nach dem, was ich gelesen habe.

BMiner
quelle
2
Eigentlich war so etwas mein erster Instinkt.
Oliver Moran
14
Beachten Sie, dass dies __proto__seit langem veraltet ist . Darüber hinaus wird aus Leistungsgründen nicht empfohlen, die interne Eigenschaft eines Prototyps eines bereits erstellten Objekts zu ändern (durch Festlegen __proto__oder auf andere Weise).
Yu Asakusa
1
Leider ist keine der tatsächlich nicht veralteten Lösungen weitaus komplexer als diese ...
Wim Leers
Ich habe einige Tests zur Leistung von Änderungen durchgeführt [[prototype]]und es scheint in Chrome irrelevant zu sein. In Firefox ist das Aufrufen von new langsamer als die Verwendung von Prototypen, und Object.create ist am schnellsten. Ich denke, das Problem mit FF ist, dass der erste Test langsamer ist als der letzte, nur die Reihenfolge der Ausführung ist wichtig. In Chrom läuft alles fast gleich schnell. Ich meine den Zugang zu Eigentum und die Aufforderung von Methoden. Kreatin ist mit neuen schneller, aber das ist nicht so wichtig. siehe: jsperf.com/prototype-change-test-8874874/1 und: jsperf.com/prototype-changed-method-call
Bogdan Mart
4
Ich nehme an, heutzutage würde man anrufen, Object.setPrototypeOf(fooJSON, Foo.prototype)anstatt zu setzen fooJSON.__proto__... richtig?
stakx - nicht mehr beitragen
11

Vermisse ich etwas in der Frage oder warum hat seit 2011 niemand mehr reviverParameter erwähnt JSON.parse?

Hier ist ein vereinfachter Code für eine funktionierende Lösung: 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: Ihre Frage ist verwirrend: >> Das ist großartig, aber wie kann ich dieses JavaScript-Objekt in ein bestimmtes JavaScript-Objekt (dh mit einem bestimmten Prototyp) verwandeln? widerspricht dem Titel, in dem Sie nach dem JSON-Parsing fragen, aber der zitierte Absatz fragt nach dem Ersetzen des JS-Laufzeitobjekt-Prototyps.

Philipp Munin
quelle
3

Ein alternativer Ansatz könnte verwendet werden Object.create. Als erstes Argument übergeben Sie den Prototyp und für das zweite übergeben Sie eine Karte mit Eigenschaftsnamen an Deskriptoren:

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

Matías Fidemraizer
quelle
3

Ich mag es, dem Konstruktor ein optionales Argument hinzuzufügen und aufzurufen Object.assign(this, obj)und dann alle Eigenschaften zu behandeln, die Objekte oder Arrays von Objekten selbst sind:

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

Der Vollständigkeit halber habe ich hier einen einfachen Einzeiler gefunden (ich musste nicht nach Nicht-Foo-Eigenschaften suchen):

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 }'));
rauben
quelle
2

Ich habe ein Paket namens json-dry erstellt . Es unterstützt (Zirkel-) Referenzen und auch Klasseninstanzen.

Sie müssen 2 neue Methoden in Ihrer Klasse definieren ( toDryauf dem Prototyp und unDryals statische Methode), die Klasse registrieren ( Dry.registerClass) und los geht's.

Skerit
quelle
1

Obwohl dies technisch nicht das ist, was Sie wollen, können Sie die Call / Apply-Methoden des Prototyps Ihres bekannten Objekts verwenden, wenn Sie vorher wissen, welchen Objekttyp Sie behandeln möchten.

Sie können dies ändern

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

dazu

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

Ich habe die Lösungen, die ich finden konnte, kombiniert und zu einer generischen Lösung kompiliert, mit der ein benutzerdefiniertes Objekt und alle seine Felder rekursiv automatisch analysiert werden können, sodass Sie nach der Deserialisierung Prototypmethoden verwenden können.

Eine Annahme ist, dass Sie ein spezielles Feld definiert haben, das den Typ in jedem Objekt angibt, das Sie automatisch anwenden möchten ( this.__typeim Beispiel).

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

Verwendung:

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

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

Typzuweisungsfunktion (funktioniert rekursiv, um verschachtelten Objekten Typen zuzuweisen; sie durchläuft auch Arrays, um geeignete Objekte zu finden):

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
quelle
0

Die aktuell akzeptierte Antwort funktionierte bei mir nicht. Sie müssen Object.assign () ordnungsgemäß verwenden:

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

Sie erstellen normalerweise Objekte dieser Klasse:

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

Wenn Sie eine JSON-Zeichenfolge haben, die Sie in die Person-Klasse analysieren müssen, gehen Sie folgendermaßen vor:

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
quelle
-3

Olivers Antworten sind sehr klar, aber wenn Sie nach einer Lösung in Angular Js suchen, habe ich ein nettes Modul namens Angular-JsClass geschrieben, das dies erleichtert. Objekte in Litaral-Notation zu definieren, ist immer schlecht, wenn Sie auf ein großes Projekt abzielen Aber sagen, dass Entwickler vor einem Problem stehen, das genau BMiner gesagt hat, wie man einen JSON zu Prototyp- oder Konstruktor-Notationsobjekten serialisiert

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
quelle