Native JSON-functionaliteit gebruiken

ActionScript 3.0 beschikt over een native API voor het coderen en decoderen van ActionScript-objecten met gebruik van de JSON-indeling (JavaScript Object Notation). De JSON-klasse en ondersteunende lidfuncties volgen de vijfde uitgave van de ECMA-262-specificatie, afgezien van enkele afwijkingen.

Overzicht van de JSON API

De ActionScript JSON-API bestaat uit de JSON-klasse en de toJSON()-lidfuncties voor enkele native klassen. Voor toepassingen die voor bepaalde klassen een aangepaste JSON-codering vereisen, verschaft het ActionScript-framework manieren om de standaardcodering te overschrijven.

De JSON-klasse verwerkt intern het importeren en exporteren van alle ActionScript-klassen die geen toJSON() -lid verschaffen. In dergelijke gevallen doorloopt JSON de openbare eigenschappen van elk object dat het tegenkomt. Als een object andere objecten bevat, keert JSON terug naar de geneste objecten en wordt hetzelfde traject doorlopen. Als een object de methode toJSON() biedt, gebruikt JSON die aangepaste methode in plaats van het interne algoritme.

De JSON-interface bestaat uit de coderingsmethode stringify() en de decoderingsmethode parse(). Elke methode verschaft een parameter waarmee u uw eigen logica kunt invoegen in de JSON-workflow voor coderen of decoderen. Voor stringify() heet deze parameter replacer en voor parse() heet de parameter reviver. Deze parameters hebben een functiedefinitie met twee argumenten die de volgende handtekening gebruiken:

function(k, v):*

toJSON()-methoden

De handtekening voor toJSON()-methoden is

public function toJSON(k:String):*

JSON.stringify() roept toJSON() aan, indien deze bestaat, voor elke publieke eigenschap die deze tegenkomt tijdens het doorlopen van een object. Een eigenschap bestaat uit een sleutelwaardepaar. Wanneer stringify() toJSON() aanroept, geeft het de sleutel k door van de eigenschap die op dat moment wordt onderzocht. Een typische toJSON()-implementatie evalueert elke eigenschapsnaam en retourneert de gewenste codering van de waarde van deze eigenschap.

De methode toJSON() kan een waarde van elk type (aangeduid als *) retourneren, niet alleen een tekenreeks. Dankzij dit variabele type retournering kan toJSON(), indien van toepassing, een object retourneren. Als een eigenschap van uw aangepaste klasse bijvoorbeeld een object uit een andere externe bibliotheek bevat, kunt u dat object retourneren als toJSON() uw eigenschap aantreft. JSON keert dan terug naar het externe object. De flow van het coderingsproces gaat als volgt:

  • Als toJSON() een object retourneert dat niet wordt geëvalueerd als een tekenreeks, keert stringify() terug naar dat object.

  • Als toJSON() een tekenreeks retourneert, wordt die waarde door stringify() opgenomen in een andere tekenreeks, die vervolgens wordt geretourneerd. Hierna wordt doorgegaan naar de volgende waarde.

In veel gevallen verdient het de voorkeur dat een object wordt geretourneerd in plaats van een JSON-tekenreeks die door uw toepassing is gemaakt. Bij het retourneren van een object wordt het geïntegreerde JSON-coderingsalgoritme toegepast en kan JSON ook geneste objecten recursief doorlopen.

De toJSON()-methode is niet gedefinieerd in de Object-klasse, noch in de meeste andere native klassen. Als deze methode ontbreekt, weet JSON dat het standaard doorlopen van de publieke eigenschappen van het object moet worden uitgevoerd. Indien gewenst, kunt u toJSON() ook gebruiken om de privé-eigenschappen van uw object te onthullen.

Enkele native klassen veroorzaken problemen die de ActionScript-bibliotheken niet in alle gevallen op effectieve wijze kunnen oplossen. Voor deze klassen biedt ActionScript een triviale implementatie die clients al naar gelang hun behoeften opnieuw kunnen implementeren. De volgende klassen verschaffen eenvoudige toJSON()-leden:

  • ByteArray

  • Date

  • Dictionary

  • XML

U kunt een subklasse maken van de klasse ByteArray om de methode toJSON() van deze klasse te overschrijven, of u kunt het prototype van de klasse opnieuw definiëren. De Date- en XML-klassen die als definitief worden gedeclareerd, vereisen dat u het prototype van de klasse gebruikt om toJSON() opnieuw te definiëren. De klasse Dictionary wordt als dynamisch gedeclareerd, zodat u meer vrijheid hebt om toJSON() te overschrijven.

