Przeanalizuj ciąg JSON w konkretny prototyp obiektu w JavaScript

173

Wiem, jak przeanalizować ciąg JSON i przekształcić go w obiekt JavaScript. Możesz używać JSON.parse()w nowoczesnych przeglądarkach (i IE9 +).

To świetnie, ale jak mogę wziąć ten obiekt JavaScript i przekształcić go w konkretny obiekt JavaScript (tj. Z pewnym prototypem)?

Na przykład załóżmy, że masz:

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

Ponownie, nie zastanawiam się, jak przekonwertować ciąg JSON na ogólny obiekt JavaScript. Chcę wiedzieć, jak przekonwertować ciąg JSON na obiekt „Foo”. Oznacza to, że mój obiekt powinien teraz mieć funkcję „test” oraz właściwości „a” i „b”.

AKTUALIZACJA Po przeprowadzeniu pewnych badań pomyślałem o tym ...

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

Czy to będzie działało?

AKTUALIZACJA Maj, 2017 : „Nowoczesnym” sposobem jest użycie Object.assign, ale ta funkcja nie jest dostępna w IE 11 i starszych przeglądarkach Android.

BMiner
źródło

Odpowiedzi:

124

Bieżące odpowiedzi zawierają dużo ręcznie rozwijanego lub bibliotecznego kodu. To nie jest konieczne.

  1. Służy JSON.parse('{"a":1}')do tworzenia zwykłego obiektu.

  2. Użyj jednej ze standardowych funkcji, aby ustawić prototyp:

    • Object.assign(new Foo, { a: 1 })
    • Object.setPrototypeOf({ a: 1 }, Foo.prototype)
Erik van Velzen
źródło
2
Object. assign nie jest dostępny w starszych przeglądarkach, w tym w IE i starszych przeglądarkach Android. kangax.github.io/compat-table/es6/…
BMiner
5
Jest też duże ostrzeżenie przed używaniem Object.setPrototypeOf(...). developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/ ...
christo8989
@SimonEpskamp Ten kod nie działa. Sprawdź swój adres URL, drugim parametrem setPrototypeOfsą deskryptory właściwości.
Erik van Velzen
6
Rozwiązanie z ustawieniem prototypu nie działa, jeśli istnieje jakaś właściwość, która również musi mieć prototyp. Innymi słowy: rozwiązuje tylko pierwszy poziom hierarchii danych.
Vojta,
2
sprawdź moje rozwiązanie poniżej, które stosuje rekursywnie Object. assign (..), który może automatycznie rozwiązywać właściwości (z odrobiną informacji dostarczonych z wyprzedzeniem)
vir nas
73

Zobacz przykład poniżej (w tym przykładzie zastosowano natywny obiekt JSON). Moje zmiany są komentowane WIELKIMI LITERAMI:

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
źródło
Przypuszczam, że możesz też zrobić „przeciwieństwo” tego. Skonstruuj pusty obiekt Foo i skopiuj właściwości z fooJSON do nowego obiektu Foo. Na koniec ustaw fooJSON tak, aby wskazywał na obiekt Foo.
BMiner
8
To jest bardzo niebezpieczne. Jeśli obiekt ma atrybut, którego nie ma w definicji Foo, utworzysz obiekt Foo z dodatkową ukrytą właściwością, której nazwy nie znasz ... Zamiast pętli zrobię po prostu: this.a = obj. a and this.b = obj.b. Lub bezpośrednio podałbym „a” i „b” jako parametry: nowe Foo (obj.a, obj.b)
Gabriel Llamas
2
Warto posłuchać rad GagleKasa. (Chociaż „bardzo niebezpieczne” to trochę OTT.) Powyższy przykład ma na celu jedynie przedstawienie idei. Prawidłowa implementacja będzie zależeć od aplikacji.
Oliver Moran
11
Możesz chcieć zabezpieczyć się przed właściwościami prototypu. for (var prop in obj) {if (obj.hasOwnProperty(prop)) {this[prop] = obj[prop];}}
Romain Vergnory
3
@RomainVergnory uzyskać jeszcze większe bezpieczeństwo, ja tylko Inicjowanie właściwości utworzone w konstruktorze, ten zamiast obj: for (var prop in obj) {if (this.hasOwnProperty(prop)) {this[prop] = obj[prop];}}. Zakłada się, że oczekujesz, że serwer wypełni wszystkie właściwości, IMO powinno również zgłosić, jeśli funkcja obj.hasOwnProperty () zawiedzie ...
tekHedd
42

