Język ActionScript 3.0 oferuje natywny interfejs API służący do kodowania i dekodowania obiektów ActionScript za pomocą formatu JSON (JavaScript Object Notation). Klasa JSON i funkcje pomocnicze elementów składowych są zgodne z piątym wydaniem specyfikacji ECMA-262 (z kilkoma różnicami).
Członek społeczności Todd Anderson udostępnia porównanie natywnego interfejsu API mechanizmu JSON i klasy JSON as3corelib pochodzącej z innego źródła. Zobacz
Korzystanie z natywnej obsługi mechanizmu JSON w programie Flash Player 11
.
Omówienie interfejsu API mechanizmu JSON
Interfejs API mechanizmu JSON w języku ActionScript obejmuje klasę JSON i funkcje składowe
toJSON()
w kilku klasach natywnych. Na potrzeby aplikacji wymagających własnego kodowania JSON w dowolnej klasie architektura ActionScript udostępnia sposoby przesłaniania kodowania domyślnego.
Klasa JSON wewnętrznie obsługuje importowanie i eksportowanie dla dowolnej klasy ActionScript, która nie udostępnia elementu
toJSON()
. W takich przypadkach klasa JSON przegląda właściwości publiczne każdego napotkanego obiektu. Jeśli obiekt zawiera inne obiekty, klasa JSON przechodzi rekurencyjnie do obiektów zagnieżdżonych i wykonuje takie samo przeglądanie. Jeśli dowolny obiekt udostępnia metodę
toJSON()
, klasa JSON używa tej własnej metody zamiast algorytmu wewnętrznego.
Interfejs JSON składa się z metody kodującej
stringify()
i metody dekodującej
parse()
. Każda z tych metod udostępnia parametr umożliwiający wstawienie własnej logiki do przepływu pracy kodowania lub dekodowania danych w formacie JSON. W metodzie
stringify()
jest to parametr
replacer
. W metodzie
parse()
jest to parametr
reviver
. W tych parametrach jest przekazywana definicja funkcji z dwoma argumentami o następującym zapisie:
function(k, v):*
Metody toJSON()
Zapis dotyczący metod
toJSON()
:
public function toJSON(k:String):*
Metoda
JSON.stringify()
wywołuje metodę
toJSON()
, jeśli ona istnieje, dla każdej właściwości publicznej napotkanej podczas przeglądania obiektu. Właściwość składa się z pary klucz-wartość. Gdy metoda
stringify()
wywołuje metodę
toJSON()
, przekazuje klucz
k
właściwości, którą bada w danej chwili. Typowa implementacja metody
toJSON()
oblicza każdą nazwę właściwości i zwraca żądane kodowanie wartości.
Metoda
toJSON()
może zwrócić wartość dowolnego typu (oznaczoną jako *) — nie tylko typu String. Zmienny typ zwracanej wartości pozwala metodzie
toJSON()
zwrócić obiekt, jeśli jest to wymagane. Jeśli na przykład właściwość własnej klasy użytkownika zawiera obiekt z biblioteki innej firmy, można zwrócić ten obiekt, gdy metoda
toJSON()
napotka tę właściwość. Obiekt JSON przechodzi wtedy rekurencyjnie do obiektu pochodzącego z biblioteki innej firmy. Przepływ procesu kodowania zachowuje się następująco:
-
Jeśli metoda
toJSON()
zwraca obiekt, którego wartość nie jest ciągiem, metoda
stringify()
przechodzi rekurencyjnie do tego obiektu.
-
Jeśli metoda
toJSON()
zwraca ciąg, metoda
stringify()
opakowuje tę wartość w inny ciąg, zwraca opakowany ciąg i przechodzi do kolejnej wartości.
Często zwrócenie obiektu jest lepszym rozwiązaniem niż zwrócenie ciągu JSON utworzonego przez aplikację. Zwrócenie obiektu pozwala użyć wbudowanego algorytmu kodowania JSON i pozwala mechanizmowi JSON przejść rekurencyjnie do zagnieżdżonych obiektów.
Metoda
toJSON()
nie jest zdefiniowana w klasie Object ani w większości innych klas natywnych. W przypadku braku tej metody klasa JSON w standardowy sposób przegląda właściwości publiczne obiektu. Można również użyć metody
toJSON()
do udostępnienia właściwości prywatnych obiektu.
Niektóre klasy natywne mają wymagania, których biblioteki ActionScript nie mogą efektywnie obsłużyć w każdym przypadku użycia. Dla takich klas język ActionScript udostępnia trywialną implementację, którą klienci mogą zastąpić w celu realizacji określonych potrzeb. Do klas udostępniających trywialne elementy
toJSON()
należą:
-
ByteArray
-
Date
-
Dictionary
-
XML
Można utworzyć podklasę klasy ByteArray w celu przesłonięcia jej metody
toJSON()
albo zmienić definicję jej prototypu. W przypadku klas Date i XML, które są zadeklarowane jako ostateczne, zmiana definicji metody
toJSON()
wymaga użycia prototypu klasy. Klasa Dictionary jest zadeklarowana jako dynamiczna, co zapewnia większą swobodę przesłaniania metody
toJSON()
.
Definiowanie własnych zachowań JSON
Aby zaimplementować własne metody kodowania i dekodowania JSON dla klas natywnych, można wybrać jedną z kilku opcji:
-
Zdefiniowanie lub przesłonięcie metody
toJSON()
we własnej podklasie klasy natywnej, która nie jest ostateczna (nie ma modyfikatora final)
-
Zdefiniowanie lub zmiana definicji metody
toJSON()
w prototypie klasy
-
Zdefiniowanie właściwości
toJSON
w klasie dynamicznej
-
Użycie parametrów
JSON.stringify() replacer
i
JSON.parser() reviver
Definiowanie metody toJSON() w prototypie klasy wbudowanej
Natywna implementacja mechanizmu JSON w języku ActionScript odpowiada mechanizmowi JSON ECMAScript zdefiniowanemu w wydaniu 5 specyfikacji ECMA-262. Mechanizm ECMAScript nie obsługuje klas, dlatego w języku ActionScript zachowanie mechanizmu JSON jest zdefiniowane w formie wywołań opartych na prototypach. Prototypy są pierwowzorami klas języka ActionScript 3.0. Umożliwiają one symulowanie dziedziczenia oraz dodawanie i zmienianie definicji elementów składowych.
Język ActionScript umożliwia zdefiniowanie lub zmianę definicji metody
toJSON()
w prototypie dowolnej klasy. Ta możliwość dotyczy nawet klas, które są oznaczone jako ostateczne. Zdefiniowanie metody
toJSON()
w prototypie klasy powoduje, że definicja staje się obowiązująca dla wszystkich wystąpień tej klasy w zasięgu aplikacji. Poniżej podano przykładowy kod definiujący metodę
toJSON()
w prototypie klasy MovieClip.
MovieClip.prototype.toJSON = function(k):* {
trace("prototype.toJSON() called.");
return "toJSON";
}
Gdy następnie aplikacja wywoła metodę
stringify()
wystąpienia klasy MovieClip, metoda
stringify()
zwróci dane wyjściowe metody
toJSON()
użytkownika.
var mc:MovieClip = new MovieClip();
var js:String = JSON.stringify(mc); //"prototype toJSON() called."
trace("js: " + js); //"js: toJSON"
Metodę
toJSON()
można też przesłonić w klasach natywnych, które ją definiują. Poniższy przykładowy kod przesłania metodę
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"
Definiowanie i przesłanianie metody toJSON() na poziomie klasy
Aplikacje nie muszą zawsze zmieniać definicji metody
toJSON()
za pomocą prototypów. Można także zdefiniować metodę
toJSON()
jako element podklasy, jeśli klasa macierzysta nie jest oznaczona jako ostateczna (nie ma modyfikatora final). Można na przykład rozszerzyć klasę ByteArray i zdefiniować publiczną funkcję
toJSON()
.
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"
Jeśli klasa jest dynamiczna, można dodać właściwość
toJSON
do obiektu tej klasy i przypisać do niej funkcję w następujący sposób:
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."}
Metodę
toJSON()
można przesłonić, zdefiniować lub zdefiniować ponownie w dowolnej klasie ActionScript. Metoda
toJSON()
nie jest zdefiniowana w większości wbudowanych klas ActionScript. Właściwość
toJSON
nie jest zdefiniowana w prototypie domyślnym klasy Object ani zadeklarowana jako element tej klasy. Omawiana metoda jest zdefiniowana jako funkcja prototypu tylko w kilku klasach natywnych. Z tego powodu najczęściej nie można przesłonić metody
toJSON()
w normalny sposób.
Klasy natywne, w których nie jest zdefiniowana metoda
toJSON()
, podlegają serializacji do formatu JSON przy użyciu wewnętrznej implementacji mechanizmu JSON. Jeśli to możliwe, należy unikać zastępowania tych wbudowanych funkcji. Jeśli zostanie zdefiniowany element składowy
toJSON()
, klasa JSON będzie używać logiki określonej w programie zamiast własnych funkcji.
Używanie parametru replacer metody JSON.stringify()
Przesłanianie metody
toJSON()
w prototypie jest przydatne w przypadku zmiany sposobu eksportowania klasy do formatu JSON w obrębie całej aplikacji. Czasami jednak logika eksportowania określona przez użytkownika może dotyczyć tylko specjalnych przypadków w zmiennych warunkach. Aby zastosować takie zmiany o małym zasięgu, można skorzystać z parametru
replacer
metody
JSON.stringify()
.
Metoda
stringify()
stosuje funkcję przekazaną w parametrze
replacer
do kodowanego obiektu. Zapis tej funkcji jest podobny do zapisu metody
toJSON()
.
function (k,v):*
W przeciwieństwie do metody
toJSON()
funkcja
replacer
wymaga wartości
v
oraz klucza
k
. Ta różnica jest konieczna, ponieważ metoda
stringify()
jest zdefiniowana w statycznym obiekcie JSON, a nie w kodowanym obiekcie. Gdy metoda
JSON.stringify()
wywołuje funkcję
replacer(k,v)
, przegląda ona pierwotny obiekt wejściowy. Niejawny parametr
this
przekazywany do funkcji
replacer
odnosi się do obiektu zawierającego klucz i wartość. Metoda
JSON.stringify()
nie modyfikuje pierwotnego obiektu wejściowego — pozostaje on niezmieniony w przeglądanym kontenerze. Można dzięki temu użyć kodu
this[k]
w celu odczytania klucza z pierwotnego obiektu. Parametr
v
zawiera wartość konwertowaną przez metodę
toJSON()
.
Podobnie jak metoda
toJSON()
, funkcja
replacer
może zwrócić wartość dowolnego typu. Jeśli funkcja
replacer
zwróci ciąg znaków, mechanizm JSON oznaczy znaki specjalne zawartości w cudzysłowie, a następnie umieści tę zmodyfikowaną zawartość w cudzysłowie. Pozwala to zagwarantować, że metoda
stringify()
otrzyma prawidłowy obiekt ciągu znaków JSON, który pozostanie ciągiem znaków w wywoływanej następnie metodzie
JSON.parse()
.
W poniższym kodzie parametr
replacer
i niejawny parametr
this
służą do zwrócenia wartości
time
(czas) i
hours
(godziny) obiektu Date.
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;
});
Korzystanie z parametru reviver metody JSON.parse()
Parametr
reviver
metody
JSON.parse()
pozwala wykonywać operację odwrotną w stosunku do funkcji
replacer
— konwertowanie ciągu znaków JSON na użyteczny obiekt ActionScript. Argument
reviver
jest funkcją otrzymującą dwa parametry i zwracającą dowolny typ.
function (k,v):*
W tej funkcji wartość
k
jest kluczem, a wartość
v
jest wartością klucza
k
. Podobnie jak metoda
stringify()
, metoda
parse()
przegląda pary klucz-wartość JSON i stosuje do każdej z nich funkcję
reviver
(jeśli taka funkcja istnieje). Potencjalnym problemem jest to, że klasa JSON nie przekazuje nazwy klasy ActionScript dotyczącej obiektu. Może to utrudniać ustalenie typu obiektu do przywrócenia. Ten problem może być szczególnie istotny, gdy obiekty są zagnieżdżone. Projektując funkcje
replacer
i
reviver
metody
toJSON()
, można opracować sposoby identyfikowania eksportowanych obiektów ActionScript przy jednoczesnym pozostawieniu oryginalnych obiektów bez zmian.
Przykład analizy
Poniższy przykład ilustruje strategię przywracania obiektów uzyskanych w wyniku analizy ciągów znaków JSON. W tym przykładzie są zdefiniowane dwie klasy: JSONGenericDictExample i JSONDictionaryExtnExample. Klasa JSONGenericDictExample jest własną klasą słownika. Każdy rekord zawiera imię i datę urodzenia osoby, a także unikatowy identyfikator. Przy każdym wywołaniu konstruktor klasy JSONGenericDictExample dodaje nowo utworzony obiekt do wewnętrznej tablicy statycznej ze statycznie zwiększaną wartością całkowitą będącą identyfikatorem obiektu. Klasa JSONGenericDictExample ma również zdefiniowaną metodę
revive()
, która wyodrębnia tylko część całkowitą z dłuższego elementu
id
. Metoda
revive()
używa tej wartości całkowitej do wyszukania i zwrócenia poprawnego obiektu, który można przywrócić.
Klasa JSONDictionaryExtnExample rozszerza klasę Dictionary języka ActionScript. Jej rekordy nie mają ustawionej struktury i mogą zawierać dowolne dane. Dane są przypisywane po skonstruowaniu obiektu klasy JSONDictionaryExtnExample, a nie jako właściwości zdefiniowane w klasie. Rekordy w klasie JSONDictionaryExtnExample używają obiektów JSONGenericDictExample jako kluczy. Podczas przywracania obiektu klasy JSONDictionaryExtnExample funkcja
JSONGenericDictExample.revive()
używa identyfikatora skojarzonego z klasą JSONDictionaryExtnExample, aby pobrać poprawny obiekt klucza.
Co najważniejsze, oprócz obiektu klasy JSONDictionaryExtnExample metoda
JSONDictionaryExtnExample.toJSON()
zwraca ciąg znaków będący znacznikiem. Ten ciąg znaków identyfikuje dane wyjściowe JSON jako należące do klasy JSONDictionaryExtnExample. Ten znacznik jednoznacznie określa, jaki typ obiektu jest przetwarzany w metodzie
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;
}
}
}
Gdy następujący skrypt środowiska wykonawczego wywołuje metodę
JSON.parse()
dla obiektu JSONDictionaryExtnExample, funkcja
reviver
wywołuje metodę
JSONGenericDictExample.revive()
dla każdego obiektu JSONDictionaryExtnExample. To wywołanie pobiera identyfikator reprezentujący klucz obiektu. Funkcja
JSONGenericDictExample.revive()
za pomocą tego identyfikatora pobiera przechowywany obiekt JSONDictionaryExtnExample z prywatnej statycznej tablicy i zwraca go.
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]>]
|
|
|