Aangepast JSON-gedrag definiëren

Als u uw eigen JSON-codering en decodering wilt implementeren voor native klassen, hebt u de volgende mogelijkheden:

  • toJSON() definiëren of overschrijven voor uw aangepaste subklasse van een niet-definitieve native klasse

  • toJSON() definiëren of opnieuw definiëren voor het klassenprototype

  • De eigenschap toJSON definiëren voor een dynamische klasse

  • De parameters JSON.stringify() replacer en JSON.parser() reviver gebruiken

toJSON() definiëren voor het prototype van een geïntegreerde klasse

De native JSON-implementatie in ActionScript spiegelt het ECMAScript JSON-mechanisme dat is gedefinieerd in de vijfde editie van ECMA-262. Aangezien ECMAScript geen ondersteuning biedt voor klassen, definieert ActionScript JSON-gedrag in termen van op prototype gebaseerde verzending. Prototypen zijn voorlopers van ActionScript 3.0-klassen waarmee gesimuleerde overerving, het toevoegen van leden en herdefinities mogelijk worden.

U kunt in ActionScript toJSON() definiëren of opnieuw definiëren als het prototype van een willekeurige klasse. Dit recht is zelfs van toepassing op klassen die als definitief zijn gemarkeerd. Wanneer u toJSON() definieert voor een klasseprototype, geldt uw definitie voor alle instanties van die klasse binnen het bereik van uw toepassing. Hier ziet u bijvoorbeeld hoe u een toJSON()-methode kunt definiëren voor het prototype MovieClip:

MovieClip.prototype.toJSON = function(k):* { 
    trace("prototype.toJSON() called."); 
    return "toJSON"; 
} 

Als uw toepassing vervolgens stringify() aanroept voor een willekeurige MovieClip-instantie, retourneert stringify() de uitvoer van uwtoJSON() -methode:

var mc:MovieClip = new MovieClip(); 
var js:String = JSON.stringify(mc); //"prototype toJSON() called." 
trace("js: " + js); //"js: toJSON"

Het is ook mogelijk toJSON() te overschrijven in de native klassen die de methode definiëren. Met de volgende code overschrijft u bijvoorbeeld 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" 

toJSON() definiëren of overschrijven op klassenniveau

Toepassingen zijn niet altijd verplicht prototypen te gebruiken om toJSON() opnieuw te definiëren. U kunt toJSON() ook definiëren als lid van een subklasse als de bovenliggende klasse niet is gemarkeerd als definitief. U kunt bijvoorbeeld de klasse ByteArray uitbreiden en een openbare toJSON()-functie definiëren:

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"

In geval van dynamische klassen kunt u een toJSON-eigenschap toevoegen aan een object van die klasse en er als volgt een functie aan toewijzen:

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."}

U kunt toJSON() voor elke ActionScript-klasse overschrijven, definiëren of opnieuw definiëren. De meeste geïntegreerde ActionScript-klassen definiëren toJSON() echter niet. De Object-klasse definieert toJSON niet in het standaardprototype en declareert het niet als een klassenlid. Slechts enkele native klassen definiëren de methode als een prototypefunctie. In de meeste klassen is het dus niet mogelijk om toJSON() in de traditionele betekenis van het woord te overschrijven.

Native klassen die geen definitie bieden voor toJSON() worden geserialiseerd met betrekking tot JSON door de interne JSON-implementatie. Vervang deze geïntegreerde functionaliteit waar mogelijk niet. Als u een lid van toJSON() definieert, wordt uw logica gebruikt door de JSON-klasse, en niet de eigen functionaliteit van deze klasse.

De replacer-parameter JSON.stringify() gebruiken

Het is handig toJSON() te overschrijven voor het prototype als u het JSON-exportgedrag van een klasse in de gehele toepassing wilt wijzigen. In bepaalde gevallen is het echter mogelijk dat uw exportlogica alleen van toepassing is op speciale gevallen onder overgangsvoorwaarden. Voor dergelijke kleine wijzigingen kunt u de parameter replacer van de methode JSON.stringify() gebruiken.

De methode stringify() past de functie die is doorgegeven via de parameter replacer toe op het object dat wordt gecodeerd. De handtekening voor deze functie is vergelijkbaar met die van toJSON():

function (k,v):* 

