2011-11-13 1 views
24

Ho un'app che consente agli utenti di generare oggetti e archiviarli (in una tabella MySQL, come stringhe) per un uso successivo. L'oggetto potrebbe essere:Trasformare stringhe JSON in oggetti con metodi

function Obj() { 
    this.label = "new object"; 
} 

Obj.prototype.setLabel = function(newLabel) { 
    this.label = newLabel; 
} 

Se uso JSON.stringify su questo oggetto, mi limito a ottenere le informazioni sul Obj.label (l'oggetto in stringa sarebbe una stringa come {label: "new object"} Se devo conservare questa stringa, e vogliono. consentire al mio utente di recuperare l'oggetto in un secondo momento, il metodo setLabel verrà perso.

Quindi la mia domanda è: come posso riattivare l'oggetto, in modo che mantenga le proprietà memorizzate grazie a JSON.stringify, ma anche a indietro i diversi metodi che dovrebbero appartenere al suo prototipo. Come lo faresti? Stavo pensando a qualcosa insieme a "creare un oggetto vuoto" e "unirlo con le proprietà memorizzate", ma Non riesco a farlo funzionare.

+0

Mi congratulo con @Cystack! La nozione di trasformare oggetti Javascript (che sono attributi puramente dati, nessun metodo) è discussa molto qui. Eppure questa è la prima domanda che incontro che si concentra veramente sugli oggetti, che sono oggetti con metodi. Il metodo '' JSON.stringify' 'sopporta abbastanza insidie ​​(riferimenti ciclici, caratteri di escape/caratteri speciali, ecc.), Quindi la maggior parte delle domande si concentrano su questo e purtroppo molto poco posso trovare su questo "reinstanciamento automatico". – humanityANDpeace

risposta

19

Per fare ciò, è necessario utilizzare una funzione "reviver" durante l'analisi della stringa JSON (e una funzione "replacer" o una funzione toJSON sul prototipo del costruttore durante la sua creazione). Vedere Section 15.12.2 e 15.12.3 della specifica. Se il proprio ambiente non supporta ancora l'analisi JSON nativa, è possibile utilizzare one of Crockford's parsers (Crockford è l'inventore di JSON), che supporta anche le funzioni di "reviver".

Ecco un semplice esempio su misura che funziona con i browser compatibili con ES5 (o librerie che emulano il comportamento di ES5) (live copy, eseguito in Chrome o Firefox o simile), ma si prenda cura dell'esempio per una soluzione più generalizzata.

// Our constructor function 
function Foo(val) { 
    this.value = val; 
} 
Foo.prototype.nifty = "I'm the nifty inherited property."; 
Foo.prototype.toJSON = function() { 
    return "/Foo(" + this.value + ")/"; 
}; 

// An object with a property, `foo`, referencing an instance 
// created by that constructor function, and another `bar` 
// which is just a string 
var obj = { 
    foo: new Foo(42), 
    bar: "I'm bar" 
}; 

// Use it 
display("obj.foo.value = " + obj.foo.value); 
display("obj.foo.nifty = " + obj.foo.nifty); 
display("obj.bar = " + obj.bar); 

// Stringify it with a replacer: 
var str = JSON.stringify(obj); 

// Show that 
display("The string: " + str); 

// Re-create it with use of a "reviver" function 
var obj2 = JSON.parse(str, function(key, value) { 
    if (typeof value === "string" && 
     value.substring(0, 5) === "/Foo(" && 
     value.substr(-2) == ")/" 
    ) { 
    return new Foo(value.substring(5, value.length - 2)); 
    } 
    return value; 
}); 

// Use the result 
display("obj2.foo.value = " + obj2.foo.value); 
display("obj2.foo.nifty = " + obj2.foo.nifty); 
display("obj2.bar = " + obj2.bar); 

Annotare il toJSON su Foo.prototype, e la funzione passiamo in JSON.parse.

Il problema, tuttavia, è che il reviver è strettamente accoppiato al costruttore Foo. Invece, è possibile adottare un framework generico nel codice, in cui qualsiasi funzione del costruttore può supportare una funzione fromJSON (o simile) ed è possibile utilizzare solo un reviver generalizzato.

Ecco un esempio di un rianimatore generalizzata che cerca una proprietà ctor e una proprietà data, e chiede ctor.fromJSON se trovato, passando il valore di fondo che ha ricevuto (live example):

