Uso della funzionalità JSON nativa

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.

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]>]