In tegenstelling tot toJSON() vereist de functie replacer de waarde v plus de sleutel k. Dit verschil is nodig omdat stringify() wordt gedefinieerd voor het statische JSON-object in plaats van voor het object dat wordt gecodeerd. Als JSON.stringify()replacer(k,v) aanroept, doorloopt het het oorspronkelijke invoerobject. De impliciete parameter this die aan de functie replacer is doorgegeven, verwijst naar het object dat de sleutel en waarde bevat. Aangezien JSON.stringify() het oorspronkelijke invoerobject niet wijzigt, blijft dat object ongewijzigd in de container die wordt doorlopen. U kunt daarom de code this[k] gebruiken om een query uit te voeren naar de sleutel voor het oorspronkelijke object. De parameter v bevat de waarde die toJSON() omzet.

Net als toJSON() kan de functie replacer elk mogelijke type waarde retourneren. Als een replacer een tekenreeks retourneert, kan de JSON-engine de inhoud tussen escape-tekens plaatsen en deze inhoud tussen escape-tekens vervolgens tussen aanhalingstekens plaatsen. Deze manier van insluiten garandeert dat stringify() een geldig JSON-tekenreeksobject ontvangt dat een tekenreeks blijft in de volgende aanroep naar JSON.parse().

De volgende code gebruikt de parameter replacer en de impliciete parameter this om de waarden time en hours van een Date-object te retourneren:

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

De JSON.parse()-parameter reviver gebruiken

De parameter reviver van de methode JSON.parse() doet het tegenovergestelde van de functie replacer: de parameter zet een JSON-tekenreeks om in een bruikbaar ActionScript-object. Het argument reviver is een functie die twee parameters gebruikt en een willekeurig type retourneert:

function (k,v):* 

In deze functie is k een sleutel en is v de waarde van k. Net als stringify() doorloopt parse() de JSON-sleutelwaardeparen en past deze de functie reviver (als deze bestaat) toe op elk paar. Een mogelijk probleem is dat de JSON-klasse de ActionScript-klassenaam van een object niet uitvoert. Het kan dus lastig zijn te weten welk type object moet worden vernieuwd. Dit kan vooral lastig zijn in geval van geneste objecten. Tijdens het ontwerpen van de functies toJSON(), replacer en reviver kunt u manieren bedenken voor het identificeren van de ActionScript-objecten die worden geëxporteerd terwijl de originele objecten intact blijven.

Voorbeeld van parseren

Het volgende voorbeeld illustreert een strategie voor het vernieuwen van objecten die zijn geparseerd van JSON-tekenreeksen. In dit voorbeeld worden de volgende twee klassen gedefinieerd: JSONGenericDictExample en JSONDictionaryExtnExample. JSONGenericDictExample is een aangepaste Dictionary-klasse. Elke record bevat de naam en geboortedatum van een persoon, plus een unieke id. Steeds wanneer de constructor JSONGenericDictExample wordt aangeroepen, wordt het net gemaakte object toegevoegd aan een interne statische array met als id een statisch toenemend geheel getal. De klasse JSONGenericDictExample definieert ook de een methode revive() die alleen het gedeelte met het gehele getal extraheert uit het langere id-lid. De methode revive() gebruikt dit gehele getal om het juiste vernieuwbare object op te zoeken en te retourneren.

De klasse JSONDictionaryExtnExample vormt een uitbreiding van de klasse Dictionary van ActionScript. De records van deze klasse hebben geen ingestelde structuur en kunnen willekeurige gegevens bevatten. Gegevens worden toegewezen nadat een JSONDictionaryExtnExample-object is samengesteld in plaats van als eigenschappen die door de klasse zijn gedefinieerd. JSONDictionaryExtnExample legt de gebruikte JSONGenericDictExample-objecten vast als sleutels. Wanneer een JSONDictionaryExtnExample-object wordt vernieuwd, gebruikt de functie JSONGenericDictExample.revive() de id die aan JSONDictionaryExtnExample is gekoppeld om het juiste sleutelobject op te halen.

Het belangrijkste is echter dat de methode JSONDictionaryExtnExample.toJSON() in aanvulling op het JSONDictionaryExtnExample-object ook een markeringsreeks retourneert. Deze reeks identificeert de JSON-uitvoer als onderdeel van de klasse JSONDictionaryExtnExample. Deze markering neemt alle twijfel weg over het type object dat tijdens JSON.parse() wordt verwerkt.

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

Wanneer de volgende runtimescript JSON.parse() aanroept voor een JSONDictionaryExtnExample-object, roept de reviver-functie JSONGenericDictExample.revive() aan voor elk object in JSONDictionaryExtnExample. Bij deze aanroep wordt de id die staat voor de objectsleutel geëxtraheerd. Deze id wordt door de functie JSONGenericDictExample.revive()gebruikt om het opgeslagen JSONDictionaryExtnExample-object op te halen van een statische privé-array en het vervolgens te retourneren.

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