Använda systemspecifik JSON-funktionalitet

I ActionScript 3.0 finns ett systemspecifikt API för kodning och avkodning av ActionScript-objekt med hjälp av JSON-formatet (JavaScript Object Notation). JSON-klassen och de medlemsfunktioner som stöds uppfyller med några få undantag den femte upplagespecifikationen av ECMA-262.

Översikt av JSON-API

ActionScript JSON API består av JSON-klassen och medlemsfunktioner för toJSON() i fem systemspecifika klasser. För program som kräver en egen JSON-kodning för någon klass, ger ActionScript-ramverket möjlighet att åsidosätta standardkodningen.

Klassen JSON hanterar internt import och export för alla ActionScript-klasser som inte har en toJSON() -medlem. I sådana fall går JSON igenom de publika egenskaperna för varje objekt som upptäcks. Om ett objekt innehåller andra objekt, repeteras JSON för de kapslade objekten och utför samma genomgång. Om något av objekten tillhandahåller en toJSON() -metod, kommer den anpassade metoden för JSON att användas i stället för dess interna algoritm.

JSON-gränssnittet består av en kodningsmetod, stringify() , och en avkodningsmetod, parse() . Dessa metoder ger en parameter som gör att du kan infoga egen logik i arbetsflödet för JSON-kodning/-avkodning. För stringify() heter parametern replacer och för parse() reviver . Dessa parametrar tar emot en funktionsdefinition med två argument med hjälp av följande signatur:

function(k, v):*

toJSON()-metoder

Signaturen för toJSON() -metoderna är

public function toJSON(k:String):*

JSON.stringify() anropar toJSON() , om den finns, för varje publik egenskap som upptäcks under genomgången av ett objekt. Egenskapen består av ett nyckelvärdepar. När stringify( ) anropar toJSON() , skickas nyckeln, k , för egenskapen som för närvarande undersöks. I en vanlig toJSON() -implementation utvärderas varje egenskapsnamn och returnerar den önskade kodningen av värdet.

Metoden toJSON() kan returnera alla typer av värden (anges med *), inte bara en sträng. Den här variabla returtypen tillåter toJSON() att returnera ett objekt om det skulle behövas. Om till exempel en egenskap för din klass innehåller ett objekt från ett annat tredjepartsbibliotek, kan du returnera objektet när toJSON() upptäcker egenskapen. JSON kommer sedan att repeteras i tredjepartsobjeket. Kodningsprocessen ser ut så här:

  • Om toJSON() returnerar ett objekt som inte utvärderas som en sträng, repeteras stringify() i detta objekt.

  • Om toJSON() returnerar en sträng för stringify() över det värdet till en annan sträng, returnerar den överförda strängen och fortsätter sedan till nästa värde.

I många fall är det bättre att ett objekt returneras än att en JSON-sträng som skapats av ditt program returneras. Returnerandet av ett objekt utnyttjar den inbyggda JSON-kodningsalgoritmen och gör det även möjligt för JSON att gå igenom kapslade objekt rekursivt.

Metoden toJSON() är inte definierad i klassen Object och inte heller i de flesta andra inbyggda klasser. Dess frånvaro leder till att JSON utför standardgenomgången av objektets publika egenskaper. Om du vill kan du även använda toJSON() för att visa objektets privata egenskaper.

Vissa inbyggda klasser kan medföra problem som ActionScript-biblioteken inte kan lösa effektivt för alla tänkbara användningssätt. För dessa klasser erbjuder ActionScript en enkel implementation som kan återimplementeras på klienter för att passa för de behov som finns. Klasserna som tillhandahåller enkla toJSON() -medlemmar är:

  • ByteArray

  • Date

  • Dictionary

  • XML

Du kan göra en underklass av ByteArray-klassen för att åsidosätta dess toJSON() -metod eller så kan du omdefiniera dess prototyp. Klasserna Date och XML, som deklareras sist, kräver att du använder klassprototypen för att omdefiniera toJSON() . Klassen Dictionary deklareras dynamiskt, vilket ger dig extra frihet att åsidosätta toJSON() .

