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.
Todd Anderson, een lid van de Community, verschaft een vergelijking tussen de native JSON-API en de door een derde partij ontwikkelde JSON-klasse as3corelib. Zie
Met native JSON werken in Flash Player 11
.
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 uw
toJSON()
-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]>]
|
|
|