// A generic "smart reviver" function. 
// Looks for object values with a `ctor` property and 
// a `data` property. If it finds them, and finds a matching 
// constructor that has a `fromJSON` property on it, it hands 
// off to that `fromJSON` fuunction, passing in the value. 
function Reviver(key, value) { 
    var ctor; 

    if (typeof value === "object" && 
     typeof value.ctor === "string" && 
     typeof value.data !== "undefined") { 
    ctor = Reviver.constructors[value.ctor] || window[value.ctor]; 
    if (typeof ctor === "function" && 
     typeof ctor.fromJSON === "function") { 
     return ctor.fromJSON(value); 
    } 
    } 
    return value; 
} 
Reviver.constructors = {}; // A list of constructors the smart reviver should know about 

per evitare di dover ripetere logica comune in toJSON e fromJSON funzioni, si potrebbe avere versioni generiche:

// A generic "toJSON" function that creates the data expected 
// by Reviver. 
// `ctorName` The name of the constructor to use to revive it 
// `obj`  The object being serialized 
// `keys`  (Optional) Array of the properties to serialize, 
//    if not given then all of the objects "own" properties 
//    that don't have function values will be serialized. 
//    (Note: If you list a property in `keys`, it will be serialized 
//    regardless of whether it's an "own" property.) 
// Returns: The structure (which will then be turned into a string 
//    as part of the JSON.stringify algorithm) 
function Generic_toJSON(ctorName, obj, keys) { 
    var data, index, key; 

    if (!keys) { 
    keys = Object.keys(obj); // Only "own" properties are included 
    } 

    data = {}; 
    for (index = 0; index < keys.length; ++index) { 
    key = keys[index]; 
    data[key] = obj[key]; 
    } 
    return {ctor: ctorName, data: data}; 
} 

// A generic "fromJSON" function for use with Reviver: Just calls the 
// constructor function with no arguments, then applies all of the 
// key/value pairs from the raw data to the instance. Only useful for 
// constructors that can be reasonably called without arguments! 
// `ctor`  The constructor to call 
// `data`  The data to apply 
// Returns: The object 
function Generic_fromJSON(ctor, data) { 
    var obj, name; 

    obj = new ctor(); 
    for (name in data) { 
    obj[name] = data[name]; 
    } 
    return obj; 
} 

Il vantaggio è che si de per l'implementazione di uno specifico "tipo" (per mancanza di un termine migliore) per come serializza e deserializza. Così si potrebbe avere un "tipo" che utilizza solo i farmaci generici:

// `Foo` is a constructor function that integrates with Reviver 
// but doesn't need anything but the generic handling. 
function Foo() { 
} 
Foo.prototype.nifty = "I'm the nifty inherited property."; 
Foo.prototype.spiffy = "I'm the spiffy inherited property."; 
Foo.prototype.toJSON = function() { 
    return Generic_toJSON("Foo", this); 
}; 
Foo.fromJSON = function(value) { 
    return Generic_fromJSON(Foo, value.data); 
}; 
Reviver.constructors.Foo = Foo; 

... o uno che, per qualsiasi motivo, ha a che fare qualcosa di più personalizzato:

// `Bar` is a constructor function that integrates with Reviver 
// but has its own custom JSON handling for whatever reason. 
function Bar(value, count) { 
    this.value = value; 
    this.count = count; 
} 
Bar.prototype.nifty = "I'm the nifty inherited property."; 
Bar.prototype.spiffy = "I'm the spiffy inherited property."; 
Bar.prototype.toJSON = function() { 
    // Bar's custom handling *only* serializes the `value` property 
    // and the `spiffy` or `nifty` props if necessary. 
    var rv = { 
    ctor: "Bar", 
    data: { 
     value: this.value, 
     count: this.count 
    } 
    }; 
    if (this.hasOwnProperty("nifty")) { 
    rv.data.nifty = this.nifty; 
    } 
    if (this.hasOwnProperty("spiffy")) { 
    rv.data.spiffy = this.spiffy; 
    } 
    return rv; 
}; 
Bar.fromJSON = function(value) { 
    // Again custom handling, for whatever reason Bar doesn't 
    // want to serialize/deserialize properties it doesn't know 
    // about. 
    var d = value.data; 
     b = new Bar(d.value, d.count); 
    if (d.spiffy) { 
    b.spiffy = d.spiffy; 
    } 
    if (d.nifty) { 
    b.nifty = d.nifty; 
    } 
    return b; 
}; 
Reviver.constructors.Bar = Bar; 

Ed ecco come ci potrebbe verificare che Foo e Bar lavoro come previsto (live copy):