Definiera anpassade JSON-beteenden

Om du vill implementera egen JSON-kodning och avkodning för inbyggda klasser kan du välja mellan flera alternativ. Du kan:

  • definiera eller åsidosätta toJSON() för din anpassade underklass för en icke-slutlig inbyggd klass,

  • definiera eller definiera om toJSON() för klassens prototyp,

  • definiera en toJSON -egenskap för en dynamisk klass,

  • använda parametrarna JSON.stringify() replacer och JSON.parser() reviver .

Definiera toJSON() på prototypen för en inbyggd klass

Den systemspecifika JSON-implementeringen i ActionScript speglar ECMAScript JSON-förhållandet definierad i ECMA-262, femte upplagan. Efterson det inte finns stöd för klasser i ECMAScript, definieras JSON-beteendet i ActionScript utifrån prototypbaserade skickningar. Prototyper är föregångare till ActionScript 3.0-klasser, som tillåter simulerade arv, tillägg av medlemmar och omdefinitioner.

ActionScript tillåter att du definierar eller omdefinierar toJSON() på prototypen för valfri klass. Denna behörighet gäller även för klasser som är märkta som slutgiltiga. När du definierar toJSON() för en klassprototyp, kommer definitionen att gälla för alla instanser av klassen inom programmets räckvidd. Här visas till exempel hur du kan definiera en toJSON() -metod för prototypen MovieClip:

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

När programmet sedan anropar stringify() i en MovieClip-instans, returnerar stringify() utdata för din toJSON() -metod:

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

Du kan också åsidosätta toJSON() i inbyggda klasser som definierar metoden. I följande kod åsidosätts till exempel 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" 

Definiera eller åsidosätta toJSON() på klassnivå

Program behöver inte alltid använda prototyper för att omdefiniera toJSON() . Du kan också definiera toJSON() som en medlem i en underklass, om den överordnade klassen inte har markerats som slutlig. Du kan till exempel utöka klassen ByteArray och definiera en offentlig toJSON() -funktion:

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"

Om en klass är dynamisk kan du lägga till en toJSON -egenskap till ett objekt i denna klass och tilldela en funktion till den enligt följande:

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

Du kan åsidosätta, definiera eller definiera om toJSON() för alla ActionScript-klasser. De flesta inbyggda ActionScript-klasser definierar emellertid inte toJSON() . Klassen Object definierar inte toJSON i dess standardprototyp och deklarerar den inte som en klassmedlem. Endast en handfull inbyggda klasser definierar metoden som en prototypfunktion. Således kan du i de flesta klasser inte åsidosätta toJSON() på traditionellt sätt.

Inbyggda klasser som inte definierar toJSON() serialiseras till JSON via den interna JSON-implementeringen. Undvik om möjligt att ersätta denna inbyggda funktion. Om du definierar en toJSON() -medlem använder JSON-klassen din logik i stället för dess egna funktioner.

Använda parametern replacer för JSON.stringify()

Att åsidosätta toJSON() på en prototyp är användbart när du vill ändra en klass JSON-exportbeteende i hela programmet. I vissa klasser kanske din exportlogik endast gäller speciella fall under kortvariga villkor. För att tillgodose sådana små förändringar kan du använda parametern replacer i metoden JSON.stringify() .

Metoden stringify() tillämpar funktionen som skickas med replacer -parametern till objektet som kodas. Signaturen för denna funktion påminner om den som gäller för toJSON() :

function (k,v):* 

Till skillnad från toJSON() , kräver funktionen replacer värdet, v , samt nyckeln, k . Den här skillnaden är nödvändig eftersom stringify() definieras för ett statiskt JSON-objekt i stället för att objektet kodas. När JSON.stringify() anropar replacer(k,v ), kommer det att gå igenom det ursprungliga indataobjektet . Den implicita this -parametern, som skickas till funktionen replacer , refererar till objektet som håller nyckeln och värdet. Eftersom JSON.stringify() inte förändrar det ursprungliga indataobjektet, kommer detta objekt att förbli oförändrat i den behållare som gås igenom. Du kan således använda koden this[k] för att tillfråga nyckeln i originalobjektet. Parametern v innehåller värdet som konverteras med toJSON() .

