ActionScript 3.0 stellt eine native API zum Kodieren und Dekodieren von ActionScript-Objekten bereit. Dazu wird das Format JavaScript Object Notation (JSON) verwendet. Die JSON-Klasse und ergänzende Mitgliederfunktionen folgen den Spezifikationen der ECMA-262, 5. Edition, mit einigen Abweichungen.
Community-Mitglied Todd Anderson stellt einen Vergleich der nativen JSON-API und der JSON-Klasse „as3corelib“ eines Drittanbieters bereit. Siehe
Working with Native JSON in Flash Player 11
.
Überblick über die JSON-API
Die ActionScript-JSON-API besteht aus der JSON-Klasse und
toJSON()
-Mitgliedfunktionen in einigen nativen Klassen. Bei Anwendungen, die eine benutzerdefinierte JSON-Kodierung für eine beliebige Klasse erfordern, stellt das ActionScript-Framework Möglichkeiten zum Überschreiben der Standardkodierung bereit.
Die JSON-Klasse verarbeitet intern den Import und Export für alle ActionScript-Klassen, die kein
toJSON()
-Mitglied bereitstellen. Für derartige Klassen arbeitet sich JSON durch die öffentlichen Eigenschaften aller Objekte, die erkannt werden. Wenn ein Objekt andere Objekte enthält, führt JSON eine Rekursion in die verschachtelten Objekte durch und beginnt mit demselben Untersuchungsdurchlauf. Wenn ein beliebiges Objekt eine
toJSON()
-Methode bereitstellt, verwendet JSON diese benutzerdefinierte Methode anstelle der internen Algorithmen.
Die JSON-Schnittstelle besteht aus einer Kodierungsmethode,
stringify()
, und einer Dekodierungsmethode,
parse()
. Beide Methoden stellen einen Parameter bereit, der es Ihnen erlaubt, Ihre eigene Logik in den JSON-Arbeitsablauf für das Kodieren oder Dekodieren einzufügen. Für
stringify()
heißt dieser Parameter
replacer
; für
parse()
ist es
reviver
. Diese Parameter nehmen eine Funktionsdefinition mit zwei Argumenten. Dazu wird die folgende Signatur verwendet:
function(k, v):*
toJSON()-Methoden
Die Signatur für
toJSON()
-Methoden lautet
public function toJSON(k:String):*
JSON.stringify()
ruft
toJSON()
, sofern vorhanden, für jede öffentliche Eigenschaft auf, die bei der Untersuchung eines Objekts gefunden wird. Eine Eigenschaft besteht aus einem Schlüssel-Wert-Paar. Wenn
stringify()
toJSON()
aufruft, wird der Schlüssel,
k
, der zurzeit untersuchten Eigenschaft übergeben. Eine typische
toJSON()
-Implementierung evaluiert jeden Eigenschaftennamen und gibt die gewünschte Kodierung ihres Werts zurück.
Die
toJSON()
-Methode kann Werte eines beliebigen Typs (gekennzeichnet als *) zurückgegeben, nicht nur Strings. Dieser variable Rückgabetyp ermöglicht
toJSON()
, ggf. ein Objekt zurückzugeben. Wenn zum Beispiel eine Eigenschaft in Ihrer benutzerdefinierten Klasse ein Objekt aus einer anderen Drittanbieterbibliothek enthält, können Sie dieses Objekt zurückgeben, wenn
toJSON()
auf die Eigenschaft trifft. JSON führt dann eine Rekursion in das Drittanbieterobjekt aus. Der Kodierungsprozessfluss verhält sich folgendermaßen:
-
Wenn
toJSON()
ein Objekt zurückgibt, das nicht zu einem String evaluiert wird, führt
stringify()
eine Rekursion in dieses Objekt aus.
-
Wenn
toJSON()
einen String zurückgibt, hüllt
stringify()
diesen in einen anderen String ein, gibt den umhüllten String zurück und fährt mit dem nächsten Wert fort.
Häufig ist es besser, ein Objekt zurückzugeben anstatt eines JSON-Strings, der von Ihrer Anwendung erstellt wurde. Durch die Zurückgabe eines Objekts wird der eingebaute JSON-Kodierungsalgorithmus genutzt, außerdem kann JSON dann eine Rekursion in verschachtelten Objekten ausführen.
Die
toJSON()
-Methode ist weder in der Object-Klasse noch in den meisten anderen nativen Klassen definiert. Das Nichtvorhandensein dieser Methode signalisiert JSON, den standardmäßigen Untersuchungsdurchlauf der öffentlichen Eigenschaften des Objekts auszuführen. Wenn Sie dies möchten, können Sie auch
toJSON()
verwenden, um die privaten Eigenschaften des Objekts bereitzustellen.
Einige native Klassen stellen jedoch Herausforderungen dar, die die ActionScript-Bibliotheken nicht für alle Verwendungszwecke lösen können. Für diese Klassen stellt ActionScript eine einfache Implementierung bereit, die Kunden für ihre Zwecke implementieren können. Die folgenden Klassen stellen triviale
toJSON()
-Mitglieder bereit:
-
ByteArray
-
Date
-
Dictionary
-
XML
Sie können eine Unterklasse der ByteArray-Klasse erstellen, um ihre
toJSON()
-Methode zu überschreiben, oder ihren Prototyp neu definieren. Da die Date- und XML-Klassen finale Klassen sind, müssen Sie den Klassenprototyp verwenden, um
toJSON()
neu zu definieren. Die Dictionary-Klasse ist dynamisch, was Ihnen zusätzliche Möglichkeiten beim Überschreiben von
toJSON()
gibt.
Definieren von JSON-Verhalten
Wenn Sie Ihre eigene JSON-Kodierung und Dekodierung für native Klassen implementieren möchten, haben Sie verschiedene Möglichkeiten:
-
Definieren oder Überschreiben von
toJSON()
in Ihrer benutzerdefinierten Unterklasse einer nicht-finalen nativen Klasse
-
Definieren oder Neudefinieren von
toJSON()
im Klassenprototyp
-
Definieren einer
toJSON
-Eigenschaft in einer dynamischen Klasse
-
Verwenden der Parameter
JSON.stringify() replacer
und
JSON.parser() reviver
Definieren von toJSON() für den Prototyp einer integrierten Klasse
Die native JSON-Implementierung in ActionScript spiegelt den ECMAScript-JSON-Mechanismus wider, der in ECMA-262, 5. Edition definiert ist. Da ECMAScript keine Klassen unterstützt, definiert ActionScript das JSON-Verhalten anhand prototyp-basierter Absetzungen. Prototypen sind Vorläufer von ActionScript 3.0-Klassen, die das simulierte Vererben sowie das Hinzufügen und Neudefinieren von Mitgliedern zulassen.
Mit ActionScript können Sie
toJSON()
für den Prototyp jeder Klasse definieren oder neu definieren. Dies gilt sogar für Klassen, die als final gekennzeichnet sind. Wenn Sie
toJSON()
für einen Klassenprototyp definieren, wird Ihre Definition die aktuelle Definition für alle Instanzen dieser Klasse im Rahmen Ihrer Anwendung. So können Sie zum Beispiel eine
toJSON()
-Methode für den MovieClip-Prototyp definieren:
MovieClip.prototype.toJSON = function(k):* {
trace("prototype.toJSON() called.");
return "toJSON";
}
Wenn Ihre Anwendung dann
stringify()
für eine beliebige MovieClip-Instanz aufruft, gibt
stringify()
die Ausgabe Ihrer
toJSON()
-Methode zurück:
var mc:MovieClip = new MovieClip();
var js:String = JSON.stringify(mc); //"prototype toJSON() called."
trace("js: " + js); //"js: toJSON"
Sie können
toJSON()
auch in nativen Klassen, die diese Methode definieren, überschreiben. Mit dem folgenden Code überschreiben Sie zum Beispiel
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"
Definieren oder Überschreiben von toJSON() auf Klassenebene
Anwendungen müssen nicht immer Prototypen verwenden, um
toJSON()
neu zu definieren. Sie können
toJSON()
auch als Mitglied einer Unterklasse definieren, falls die übergeordnete Klasse nicht als finale Klasse gekennzeichnet ist. Sie können zum Beispiel die ByteArray-Klasse erweitern und eine öffentliche
toJSON()
-Funktion definieren:
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"
Wenn eine Klasse dynamisch ist, können Sie einem Objekt dieser Klasse eine
toJSON
-Eigenschaft hinzufügen und ihr auf folgende Weise eine Funktion zuweisen:
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."}
Sie können
toJSON()
in jeder ActionScript-Klasse überschreiben, definieren oder neu definieren. Die meisten integrierten ActionScript-Klassen definieren
toJSON()
jedoch nicht. Die Object-Klasse definiert
toJSON
weder in ihrem Standardprototyp noch als Klassenmitglied. Nur wenige native Klassen definieren die Methode als Prototypfunktion. Deshalb können Sie
toJSON()
in den meisten Klassen nicht im herkömmlichen Sinne überschreiben.
Native Klassen, die
toJSON()
nicht definieren, werden durch die interne JSON-Implementierung zu JSON serialisiert. Vermeiden Sie nach Möglichkeit, diese eingebaute Funktionalität zu ersetzen. Wenn Sie ein
toJSON()
-Mitglied definieren, verwendet die JSON-Klasse statt ihrer eigenen Funktionalität Ihre Logik.
Verwenden des replacer-Parameters von JSON.stringify()
Das Überschreiben von
toJSON()
im Prototyp ist hilfreich zum Ändern des JSON-Exportverhaltens einer Klasse in der gesamten Anwendung. In einigen Fällen gilt Ihre Exportlogik aber möglicherweise nur für bestimmte Szenarien unter vorübergehenden Bedingungen. Um diese Änderungen mit kleinem Umfang zu ermöglichen, können Sie den
replacer
-Parameter der
JSON.stringify()
-Methode verwenden.
Die
stringify()
-Methode wendet die über den
replacer
-Parameter übergebene Funktion auf das Objekt an, das kodiert wird. Die Signatur dieser Funktion ähnelt der von
toJSON()
:
function (k,v):*
Anders als
toJSON()
erfordert die
replacer
-Funktion sowohl den Wert,
v
, als auch den Schlüssel
k
. Dieser Unterschied ist notwendig, da
stringify()
für das statische JSON-Objekt anstatt für das kodierte Objekt definiert wird. Wenn
JSON.stringify()
replacer(k,v)
aufruft, wird das ursprüngliche Eingabeobjekt untersucht. Der implizite
this
-Parameter, der an die
replacer
-Funktion übergeben wird, verweist auf das Objekt, das den Schlüssel und den Wert enthält. Da
JSON.stringify()
das ursprüngliche Eingabeobjekt nicht verändert, bleibt dieses Objekt unverändert im untersuchten Container. Sie können den Code
this[k]
verwenden, um den Schlüssel im ursprünglichen Objekt abzufragen. Der
v
-Parameter enthält den Wert, den
toJSON()
konvertiert.
Wie auch
toJSON()
kann die
replacer
-Funktion Werte beliebiger Typen ausgeben. Wenn
replacer
einen String zurückgibt, setzt die JSON-Engine die Inhalte in Anführungszeichen und umhüllt diese geschützten Inhalte dann ebenfalls mit Anführungszeichen. Dieses Umhüllen stellt sicher, dass
stringify()
ein gültiges JSON-Stringobjekt empfängt, das in einem nachfolgenden Aufruf von
JSON.parse()
auch ein String bleibt.
Der folgende Code verwendet den
replacer
-Parameter und den impliziten
this
-Parameter, um die
time
- und
hours
-Werte eines Date-Objekts zurückzugeben:
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;
});
Verwenden des reviver-Parameters von JSON.parse()
Der
reviver
-Parameter der
JSON.parse()
-Method ist das Gegenstück zur
replacer
-Funktion: Er konvertiert einen JSON-String in ein verwendbares ActionScript-Objekt. Das
reviver
-Argument ist eine Funktion, die zwei Parameter nimmt und einen beliebigen Typ zurückgeben kann:
function (k,v):*
In dieser Funktion ist
k
ein Schlüssel und
v
ist der Wert von
k
. Wie
stringify()
, so untersucht
parse()
die JSON-Schlüssel-Wert-Paare und wendet die
reviver
-Funktion, sofern vorhanden, auf jedes Paar an. Ein potenzielles Problem ist die Tatsache, dass die JSON-Klasse nicht den ActionScript-Klassennamen eines Objekts ausgibt. Deshalb kann es schwierig sein, zu wissen, auf welches Objekt die reviver-Funktion angewendet werden soll. Dies gilt umso mehr, wenn Objekte verschachtelt sind. Beim Entwurf von
toJSON()
,
replacer
- und
reviver
-Funktionen können Sie Möglichkeiten einplanen, die ActionScript-Objekte zu identifizieren, die exportiert werden, während die Originalobjekte intakt bleiben.
Parsing-Beispiel
Das folgende Beispiel demonstriert eine Strategie zum Anwenden der reviver-Funktion auf Objekte, die aus JSON-Strings geparst wurden. In diesem Beispiel werden zwei Klassen definiert: JSONGenericDictExample und JSONDictionaryExtnExample. Die JSONGenericDictExample-Klasse ist eine benutzerdefinierte Wörterbuchklasse. Jeder Datensatz enthält den Namen und den Geburtstag einer Person sowie eine eindeutige ID. Jedes Mal, wenn der JSONGenericDictExample-Konstruktor aufgerufen wird, fügt er das neu erstellte Objekt einem internen statischen Array hinzu, mit einer statisch inkrementierten Ganzzahl als ID. Die JSONGenericDictExample-Klasse definiert auch eine
revive()
-Methode, die nur den Ganzzahlteil aus dem längeren
id
-Mitglied extrahiert. Die
revive()
-Methode verwendet diese Ganzzahl, um das richtige Objekt nachzuschlagen und zurückzugeben.
Die JSONDictionaryExtnExample-Klasse erweitert die ActionScript-Dictionary-Klasse. Ihre Datensätze haben keine festgelegte Struktur und können beliebige Daten enthalten. Daten werden nach der Konstruktion eines JSONDictionaryExtnExample-Objekts zugewiesen, nicht als klassendefinierte Eigenschaften. JSONDictionaryExtnExample-Datensätze verwenden JSONGenericDictExample-Objekte als Schlüssel. Wenn ein JSONDictionaryExtnExample-Objekt wiederbelebt wird, verwendet die
JSONGenericDictExample.revive()
-Funktion die ID, die JSONDictionaryExtnExample zugeordnet ist, um das richtige Schlüsselobjekt abzurufen.
Am wichtigsten ist, dass die
JSONDictionaryExtnExample.toJSON()
-Methode zusätzlich zum JSONDictionaryExtnExample-Objekt einen Markerstring zurückgibt. Dieser String kennzeichnet die JSON-Ausgabe als zur JSONDictionaryExtnExample-Klasse gehörig. Dieser Marker gibt eindeutig an, welcher Objekttyp während
JSON.parse()
verarbeitet wird.
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;
}
}
}
Wenn das folgende Laufzeitskript
JSON.parse()
für ein JSONDictionaryExtnExample-Objekt aufruft, ruft die
reviver
-Funktion
JSONGenericDictExample.revive()
für jedes Objekt in JSONDictionaryExtnExample auf. Mit diesem Aufruf wird die ID extrahiert, die den Objektschlüssel darstellt. Die
JSONGenericDictExample.revive()
-Funktion verwendet diese ID, um das gespeicherte JSONDictionaryExtnExample-Objekt aus einem privaten statischen Array abzurufen und zurückzugeben.
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]>]
|
|
|