// An object with `foo` and `bar` properties: 
var before = { 
    foo: new Foo(), 
    bar: new Bar("testing", 42) 
}; 
before.foo.custom = "I'm a custom property"; 
before.foo.nifty = "Updated nifty"; 
before.bar.custom = "I'm a custom property"; // Won't get serialized! 
before.bar.spiffy = "Updated spiffy"; 

// Use it 
display("before.foo.nifty = " + before.foo.nifty); 
display("before.foo.spiffy = " + before.foo.spiffy); 
display("before.foo.custom = " + before.foo.custom + " (" + typeof before.foo.custom + ")"); 
display("before.bar.value = " + before.bar.value + " (" + typeof before.bar.value + ")"); 
display("before.bar.count = " + before.bar.count + " (" + typeof before.bar.count + ")"); 
display("before.bar.nifty = " + before.bar.nifty); 
display("before.bar.spiffy = " + before.bar.spiffy); 
display("before.bar.custom = " + before.bar.custom + " (" + typeof before.bar.custom + ")"); 

// Stringify it with a replacer: 
var str = JSON.stringify(before); 

// Show that 
display("The string: " + str); 

// Re-create it with use of a "reviver" function 
var after = JSON.parse(str, Reviver); 

// Use the result 
display("after.foo.nifty = " + after.foo.nifty); 
display("after.foo.spiffy = " + after.foo.spiffy); 
display("after.foo.custom = " + after.foo.custom + " (" + typeof after.foo.custom + ")"); 
display("after.bar.value = " + after.bar.value + " (" + typeof after.bar.value + ")"); 
display("after.bar.count = " + after.bar.count + " (" + typeof after.bar.count + ")"); 
display("after.bar.nifty = " + after.bar.nifty); 
display("after.bar.spiffy = " + after.bar.spiffy); 
display("after.bar.custom = " + after.bar.custom + " (" + typeof after.bar.custom + ")"); 

display("(Note that after.bar.custom is undefined because <code>Bar</code> specifically leaves it out.)"); 
+0

suggerimento interessante, ma questo funziona solo con la tua "classe" di Foo. Se guardi il mio Obj(), non posso usarlo per far rivivere l'oggetto, poiché la proprietà '.label' andrà persa. – Cystack

+0

vedere questo basato sul tuo esempio: http://jsfiddle.net/cBBd4/ la proprietà nifty è persa – Cystack

+0

@Cystack: In realtà, ho aggiunto un reviver generalizzato (e appena sostituito con uno migliore). Re il tuo violino: certo, questo dipende dalla funzione 'toJSON' dell'oggetto' Foo', che deve essere sicuro di salvare le proprietà appropriate (molti oggetti avranno proprietà che non dovrebbero essere serializzate). Puoi anche fare una cosa generalizzata, come mostrato nel mio ultimo aggiornamento. –

0

Prova a usare toString sul metodo.

Aggiornamento:

scorrere i metodi di obj e memorizzarli come stringa, e poi istanziare con la nuova funzione.

+0

cosa intendi? in modo che io memorizzi il metodo come una stringa nell'oggetto?che sconfigge lo scopo del prototipo, no? – Cystack

+0

non necessariamente nell'oggetto, nel tuo DB, basta aggiungere una tabella per i metodi e l'oggetto a cui appartengono, inviare indietro normale json per l'oggetto e quindi le stringhe del metodo e aggiungerli agli oggetti .. –

+0

Poiché l'OP ha chiesto informazioni gli oggetti per avere metodi, quando si tratta di JSON, quindi penso che l'idea di usare 'replacer' per memorizzare i metodi in una stringa (che forse dovrebbe essere sfuggita e o base64 per non frenare la notazione JSON) e per far rivivere i metodi dopo. Il suggerimento potrebbe non essere considerato (bello, cioè avere metodi memorizzati in un DB) ma penso che si colleghi all'OP e la risposta arricchisca in modo defnito lo spazio della soluzione, @ j-a grazie! – humanityANDpeace

1

Per quanto ne so, questo significa allontanarsi da JSON; lo stai personalizzando e così ti accorgi di tutti i potenziali mal di testa che ciò comporta. L'idea di JSON è di includere solo i dati, non il codice, per evitare tutti i problemi di sicurezza che si verificano quando si consente di includere il codice. Consentire il codice significa che devi usare eval per eseguire quel codice e eval è malvagio.

+0

Non voglio inserire codice in JSON, sto solo citando il fatto che non memorizza i metodi in alcun modo. Ma sono aperto a qualsiasi soluzione! – Cystack

+0

* "Per quanto ne so, questo significa allontanarsi da JSON ..." * Non proprio, le funzioni del reviver sono in circolazione da molto tempo. –