Precis som toJSON() , kan funktionen replacer returnera olika typer av värden. Om replacer returnerar en sträng, kommer JSON-motorn att undertrycka innehållet med citattecken och omsluter sedan detta innehåll med ytterligare citattecken. Den här förpackningen garanterar att stringify() tar emot ett giltigt JSON-strängobjekt som behålls som en sträng i ett efterföljande anrop till JSON.parse() .

I följande kod används parametern replacer och den implicita this -parametern för att returnera värdena för time och hours i Date-objektet:

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

Använda reviver-parametern i JSON.parse()

Parametern reviver i JSON.parse() -metoden gör det motsatta mot replacer -funktionen: Den konverterar en JSON-sträng till ett användbart ActionScript-objekt. Argumentet reviver är en funktion som tar två parametrar och returnerar valfri typ:

function (k,v):* 

I denna funktion är k en nyckel och v värdet på k . Precis som stringify() , går parse() igenom nyckelvärdepar för JSON och tillämpar reviver -funktionen, om det finns någon, på varje par. Ett potentiellt problem är det faktum att JSON-klassen inte lämnar ut ett objekts ActionScript-klassnamn. Det kan således vara svårt att veta vilken typ av objekt som ska återställas. Detta problem kan vara speciellt besvärligt när objekten är kapslade. När du skapar toJSON() -, replacer - och reviver -funktioner, kan du göra egna identifiera för ActionScript-objekten som är exporterade, samtidigt som du behåller originalobjekten intakta.

Tolkningsexempel

I följande exempel visas en strategi för att återställa objekt tolkade från JSON-strängar. I det här exemplet definieras två klasser: JSONGenericDictExample och JSONDictionaryExtnExample. Klassen JSONGenericDictExample är en anpassad ordlisteklass. Varje post innehåller ett personnamn och födelsedatum samt ett unikt ID. Varje gång JSONGenericDictExample-konstruktorn anropas, lägger den till det nyligen skapade objektet i en intern statisk array med ett jämt ökande heltal som dess ID. Klassen JSONGenericDictExample definierar även en revive() -metod, som extraherar endast heltalsdelen från den längre id -medlemmen. Metoden revive() använder detta heltal för att söka efter och returnera rätt återställningsobjekt.

Klassen JSONDictionaryExtnExample utökar ActionScript-klassen Dictionary. Dess poster har ingen fast struktur, utan kan innehålla alla typer av data. Data tilldelas efter det att ett JSONDictionaryExtnExample-objekt har konstruerats, och inte som klassdefinierade egenskaper. JSONDictionaryExtnExample-poster använder JSONGenericDictExample-objekt som nycklar. När ett JSONDictionaryExtnExample-objekt återställs använder funktionen JSONGenericDictExample.revive() det ID som är associerat med JSONDictionaryExtnExample för att hämta korrekt nyckelobjekt.

Viktigast av allt är att metoden JSONDictionaryExtnExample.toJSON() returnerar en markörsträng, utöver JSONDictionaryExtnExample-objektet. Denna sträng identifierar att dessa JSON-utdata tillhör klassen JSONDictionaryExtnExample. Denna markör lämnar inget tvivel om vilken objekttyp som bearbetas med 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; 
        } 
    } 
} 

När följande skript anropar JSON.parse() för ett JSONDictionaryExtnExample-objekt anropar funktionen reviver JSONGenericDictExample.revive() för alla objekt i JSONDictionaryExtnExample. Det här anropet extraherar det ID som representerar objektnyckeln. ID:t används sedan av funktionen JSONGenericDictExample.revive() för att hämta och returnera det lagrade JSONDictionaryExtnExample-objektet från en privat, statisk array.

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