Czy chcesz dodać funkcję serializacji / deserializacji JSON, prawda? Następnie spójrz na to:

Chcesz to osiągnąć:

UML

toJson () to normalna metoda.
fromJson () jest metodą statyczną.

Wdrażanie :

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

Zastosowanie :

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

Uwaga: Jeśli chcesz, możesz zmienić wszystkie definicje własności jak this.title, this.authoritp przez var title, var authoritp i dodać pobierające im osiągnąć definicji UML.

Gabriel Llamas
źródło
4
Zgadzam się. Ta implementacja na pewno zadziała i jest świetna ... tylko trochę rozwlekła i specyficzna dla obiektu książki. IMHO, moc JS pochodzi z prototypów i możliwości posiadania dodatkowych właściwości, jeśli chcesz. To wszystko, co mówię. Naprawdę szukałem jednowierszowego: x .__ proto__ = X.prototype; (chociaż w tej chwili nie jest to zgodne z przeglądarką IE)
BMiner
4
Nie zapominaj, że Twoja toJson()metoda - niezależnie od tego, czy ma zakodowane na stałe indywidualne właściwości, czy używa dla każdego z nich - będzie wymagała dodania kodów ucieczki z ukośnikiem odwrotnym dla niektórych znaków, które mogą znajdować się w każdej właściwości ciągu. (Na przykład tytuł książki może zawierać cudzysłowy).
nnnnnn
1
Tak, wiem, moja odpowiedź była przykładem i najlepszą odpowiedzią na pytanie, ale ... nawet nie pozytywną kwestią ... Nie wiem, dlaczego marnuję czas na pomaganie innym
Gabriel Llamas
7
W dzisiejszych czasach JSON.stringify()zamiast pisać toJSon () sam. Nie ma potrzeby odkrywania na nowo koła, gdy obsługują go wszystkie nowoczesne przeglądarki.
kamień
2
Uzgodniono z @skypecakes. Jeśli chcesz serializować tylko podzbiór właściwości, utwórz stałą właściwości możliwych do serializacji. serializable = ['title', 'author', ...]. JSON.stringify(serializable.reduce((obj, prop) => {...obj, [prop]: this[prop]}, {}))
Atticus
19

Post na blogu, który okazał się przydatny: Understanding JavaScript Prototypes

Możesz zadzierać z właściwością __proto__ obiektu.

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

Dzięki temu fooJSON może odziedziczyć prototyp Foo.

Myślę, że to nie działa w IE, chociaż ... przynajmniej z tego, co przeczytałem.

BMiner
źródło
2
Właściwie coś takiego było moim pierwszym odruchem.
Oliver Moran
14
Zwróć uwagę, że __proto__od dawna jest przestarzały . Ponadto ze względu na wydajność nie zaleca się modyfikowania wewnętrznej właściwości [[Prototyp]] już utworzonego obiektu (poprzez ustawienie __proto__lub w jakikolwiek inny sposób).
Yu Asakusa,
1
Niestety, żadne z faktycznie nieaktualnych rozwiązań nie jest dużo bardziej złożone niż to…
Wim Leers
Wykonałem kilka testów wydajności zmiany [[prototype]]i wydaje się, że nie ma to znaczenia w Chrome. W Firefoksie wywołanie new jest wolniejsze niż używanie prototypu, a Object.create jest najszybsze. Myślę, że problem z FF polega na tym, że pierwszy test jest wolniejszy niż ostatni, tylko kolejność wykonywania ma znaczenie. W chrome wszystko działa prawie z taką samą prędkością. Mam na myśli dostęp do własności i odwoływanie się do metod. Kreatyna jest szybsza dzięki nowemu, ale to nie jest takie ważne. patrz: jsperf.com/prototype-change-test-8874874/1 oraz: jsperf.com/prototype-changed-method-call
Bogdan Mart
4
Przypuszczam, że w dzisiejszych czasach ktoś dzwoniłby Object.setPrototypeOf(fooJSON, Foo.prototype)zamiast ustawiać fooJSON.__proto__... prawda?
stakx - nie publikuje już
11