+0

Immagino di non aver capito la natura della domanda. Una funzione di reviver consente di utilizzare una funzione esistente (non una nel JSON) per eseguire operazioni sui dati durante la lettura, tra cui la creazione di un oggetto e l'aggiunta del proprio codice. Ciò potrebbe significare che è possibile memorizzare nomi di proprietà che indicano quali funzioni devono essere aggiunte all'oggetto ricostituito. Ma non memorizza codice nel JSON. A meno che mi manchi qualcosa ... se lo sono, per favore sentiti libero di illuminarmi. – dnuttle

5

È possibile creare un'istanza vuota e quindi unire l'istanza con i dati. Raccomando di utilizzare una funzione di libreria per facilità d'uso (come jQuery.extend).

Si sono verificati alcuni errori (function ... = function(...) e JSON richiede che le chiavi siano circondate da ").

http://jsfiddle.net/sc8NU/1/

var data = '{"label": "new object"}'; // JSON 
var inst = new Obj;     // empty instance 
jQuery.extend(inst, JSON.parse(data)); // merge 

Nota che la fusione come questo imposta le proprietà direttamente, quindi se setLabel sta facendo alcune cose di controllo, questo non sarà fatto in questo modo.

+0

molto buono! questo è il più vicino a quello che pensavo. Ora l'unico svantaggio è che ho passato ore a non includere jQuery per vari motivi, mi dispiacerebbe includerlo solo per quello ^^ guarderò nel codice della funzione .extend anche se – Cystack

+0

@Cystack: ci sono numerosi 'extend' funzioni disponibili in molte librerie; potresti anche prestarne uno e copiarlo nel tuo progetto. Underscore.js è privo di dipendenze: http://documentcloud.github.com/underscore/underscore.js. – pimvdb

+2

@Cystack & pimvdb: Il problema qui è che presuppone che 'Obj' * possa * essere chiamato senza argomenti e, come indicato da pimvdb, ignora l'API del tipo (che può essere proprio o meno). Dal mio punto di vista, è molto meglio rinviare tali decisioni al tipo stesso (che potrebbe, naturalmente, usare semplicemente "estendere" se approprafico). –

0

Dovresti scrivere il tuo metodo stringify che memorizza le funzioni come proprietà convertendole in stringhe usando il metodo toString.

1

Se si desidera utilizzare il setter di obj:

0.123.
Obj.createFromJSON = function(json){ 
    if(typeof json === "string") // if json is a string 
     json = JSON.parse(json); // we convert it to an object 
    var obj = new Obj(), setter; // we declare the object we will return 
    for(var key in json){ // for all properties 
     setter = "set"+key[0].toUpperCase()+key.substr(1); // we get the name of the setter for that property (e.g. : key=property => setter=setProperty 
     // following the OP's comment, we check if the setter exists : 
     if(setter in obj){ 
     obj[setter](json[key]); // we call the setter 
     } 
     else{ // if not, we set it directly 
     obj[key] = json[key]; 
     } 
    } 
    return obj; // we finally return the instance 
}; 

Ciò richiede che la classe abbia setter per tutte le sue proprietà. Questo metodo è statico, in modo da poter utilizzare in questo modo:

var instance = Obj.createFromJSON({"label":"MyLabel"}); 
var instance2 = Obj.createFromJSON('{"label":"MyLabel"}'); 
+0

+1 Soluzione piacevole; forse nel caso in cui 'setXXX' non sia disponibile, è sufficiente impostarlo direttamente (poiché un numero piuttosto elevato di proprietà potrebbe non richiedere una funzione di setter separata). – pimvdb

+0

@pimvdb OK, ho preso in considerazione il tuo commento nell'ultima modifica. – fflorent

0

JavaScript è prototype based programming language che è linguaggio senza classi in cui l'orientamento scopo raggiunto dal processo di clonazione oggetti esistenti che servono come prototipi.

serializzazione JSON sarebbe prendendo in considerazione tutti i metodi, per esempio se si dispone di un oggetto

var x = { 
    a: 4 
    getText: function() { 
     return x.a; 
    } 
}; 

Si otterrà solo { a:4 } dove getText metodo è saltato dal serializzatore.

mi sono imbattuto in questo stesso problema un anno indietro e ho dovuto mantenere una classe di supporto separata per ciascuno dei miei oggetti di dominio e usato $.extend() al mio deserializzare oggetto quando il bisogno, solo più come avere metodi per una base classe per gli oggetti dominio.

Problemi correlati