ActionScript 3.0 offre un'API nativa per la codifica e decodifica di oggetti ActionScript mediante il formato JavaScript Object Notation (JSON). La classe JSON e le funzioni membro che supporta seguono le specifiche ECMA-262 5a edizione con alcune varianti.
Il membro della comunità Todd Anderson ha prodotto un confronto tra l'API JSON nativa e la classe JSON as3corelib di terze parti. Vedete
Working with Native JSON in Flash Player 11
(Operazioni con JSON nativo in Flash Player 11).
Panoramica dell'API JSON
L'API JSON di ActionScript è costituita dalle classe JSON e dalle funzioni membro
toJSON()
su alcune classi native. Per le applicazioni che richiedono una codifica JSON personalizzata per qualsiasi classe, il framework ActionScript consente di sostituire la codifica predefinita.
La classe JSON gestisce internamente l'importazione ed esportazione di qualunque classe ActionScript che non fornisce un membro
toJSON()
. Per tali casi, JSON legge le proprietà pubbliche di ogni oggetto che incontra. Se un oggetto contiene altri oggetti, JSON esegue la ricorsione all'interno degli oggetti più vicini ed esegue la stessa lettura. Se un qualsiasi oggetto fornisce un metodo
toJSON()
, JSON impiega tale metodo personalizzato anziché il suo algoritmo interno.
L'interfaccia JSON è costituita da un metodo di codifica,
stringify()
, e da un metodo di decodifica,
parse()
. Ognuno di questi metodi fornisce un parametro che vi consente di inserire la vostra logica nel flusso di lavoro di codifica o decodifica JSON. Per
stringify()
, tale parametro è denominato
replacer
; per
parse()
è
reviver
. Questi parametri accettano una definizione di funzione con due argomenti utilizzando la firma seguente:
function(k, v):*
Metodi toJSON()
La firma dei metodi
toJSON()
è
public function toJSON(k:String):*
JSON.stringify()
chiama il metodo
toJSON()
, se esiste, per ogni proprietà pubblica che incontra durante la lettura di un oggetto. Una proprietà è costituita da una coppia chiave-valore. Se
stringify(
) effettua una chiamata a
toJSON()
, esso trasmette la chiave
k
della proprietà che sta esaminando. Un'implementazione tipica di
toJSON()
valuta ogni nome di proprietà e restituisce la codifica desiderata del relativo valore.
Il metodo
toJSON()
può restituire un valore di qualsiasi tipo (indicato come *), non semplicemente una stringa. Questo tipo di risultato variabile consente a
toJSON()
di restituire un oggetto, se appropriato. Ad esempio, se una proprietà della vostra classe personalizzata contiene un oggetto di una libreria di terze parti, tale oggetto può essere restituito quando
toJSON()
incontra la vostra proprietà. JSON esegue quindi una ricorsione nell'oggetto di terze parti. Il flusso del processo di codifica è il seguente:
-
Se
toJSON()
restituisce un oggetto che a sua volta non restituisce una stringa,
stringify()
esegue una ricorsione in tale oggetto.
-
Se
toJSON()
restituisce una stringa,
stringify()
racchiude tale valore in un'altra stringa, restituisce la stringa racchiusa e quindi passa al valore successivo.
In molti casi, la restituzione di un oggetto è da preferire alla restituzione di una stringa JSON creata dall'applicazione, poiché l'oggetto restituito sfrutta l'algoritmo di codifica JSON incorporato e consente inoltre a JSON di eseguire la ricorsione in oggetti nidificati.
Il metodo
toJSON()
non viene definito nella classe Object o nella maggior parte delle altre classi native. La sua assenza indica a JSON di eseguire la propria lettura predefinita all'interno delle proprietà pubbliche dell'oggetto. Eventualmente, potete usare
toJSON()
anche per esporre le proprietà private dell'oggetto.
Alcune classi native pongono problemi che le librerie di ActionScript non sono in grado di risolvere efficacemente per tutti i casi d'uso possibili. Per tali classi, ActionScript fornisce un'implementazione semplificata che i clienti possono reimplementare in base alle loro specifiche necessità. Le classi che forniscono membri
toJSON()
semplificati sono:
-
ByteArray
-
Date
-
Dictionary
-
XML
Potete creare sottoclassi della classe ByteArray per eseguire l'override del suo metodo
toJSON()
, oppure potete ridefinirne il prototipo. Le classi Data e XML, che sono dichiarate finali, richiedono l'uso del prototipo della classe per ridefinire
toJSON()
. La classe Dictionary è dichiarata dinamica e pertanto consente più libertà nell'override di
toJSON()
.
Definizione di un comportamento JSON personalizzato
Per implementare una vostra codifica e decodifica JSON personalizzata per le classi native, potete scegliere tra varie opzioni:
-
Definizione o override di
toJSON()
su una vostra sottoclasse personalizzata di una classe nativa non finale
-
Definizione o ridefinizione di
toJSON()
sul prototipo della classe
-
Definizione di una proprietà
toJSON
su una classe dinamica
-
Uso dei parametri
JSON.stringify() replacer
e
JSON.parser() reviver
Definizione di toJSON() sul prototipo di una classe integrata
L'implementazione JSON nativa in ActionScript rispecchia il meccanismo ECMAScript JSON definito in ECMA-262, 5a edizione. Poiché ECMAScript non supporta le classi, ActionScript definisce il comportamento di JSON in termini di invio basato su prototipi. I prototipi sono precursori di classi ActionScript 3.0 che consentono l'ereditarietà simulata così come l'aggiunta e la ridefinizione dei membri.
ActionScript permette di definire o ridefinire
toJSON()
sul prototipo di qualsiasi classe. Questa possibilità è disponibile anche per le classi dichiarate come finali. Quando definite
toJSON()
sul prototipo di una classe, la vostra definizione diventa corrente per tutte le istanze di tale classe, nell'ambito della vostra applicazione. Ad esempio, ecco come potete definire un metodo
toJSON()
sul prototipo MovieClip:
MovieClip.prototype.toJSON = function(k):* {
trace("prototype.toJSON() called.");
return "toJSON";
}
Se la vostra applicazione effettua poi una chiamata a
stringify()
su una qualsiasi istanza MovieClip,
stringify()
restituisce l'output del vostro metodo
toJSON()
:
var mc:MovieClip = new MovieClip();
var js:String = JSON.stringify(mc); //"prototype toJSON() called."
trace("js: " + js); //"js: toJSON"
Potete anche eseguire l'override di
toJSON()
nelle classi native che definiscono il metodo. Ad esempio, il codice seguente consente di sostituire
Date.toJSON()
:
Date.prototype.toJSON = function (k):* {
return "any date format you like via toJSON: "+
"this.time:"+this.time + " this.hours:"+this.hours;
}
var dt:Date = new Date();
trace(JSON.stringify(dt));
// "any date format you like via toJSON: this.time:1317244361947 this.hours:14"
Definizione o override di toJSON() a livello di classe
Le applicazioni non sono sempre necessarie per utilizzare i prototipi per la ridefinizione di
toJSON()
. Potete anche definire
toJSON()
come membro di una sottoclasse se la classe superiore non è dichiarata finale. Ad esempio, potete estendere la classe ByteArray e definire una funzione pubblica
toJSON()
:
package {
import flash.utils.ByteArray;
public class MyByteArray extends ByteArray
{
public function MyByteArray() {
}
public function toJSON(s:String):*
{
return "MyByteArray";
}
}
}
var ba:ByteArray = new ByteArray();
trace(JSON.stringify(ba)); //"ByteArray"
var mba:MyByteArray = new MyByteArray(); //"MyByteArray"
trace(JSON.stringify(mba)); //"MyByteArray"
Se una classe è dinamica, potete aggiungere una proprietà
toJSON
a un oggetto di quella classe e assegnare ad essa una funzione nel modo seguente:
var d:Dictionary = new Dictionary();
trace(JSON.stringify((d))); // "Dictionary"
d.toJSON = function(){return {c : "toJSON override."};} // overrides existing function
trace(JSON.stringify((d))); // {"c":"toJSON override."}
Potete sostituire, definire o sostituire
toJSON()
in qualsiasi classe ActionScript. Tuttavia, la maggior parte delle classi ActionScript integrate non definisce
toJSON()
. La classe Object non definisce
toJSON
nel suo prototipo predefinito, né lo dichiara membro della classe. Solo poche classi native definiscono il metodo come funzione prototipo. Di conseguenza, nella maggior parte delle classi non è possibile sostituire
toJSON()
nel senso tradizionale.
Le classi native che non definiscono
toJSON()
vengono serializzate su JSON dall'implementazione JSON interna. Evitate di sostituire questa funzionalità incorporata, se possibile. Se definite un membro
toJSON()
, la classe JSON utilizza la vostra logica invece della propria funzionalità.
Uso del parametro replacer di JSON.stringify()
La sostituzione (override) di
toJSON()
sul prototipo è utile per modificare il comportamento di esportazione JSON di una classe a livello di applicazione. In alcuni casi, tuttavia, la logica di esportazione potrebbe essere richiesta solo per casi speciali e condizioni temporanee. Per eseguire solo queste modifiche di lieve entità, potete utilizzare il parametro
replacer
del metodo
JSON.stringify()
.
Il metodo
stringify()
applica la funzione passata mediante il parametro
replacer
all'oggetto che viene codificato. La firma di questa funzione è simile a quella di
toJSON()
:
function (k,v):*
A differenza di
toJSON()
, la funzione
replacer
richiede il valore,
v
, così come la chiave,
k
. Questa differenza è necessaria, in quanto
stringify()
viene definito sull'oggetto JSON statico anziché sull'oggetto che viene codificato. Quando
JSON.stringify()
chiama
replacer(k,v)
, esegue la lettura dell'oggetto di input originale. Il parametro implicito
this
trasmesso alla funzione
replacer
si riferisce all'oggetto che contiene la chiave e il valore. Poiché
JSON.stringify()
non modifica l'oggetto di input originale, tale oggetto rimane invariato nel contenitore che viene letto. Di conseguenza, potete usare il codice
this[k]
per interrogare la chiave sull'oggetto originale. Il parametro
v
contiene il valore che viene convertito da
toJSON()
.
Come
toJSON()
, la funzione
replacer
può restituire qualsiasi tipo di valore. Se
replacer
restituisce una stringa, il motore JSON esegue l'escape del contenuto tramite virgolette e quindi racchiude di nuovo tra virgolette lo stesso contenuto sottoposto a escape. Questo accorgimento assicura che
stringify()
riceva un oggetto stringa JSON valido che rimane una stringa nella chiamata successiva a
JSON.parse()
.
Il codice seguente usa il parametro
replacer
e il parametro implicito
this
per restituire i valori
time
e
hours
di un oggetto Date:
JSON.stringify(d, function (k,v):* {
return "any date format you like via replacer: "+
"holder[k].time:"+this[k].time + " holder[k].hours:"+this[k].hours;
});
Uso del parametro reviver di JSON.parse()
Il parametro
reviver
del metodo
JSON.parse()
fa il contrario della funzione
replacer
: converte una stringa JSON in un oggetto ActionScript utilizzabile. L'argomento
reviver
è una funzione che accetta due parametri e restituisce qualunque tipo:
function (k,v):*
In questa funzione,
k
è una chiave e
v
è il valore di
k
. Analogamente a
stringify()
,
parse()
legge le coppie chiave-valore JSON e applica la funzione
reviver
(se esiste) a ogni coppia. Un possibile problema consiste nel fatto che la classe JSON non genera il nome di classe ActionScript di un oggetto. Di conseguenza, può risultare difficile determinare a quale tipo di oggetto applicare la funzione reviver. Questo problema può essere particolarmente difficile da risolvere quando gli oggetti sono nidificati. Nel progettare le funzioni
toJSON()
,
replacer
, e
reviver
, potete ideare dei modi per identificare gli oggetti ActionScript che vengono esportati senza alterare gli oggetti originali.
Esempio di analisi
L'esempio seguente mostra una strategia per il reviving di oggetti analizzati in stringhe JSON. Questo esempio definisce due classi: JSONGenericDictExample e JSONDictionaryExtnExample. La classe JSONGenericDictExample è una classe dictionary personalizzata. Ogni record contiene il nome e la data di nascita di una persona, nonché un ID univoco. Ogni volta che viene chiamata la funzione di costruzione JSONGenericDictExample, essa aggiunge il nuovo oggetto creato a un array statico interno con un ID costituito da un numero intero che incrementa in modo statico. La classe JSONGenericDictExample inoltre definisce un metodo
revive()
che estrae solo la porzione del numero intero dai dati del membro
id
. Il metodo
revive()
utilizza tale numero intero per cercare e restituire l'oggetto corretto da sottoporre a reviving.
La classe JSONDictionaryExtnExample estende la classe ActionScript Dictionary. I suoi record non hanno una struttura prestabilita e possono contenere qualsiasi tipo di dati. I dati vengono assegnati dopo la costruzione di un oggetto JSONDictionaryExtnExample anziché come proprietà definite dalla classe. I record JSONDictionaryExtnExample usano gli oggetti JSONGenericDictExample come chiavi. Quando viene eseguito il reviving di un oggetto JSONDictionaryExtnExample, la funzione
JSONGenericDictExample.revive()
utilizza l'ID associato con JSONDictionaryExtnExample per recuperare l'oggetto chiave corretto.
L'aspetto più importante è rappresentato dal fatto che il metodo
JSONDictionaryExtnExample.toJSON()
restituisce una stringa di marcatura in aggiunta all'oggetto JSONDictionaryExtnExample. Tale stringa identifica l'output JSON come appartenente alla classe JSONDictionaryExtnExample. Questo marcatore identifica in modo inequivocabile il tipo di oggetto che viene elaborato durante
JSON.parse()
.
package {
// Generic dictionary example:
public class JSONGenericDictExample {
static var revivableObjects = [];
static var nextId = 10000;
public var id;
public var dname:String;
public var birthday;
public function JSONGenericDictExample(name, birthday) {
revivableObjects[nextId] = this;
this.id = "id_class_JSONGenericDictExample_" + nextId;
this.dname = name;
this.birthday = birthday;
nextId++;
}
public function toString():String { return this.dname; }
public static function revive(id:String):JSONGenericDictExample {
var r:RegExp = /^id_class_JSONGenericDictExample_([0-9]*)$/;
var res = r.exec(id);
return JSONGenericDictExample.revivableObjects[res[1]];
}
}
}
package {
import flash.utils.Dictionary;
import flash.utils.ByteArray;
// For this extension of dictionary, we serialize the contents of the
// dictionary by using toJSON
public final class JSONDictionaryExtnExample extends Dictionary {
public function toJSON(k):* {
var contents = {};
for (var a in this) {
contents[a.id] = this[a];
}
// We also wrap the contents in an object so that we can
// identify it by looking for the marking property "class E"
// while in the midst of JSON.parse.
return {"class JSONDictionaryExtnExample": contents};
}
// This is just here for debugging and for illustration
public function toString():String {
var retval = "[JSONDictionaryExtnExample <";
var printed_any = false;
for (var k in this) {
retval += k.toString() + "=" +
"[e="+this[k].earnings +
",v="+this[k].violations + "], "
printed_any = true;
}
if (printed_any)
retval = retval.substring(0, retval.length-2);
retval += ">]"
return retval;
}
}
}
Quando lo script runtime seguente chiama
JSON.parse()
su un oggetto JSONDictionaryExtnExample, la funzione
reviver
chiama
JSONGenericDictExample.revive()
su ogni oggetto presente in JSONDictionaryExtnExample. Questa chiamata estrae l'ID che rappresenta la chiave dell'oggetto. La funzione
JSONGenericDictExample.revive()
usa questo ID per recuperare l'oggetto JSONDictionaryExtnExample memorizzato da un array statico privato.
import flash.display.MovieClip;
import flash.text.TextField;
var a_bob1:JSONGenericDictExample = new JSONGenericDictExample("Bob", new Date(Date.parse("01/02/1934")));
var a_bob2:JSONGenericDictExample = new JSONGenericDictExample("Bob", new Date(Date.parse("05/06/1978")));
var a_jen:JSONGenericDictExample = new JSONGenericDictExample("Jen", new Date(Date.parse("09/09/1999")));
var e = new JSONDictionaryExtnExample();
e[a_bob1] = {earnings: 40, violations: 2};
e[a_bob2] = {earnings: 10, violations: 1};
e[a_jen] = {earnings: 25, violations: 3};
trace("JSON.stringify(e): " + JSON.stringify(e)); // {"class JSONDictionaryExtnExample":
//{"id_class_JSONGenericDictExample_10001":
//{"earnings":10,"violations":1},
//"id_class_JSONGenericDictExample_10002":
//{"earnings":25,"violations":3},
//"id_class_JSONGenericDictExample_10000":
// {"earnings":40,"violations":2}}}
var e_result = JSON.stringify(e);
var e1 = new JSONDictionaryExtnExample();
var e2 = new JSONDictionaryExtnExample();
// It's somewhat easy to convert the string from JSON.stringify(e) back
// into a dictionary (turn it into an object via JSON.parse, then loop
// over that object's properties to construct a fresh dictionary).
//
// The harder exercise is to handle situations where the dictionaries
// themselves are nested in the object passed to JSON.stringify and
// thus does not occur at the topmost level of the resulting string.
//
// (For example: consider roundtripping something like
// var tricky_array = [e1, [[4, e2, 6]], {table:e3}]
// where e1, e2, e3 are all dictionaries. Furthermore, consider
// dictionaries that contain references to dictionaries.)
//
// This parsing (or at least some instances of it) can be done via
// JSON.parse, but it's not necessarily trivial. Careful consideration
// of how toJSON, replacer, and reviver can work together is
// necessary.
var e_roundtrip =
JSON.parse(e_result,
// This is a reviver that is focused on rebuilding JSONDictionaryExtnExample objects.
function (k, v) {
if ("class JSONDictionaryExtnExample" in v) { // special marker tag;
//see JSONDictionaryExtnExample.toJSON().
var e = new JSONDictionaryExtnExample();
var contents = v["class JSONDictionaryExtnExample"];
for (var i in contents) {
// Reviving JSONGenericDictExample objects from string
// identifiers is also special;
// see JSONGenericDictExample constructor and
// JSONGenericDictExample's revive() method.
e[JSONGenericDictExample.revive(i)] = contents[i];
}
return e;
} else {
return v;
}
});
trace("// == Here is an extended Dictionary that has been round-tripped ==");
trace("// == Note that we have revived Jen/Jan during the roundtrip. ==");
trace("e: " + e); //[JSONDictionaryExtnExample <Bob=[e=40,v=2], Bob=[e=10,v=1],
//Jen=[e=25,v=3]>]
trace("e_roundtrip: " + e_roundtrip); //[JSONDictionaryExtnExample <Bob=[e=40,v=2],
//Bob=[e=10,v=1], Jen=[e=25,v=3]>]
trace("Is e_roundtrip a JSONDictionaryExtnExample? " + (e_roundtrip is JSONDictionaryExtnExample)); //true
trace("Name change: Jen is now Jan");
a_jen.dname = "Jan"
trace("e: " + e); //[JSONDictionaryExtnExample <Bob=[e=40,v=2], Bob=[e=10,v=1],
//Jan=[e=25,v=3]>]
trace("e_roundtrip: " + e_roundtrip); //[JSONDictionaryExtnExample <Bob=[e=40,v=2],
//Bob=[e=10,v=1], Jan=[e=25,v=3]>]
|
|
|