Analizzare la stringa JSON in un prototipo di oggetto particolare in JavaScript


173

So come analizzare una stringa JSON e trasformarla in un oggetto JavaScript. È possibile utilizzare JSON.parse()nei browser moderni (e IE9 +).

È fantastico, ma come posso prendere quell'oggetto JavaScript e trasformarlo in un particolare oggetto JavaScript (cioè con un certo prototipo)?

Ad esempio, supponiamo di avere:

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

Ancora una volta, non mi chiedo come convertire una stringa JSON in un oggetto JavaScript generico. Voglio sapere come convertire una stringa JSON in un oggetto "Foo". Cioè, il mio oggetto ora dovrebbe avere una funzione 'test' e proprietà 'a' e 'b'.

AGGIORNAMENTO Dopo aver fatto qualche ricerca, ho pensato a questo ...

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

Funzionerà?

AGGIORNAMENTO Maggio 2017 : il modo "moderno" di farlo è attivo Object.assign, ma questa funzione non è disponibile nei browser Android IE 11 o precedenti.


Risposte:


124

Le risposte attuali contengono molti codici a mano o di libreria. Questo non è necessario

  1. Utilizzare JSON.parse('{"a":1}')per creare un oggetto semplice.

  2. Utilizzare una delle funzioni standardizzate per impostare il prototipo:

    • Object.assign(new Foo, { a: 1 })
    • Object.setPrototypeOf({ a: 1 }, Foo.prototype)

2
Object.assign non è disponibile nei browser precedenti, inclusi IE e i browser Android precedenti. kangax.github.io/compat-table/es6/…
BMiner

5
C'è anche un grosso avvertimento contro l'uso Object.setPrototypeOf(...). developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
christo8989

@SimonEpskamp Quel codice non funziona. Controlla il tuo URL, il secondo parametro setPrototypeOfè descrittori di proprietà.
Erik van Velzen,

6
La soluzione con l'impostazione del prototipo non funziona se esiste qualche proprietà che deve anche avere un prototipo. In altre parole: risolve solo il primo livello della gerarchia dei dati.
Vojta,

