Verwenden der nativen JSON-Funktionalität

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.

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