Czy czegoś mi brakuje w pytaniu lub dlaczego nikt inny nie wspomniał o reviverparametrze JSON.parseod 2011 roku?

Oto uproszczony kod rozwiązania, które działa: 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: Twoje pytanie jest niejasne: >> To świetnie, ale jak mogę wziąć ten obiekt JavaScript i przekształcić go w konkretny obiekt JavaScript (tj. Z pewnym prototypem)? zaprzecza tytułowi, w którym pytasz o parsowanie JSON, ale cytowany akapit pyta o zastąpienie prototypu obiektu wykonawczego JS.

Philipp Munin
źródło
3

Można zastosować alternatywne podejście Object.create. Jako pierwszy argument przekazujesz prototyp, a jako drugi przekazujesz mapę nazw właściwości do deskryptorów:

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
źródło
3

Lubię dodawać opcjonalny argument do konstruktora i wywoływać Object.assign(this, obj), a następnie obsługiwać wszelkie właściwości, które są obiektami lub tablicami samych obiektów:

constructor(obj) {
    if (obj != null) {
        Object.assign(this, obj);
        if (this.ingredients != null) {
            this.ingredients = this.ingredients.map(x => new Ingredient(x));
        }
    }
}
Jason Goemaat
źródło
2

Ze względu na kompletność, oto prosty, jednoliniowy tekst, z którym skończyłem (nie musiałem sprawdzać, czy nie ma właściwości innych niż 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 }'));
Obrabować
źródło
2

Utworzyłem pakiet o nazwie json-dry . Obsługuje (cykliczne) odwołania, a także instancje klas.

Musisz zdefiniować 2 nowe metody w swojej klasie ( toDryw prototypie i unDryjako metoda statyczna), zarejestrować klasę ( Dry.registerClass) i gotowe.

skerit
źródło
1

Chociaż technicznie nie jest to to, czego chcesz, jeśli wiesz wcześniej, jaki typ obiektu chcesz obsługiwać, możesz użyć metod call / apply prototypu znanego obiektu.

możesz to zmienić

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

do tego

alert(Foo.prototype.test.call(fooJSON); //Prints 12
Remus
źródło
1

Połączyłem rozwiązania, które udało mi się znaleźć, i skompilowałem je w ogólne, które może automatycznie analizować obiekt niestandardowy i wszystkie jego pola rekurencyjnie, dzięki czemu można używać metod prototypowych po deserializacji.

Jednym z założeń jest to, że zdefiniowałeś specjalne pole, które wskazuje jego typ w każdym obiekcie, który chcesz zastosować automatycznie ( this.__typew przykładzie).

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

stosowanie:

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

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

Funkcja przypisania typu (działa rekurencyjnie, aby przypisać typy do dowolnych zagnieżdżonych obiektów; iteruje również po tablicach, aby znaleźć odpowiednie obiekty):

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);
}
wirus
źródło
0

Aktualnie zaakceptowana odpowiedź nie działa dla mnie. Musisz poprawnie użyć 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`;
    }
}

Zwykle tworzysz obiekty tej klasy:

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

Jeśli masz ciąg json, który musisz przeanalizować w klasie Person, zrób to w następujący sposób:

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
Łukasz
źródło
-3

Odpowiedzi Olivera są bardzo jasne, ale jeśli szukasz rozwiązania w angular js, napisałem fajny moduł o nazwie Angular-jsClass, który robi to łatwo, posiadanie obiektów zdefiniowanych w notacji litaralnej jest zawsze złe, gdy masz na celu duży projekt ale mówiąc, że programiści mają problem, który dokładnie powiedział BMiner, jak serializować json do prototypu lub konstruktora obiektów notacji

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
źródło