2
controlla la mia soluzione qui sotto che applica Object.assign (..) in modo ricorsivo che può risolvere automaticamente le proprietà (con un po 'di informazioni fornite in anticipo)
vir us

73

Vedi un esempio di seguito (questo esempio utilizza l'oggetto JSON nativo). Le mie modifiche sono commentate in CAPITALI:

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

Suppongo che potresti fare anche il "contrario" di questo. Costruisci un oggetto Foo vuoto e copia le proprietà da fooJSON nel nuovo oggetto Foo. Infine, imposta fooJSON in modo che punti all'oggetto Foo.
BMiner

8
Questo è molto pericoloso. Se l'obj ha un attributo che non è nella definizione di Foo, creerai un oggetto Foo con una proprietà nascosta extra che non conosci il suo nome ... Invece di un ciclo lo farò semplicemente: this.a = obj. a e this.b = obj.b. O direttamente passerei "a" e "b" come parametri: nuovo Foo (obj.a, obj.b)
Gabriel Llamas,

2
Vale la pena ascoltare il consiglio di GagleKas. (Anche se "molto pericoloso" è un po 'OTT.) L'esempio sopra è solo per darti un'idea. L'implementazione corretta dipenderà dalla tua applicazione.
Oliver Moran,

11
Potresti voler proteggerti dalle proprietà del prototipo. for (var prop in obj) {if (obj.hasOwnProperty(prop)) {this[prop] = obj[prop];}}
Romain Vergnory,

3
@RomainVergnory Per una maggiore sicurezza, I proprietà solo di inizializzazione create nel costruttore, questo invece di obj: for (var prop in obj) {if (this.hasOwnProperty(prop)) {this[prop] = obj[prop];}}. Questo presuppone che tu preveda che il server popoli tutte le proprietà, anche IMO dovrebbe lanciare se obj.hasOwnProperty () fallisce ...
tekHedd,

42

Vuoi aggiungere la funzionalità di serializzazione / deserializzazione JSON, giusto? Quindi guarda questo:

vuoi raggiungere questo obiettivo:

UML

toJson () è un metodo normale.
fromJson () è un metodo statico.

Implementazione :

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

Utilizzo :

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

Nota: Se si desidera è possibile modificare tutte le definizioni di proprietà come this.title, this.author, ecc da var title, var author, ecc e aggiungere getter a loro per realizzare la definizione di UML.


4
Sono d'accordo. Questa implementazione funzionerà sicuramente, ed è eccezionale ... solo un po 'prolisso e specifico per l'Oggetto del Libro. IMHO, la potenza di JS deriva dai prototipi e dalla possibilità di avere alcune proprietà extra se lo desideri. Questo è tutto ciò che sto dicendo. Stavo davvero cercando il one-liner: x .__ proto__ = X.prototype; (anche se al momento non è compatibile con IE Browser)
BMiner

4
Non dimenticare che il tuo toJson()metodo, indipendentemente dal fatto che abbia singole proprietà codificate o che utilizzi una per ciascuna, dovrà aggiungere codici di escape con barra rovesciata per alcuni caratteri che potrebbero trovarsi in ciascuna proprietà della stringa. (Il titolo di un libro potrebbe avere delle virgolette, per esempio.)
nnnnnn

1
Sì, lo so, la mia risposta è stata un esempio e la migliore risposta alla domanda, ma ... nemmeno un punto positivo ... Non so perché sprecare il mio tempo ad aiutare gli altri
Gabriel Llamas,

7
In questi giorni JSON.stringify()userei invece di scrivere a JSon () me stesso. Non è necessario reinventare la ruota ora che tutti i browser moderni la supportano.
pietra,

2
Concordato con @skypecakes. Se si desidera serializzare solo un sottoinsieme di proprietà, creare una costante di proprietà serializzabili. serializable = ['title', 'author', ...]. JSON.stringify(serializable.reduce((obj, prop) => {...obj, [prop]: this[prop]}, {}))
Atticus,

19

Un post sul blog che ho trovato utile: Comprensione dei prototipi JavaScript

Puoi pasticciare con la proprietà __proto__ dell'Oggetto.

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

Ciò consente a fooJSON di ereditare il prototipo Foo.

Non penso che funzioni in IE, comunque ... almeno da quello che ho letto.


2
In realtà, qualcosa del genere è stato il mio primo istinto.
Oliver Moran,

14
Si noti che __proto__è stato a lungo deprecato . Inoltre, per motivi di prestazioni, non è consigliabile modificare la proprietà interna [[prototipo]] di un oggetto già creato (impostando __proto__o con qualsiasi altro mezzo).
Yu Asakusa,

1
Purtroppo, nessuna delle soluzioni attualmente non deprecate è molto più complessa di questa ...
Wim Leers,

Ho fatto alcuni test sulle prestazioni del cambiamento [[prototype]]e sembra non essere rilevante in Chrome. In firefox chiamare new è più lento dell'uso del prototipo e Object.create è il più veloce. Immagino che il problema con FF sia che il primo test è più lento dell'ultimo, solo l'ordine di esecuzione è importante. In Chrome tutto funziona quasi alla stessa velocità. Intendo l'accesso alla proprietà e la nvocazione dei metodi. La creatina è più veloce con le nuove, ma non è così importante. vedi: jsperf.com/prototype-change-test-8874874/1 e: jsperf.com/prototype-changed-method-call
Bogdan Mart

4
Suppongo che in questi giorni si chiamerebbe Object.setPrototypeOf(fooJSON, Foo.prototype)invece di impostare fooJSON.__proto__... giusto?
stakx - non contribuisce più al

11

Mi sto perdendo qualcosa nella domanda o perché altrimenti nessuno ha menzionato il reviverparametro JSON.parsedal 2011?

Ecco un codice semplicistico per la soluzione che funziona: 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: La tua domanda è confusa: >> È grandioso, ma come posso prendere quell'oggetto JavaScript e trasformarlo in un particolare oggetto JavaScript (cioè con un certo prototipo)? contraddice il titolo, in cui si chiede informazioni sull'analisi JSON, ma il paragrafo citato chiede la sostituzione del prototipo di oggetto runtime JS.


3

Potrebbe essere utilizzato un approccio alternativo Object.create. Come primo argomento, si passa il prototipo e per il secondo si passa una mappa di nomi di proprietà ai descrittori:

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

Mi piace aggiungere un argomento facoltativo al costruttore e chiamare Object.assign(this, obj), quindi gestire tutte le proprietà che sono oggetti o matrici di oggetti stessi:

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

2

Per completezza, ecco un semplice one-liner con cui sono finito (non avevo bisogno di controllare le proprietà 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 }'));

2

Ho creato un pacchetto chiamato json-dry . Supporta riferimenti (circolari) e anche istanze di classe.

Devi definire 2 nuovi metodi nella tua classe ( toDrysul prototipo e unDrycome metodo statico), registrare la classe ( Dry.registerClass) e il gioco è fatto.


1

Tuttavia, questo non è tecnicamente ciò che vuoi, se conosci prima il tipo di oggetto che vuoi gestire puoi usare i metodi call / apply del prototipo del tuo oggetto noto.

puoi cambiarlo

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

a questa

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

1

Ho combinato le soluzioni che sono stato in grado di trovare e lo ho compilato in uno generico che può analizzare automaticamente un oggetto personalizzato e tutti i suoi campi in modo ricorsivo in modo da poter utilizzare i metodi prototipo dopo la deserializzazione.

Un presupposto è che hai definito un file speciale che indica il tipo in ogni oggetto a cui desideri applicare il tipo automaticamente ( this.__typenell'esempio).

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

utilizzo:

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

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

Funzione di assegnazione dei tipi (funziona in modo ricorsivo per assegnare i tipi a qualsiasi oggetto nidificato; scorre anche attraverso le matrici per trovare qualsiasi oggetto adatto):

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

La risposta attualmente accettata non funzionava per me. Devi usare Object.assign () correttamente:

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

Generi oggetti di questa classe normalmente:

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

Se hai una stringa json che devi analizzare nella classe Person, fallo in questo modo:

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

Le risposte di Olivers sono molto chiare, ma se stai cercando una soluzione in js angolare, ho scritto un bel modulo chiamato Angular-jsClass che facilita questa operazione, avere oggetti definiti nella notazione litarale è sempre negativo quando si mira a un grande progetto ma dicendo che gli sviluppatori affrontano il problema che esattamente BMiner ha detto, come serializzare un json su prototipo o oggetto di notazione del costruttore

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

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.