ActionScript 3.0 fournit une API native permettant de coder et de décoder des objets ActionScript à l’aide du format JavaScript Object Notation (JSON). La classe JSON et les fonctions membres associées suivent la spécification ECMA-262 5e édition avec de légères variations.
Le membre de la communauté Todd Anderson fournit une comparaison entre l’API JSON native et la classe JSON as3corelib tierce. Voir
Working with Native JSON in Flash Player 11
(disponible en anglais uniquement).
Présentation de l’API JSON
L’API JSON ActionScript est constituée de la classe JSON et des fonctions membres
toJSON()
sur quelques classes natives. Pour les applications nécessitant un codage JSON personnalisé pour une classe, la structure ActionScript propose diverses méthodes permettant de remplacer le codage par défaut.
La classe JSON gère en interne l’importation et l’exportation des classes ActionScript qui ne fournissent pas de membre
toJSON()
. Dans ces cas, la classe JSON traverse les propriétés publiques de chaque objet qu’elle détecte. Si un objet contient d’autres objets, JSON parcourt les objets imbriqués et effectue la même traversée. Si un objet fournit une méthode
toJSON()
, JSON utilise cette méthode personnalisée plutôt que son algorithme interne.
L’interface JSON est composée d’une méthode d’encodage,
stringify()
, et d’une méthode de décodage,
parse()
. Chacune de ces méthodes fournit un paramètre permettant d’insérer votre propre logique dans la procédure de codage et de décodage JSON. Pour
stringify()
, ce paramètre est appelé
replacer
; pour
parse()
, il est appelé
reviver
. Ces paramètres prennent une définition de fonction avec deux arguments à l’aide de la signature suivante :
function(k, v):*
Méthodes toJSON()
La signature des méthodes
toJSON()
est
public function toJSON(k:String):*
JSON.stringify()
appelle la méthode
toJSON()
, si elle existe, pour chaque propriété publique qu’elle rencontre lorsqu’elle traverse un objet. Une propriété consiste en une paire clé-valeur. Lorsque
stringify(
) appelle la méthode
toJSON()
, il transmet la clé,
k
, de la propriété qu’il examine actuellement. Une implémentation
toJSON()
standard évalue chaque nom de propriété et renvoie l’encodage souhaité de sa valeur.
La méthode
toJSON()
peut renvoyer tout type de valeur (identifiée à l’aide du signe *) et pas uniquement une chaîne. Ce type de renvoi de variable permet à la méthode
toJSON()
de renvoyer un objet, le cas échéant. Par exemple, si une propriété de votre classe personnalisée contient un objet issu d’une autre bibliothèque tierce, vous pouvez renvoyer cet objet dès que la méthode
toJSON()
détecte votre propriété. JSON parcourt alors l’objet tiers. Le processus de codage est le suivant :
-
Si la méthode
toJSON()
renvoie un objet qui n’est pas évalué par rapport à une chaîne,
stringify()
parcourt cet objet.
-
Si la méthode
toJSON()
renvoie une chaîne,
stringify()
enveloppe la valeur dans une autre chaîne, renvoie la chaîne enveloppée et passe à la valeur suivante.
Dans de nombreux cas, il est préférable de renvoyer un objet plutôt que de renvoyer une chaîne JSON créée par votre application. Le renvoi d’un objet implique l’utilisation de l’algorithme de codage JSON et permet à JSON de se répéter dans les objets imbriqués.
La méthode
toJSON()
n’est pas définie dans la classe Object ou dans la plupart des autres classes natives. Son absence indique à JSON d’effectuer sa traversée standard sur les propriétés publiques de l’objet. Si vous préférez, vus pouvez également utiliser la méthode
toJSON()
pour exposer les propriétés privées de votre objet.
Certaines classes natives posent néanmoins des problèmes que les bibliothèques ActionScript sont incapables de résoudre efficacement dans tous les cas d’utilisation. Pour ces classes, ActionScript fournit une implémentation triviale que le client peut à nouveau implémenter selon ses besoins. Les classes qui fournissent des membres
toJSON()
triviaux sont les suivantes :
-
ByteArray
-
Date
-
Dictionary
-
XML
Vous pouvez intégrer la classe ByteArray dans une sous-classe pour remplacer sa méthode
toJSON()
, mais vous pouvez aussi redéfinir son prototype. Les classes Date et XML, qui sont déclarées comme étant finales, vous obligent à utiliser le prototype de classe pour redéfinir
toJSON()
. La classe Dictionary est déclarée comme étant dynamique, ce qui vous donne la liberté de remplacer la méthode
toJSON()
.
Définition du comportement JSON personnalisé
Vous disposez de plusieurs options pour implémenter vos propres codage et décodage JSON pour les classes natives :
-
Définir ou remplacer
toJSON()
sur la sous-classe personnalisée d’une classe native non finale
-
Définir et redéfinir
toJSON()
sur le prototype de la classe
-
Définir une propriété
toJSON
sur une classe dynamique
-
Utiliser les paramètres
JSON.stringify() replacer
et
JSON.parser() reviver
Définition de la méthode toJSON() sur le prototype d’une classe intégrée
L’implémentation JSON native dans ActionScript imite le mécanisme JSON ECMAScript défini dans ECMA-262, 5e édition. Etant donné qu’ECMAScript ne prend pas en charge les classes, ActionScript définit le comportement de JSON en termes de distribution basée sur les prototypes. Ancêtres des classes ActionScript 3.0, les prototypes permettent un héritage simulé, ainsi que les ajouts et redéfinitions de membres.
ActionScript permet de définir ou de redéfinir
toJSON()
sur le prototype d’une classe. Ce privilège s’étend aux classes déclarées comme étant finales. Lorsque vous définissez
toJSON()
sur le prototype d’une classe, votre définition s’applique à toutes les occurrences de cette classe dans le cadre de votre application. Par exemple, voici comment vous pouvez définir une méthode
toJSON()
sur le prototype de la classe MovieClip :
MovieClip.prototype.toJSON = function(k):* {
trace("prototype.toJSON() called.");
return "toJSON";
}
Lorsque votre application appelle la méthode
stringify()
sur une occurrence de MovieClip,
stringify()
renvoie le résultat de votre méthode
toJSON()
:
var mc:MovieClip = new MovieClip();
var js:String = JSON.stringify(mc); //"prototype toJSON() called."
trace("js: " + js); //"js: toJSON"
Vous pouvez en outre remplacer
toJSON()
dans les classes natives qui définissent cette méthode. Par exemple, le code suivant remplace
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"
Définition ou remplacement de toJSON() au niveau de la classe
Les applications n’ont pas toujours besoin d’utiliser des prototypes pour redéfinir
toJSON()
. Il est également possible de définir
toJSON()
en tant que membre d’une sous-classe si la classe parente n’est pas marquée comme finale. Vous pouvez par exemple étendre la classe ByteArray et définir une fonction
toJSON()
publique :
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"
Si un classe est dynamique, il est possible d’ajouter une propriété
toJSON
à un objet de cette classe et de lui attribuer une fonction de la façon suivante :
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."}
Vous pouvez remplacer, définir ou redéfinir
toJSON()
sur n’importe quelle classe ActionScript. Néanmoins, la plupart des classes ActionScript intégrées ne définissent pas
toJSON()
. La classe Object ne définit pas la méthode
toJSON
dans son prototype par défaut ni ne la déclare en tant que membre de classe. Seule une poignée de classes natives définit la méthode comme fonction prototype. C’est pourquoi, dans la plupart des cas, vous ne pouvez pas remplacer
toJSON()
de façon traditionnelle.
Les classes natives qui ne définissent pas
toJSON()
sont sérialisées sur JSON par l’implémentation JSON interne. Dans la mesure du possible, évitez de remplacer cette fonctionnalité intégrée. Si vous définissez un membre
toJSON()
, la classe JSON utilise votre logique plutôt que sa propre fonctionnalité.
Utilisation du paramètre replacer de la méthode JSON.stringify()
Il peut être utile de remplacer
toJSON()
sur le prototype en vue de modifier le comportement d’exportation JSON d’une classe dans une application. Néanmoins, votre logique d’exportation doit s’appliquer uniquement à des cas spéciaux sous des conditions provisoires. Pour prendre en compte ces modifications à petite échelle, vous pouvez utiliser le paramètre
replacer
de la méthode
JSON.stringify()
.
La méthode
stringify()
applique la fonction transmise via le paramètre
replacer
à l’objet en cours de codage. La signature pour cette fonction est similaire à celle de
toJSON()
:
function (k,v):*
Contrairement à la méthode
toJSON()
, la fonction
replacer
requiert la valeur
v
, ainsi que la clé
k
. Cette différence est nécessaire, car la méthode
stringify()
est définie sur l’objet JSON statique et non sur l’objet en cours de codage. Lorsque la méthode
JSON.stringify()
appelle
replacer(k,v)
, elle traverse l’objet d’entrée d’origine. Le paramètre implicite
this
transmis à la fonction
replacer
fait référence à l’objet qui détient la clé et la valeur. Etant donné que la méthode
JSON.stringify()
ne modifie pas l’objet d’entrée d’origine, cet objet reste inchangé dans le conteneur actuellement traversé. Vous pouvez par conséquent utiliser le code
this[k]
pour interroger la clé sur l’objet d’origine. Le paramètre
v
renferme la valeur que
toJSON()
convertit.
Tout comme
toJSON()
, la fonction
replacer
peut renvoyer tout type de valeur. Si
replacer
renvoie une chaîne, le moteur JSON convertit le contenu en séquence d’échappement entre guillemets et place ce contenu également entre guillemets. Cette structure garantit que
stringify()
reçoive un objet de chaîne JSON valide qui reste une chaîne dans un prochain appel de
JSON.parse()
.
Le code suivant utilise le paramètre
replacer
et le paramètre
this
implicite pour renvoyer les valeurs
time
et
hours
d’un objet 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;
});
Utilisation du paramètre reviver de la méthode JSON.parse()
Le paramètre
reviver
de la méthode
JSON.parse()
est l’opposé de la fonction
replacer
: il convertit une chaîne JSON en objet ActionScript utilisable. L’argument
reviver
est une fonction qui prend deux paramètres et renvoie tout type :
function (k,v):*
Dans cette fonction,
k
est une clé et
v
est la valeur de
k
. Tout comme
stringify()
,
parse()
traverse les paires clé-valeur JSON et applique la fonction
reviver
, si elle existe, à chaque paire. L’un des problèmes potentiels est que la classe JSON ne renvoie pas le nom de classe ActionScript d’un objet. Il peut par conséquent être difficile de savoir quel type d’objet ranimer. Ce problème peut en outre s’avérer particulièrement délicat lorsque les objets sont imbriqués. En désignant les fonctions
toJSON()
,
replacer
et
reviver
, vous pouvez trouver des moyens d’identifier les objets ActionScript exportés tout en gardant intacts les objets d’origine.
Exemple d’analyse
L’exemple suivant illustre une stratégie de ranimation d’objets analysés à partir de chaînes JSON. Cet exemple définit deux classes : JSONGenericDictExample et JSONDictionaryExtnExample. La classe JSONGenericDictExample est une classe Dictionary personnalisée. Chaque enregistrement contient le nom et la date de naissance d’une personne, ainsi qu’un ID unique. Chaque fois que le constructeur JSONGenericDictExample est appelé, il ajoute l’objet nouvellement créé à un tableau statique interne avec un entier augmentant statiquement comme son ID. La classe JSONGenericDictExample définit également une méthode
revive()
qui extrait uniquement l’entier du membre
id
le plus long. La méthode
revive()
utilise cet entier pour rechercher et renvoyer l’objet ranimable adéquat.
La classe JSONDictionaryExtnExample étend la classe Dictionary ActionScript. Ses enregistrements n’ont pas de structure définie et peuvent contenir toutes sortes de données. Les données sont attribuées après la construction de l’objet JSONDictionaryExtnExample et non par les propriétés définies dans la classe. Les enregistrements de JSONDictionaryExtnExample utilisent les objets JSONGenericDictExample comme clés. Lorsqu’un objet JSONDictionaryExtnExample est ranimé, la fonction
JSONGenericDictExample.revive()
utilise l’ID associé à JSONDictionaryExtnExample pour récupérer l’objet de clé correct.
Plus important encore, la méthode
JSONDictionaryExtnExample.toJSON()
renvoie une chaîne de marqueurs en plus de l’objet JSONDictionaryExtnExample. Cette chaîne identifie la sortie JSON comme appartenant à la classe JSONDictionaryExtnExample. Ce marqueur indique clairement le type d’objet en cours de traitement lors de l’exécution de
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;
}
}
}
Lorsque le script d’exécution suivant appelle
JSON.parse()
sur un objet JSONDictionaryExtnExample, la fonction
reviver
appelle
JSONGenericDictExample.revive()
sur chaque objet dans JSONDictionaryExtnExample. Cet appel extrait l’ID qui représente la clé de l’objet. La fonction
JSONGenericDictExample.revive()
utilise cet ID pour récupérer et renvoyer l’objet JSONDictionaryExtnExample stocké à partir d’un tableau statique privé.
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]>]
|
|
|