ActionScript 3.0 proporciona una API nativa para codificar y descodificar objetos de ActionScript con formato JavaScript Object Notation (JSON). La clase JSON y las funciones de miembros admitidas siguen la especificación de la quinta edición del ECMA-262 con algunas variaciones.
Todd Anderson es un usuario de la comunidad y nos ofrece una comparación entre la API JSON nativa y la clase JSON as3corelib de otro fabricante. Consulte
Working with Native JSON in Flash Player 11
(en inglés).
Descripción de la API JSON
La API JSON de ActionScript consta de la clase JSON y de las funciones de miembros
toJSON()
en varias clases nativas. En el caso de aplicación que requieren una codificación JSON personalizada para alguna clase, la estructura de ActionScript proporciona alternativas para omitir la codificación predeterminada.
La clase JSON gestiona internamente la importación y la exportación de cualquier clase de ActionScript que no proporcione un miembro de
toJSON()
. En esos casos, JSON recorre las propiedades públicas de cada objeto que encuentra. Si un objeto contiene otros objetos, JSON lo recorre repetidamente los objetos anidados y hace su trabajo. Si algún objeto proporciona un método
toJSON()
, JSON utiliza dicho método personalizado en lugar de su algoritmo interno.
La interfaz JSON está formada por un método de codificación,
stringify()
, y uno de descodificación,
parse()
. Cada uno de ellos proporciona un parámetro que permite insertar su propia lógica en el flujo de trabajo de codificación y descodificación JSON. Para
stringify()
, este parámetro se llama
replacer
; para
parse()
, se llama
reviver
. Estos parámetros toman una definición de función con dos argumentos y para ello utilizan la siguiente firma:
function(k, v):*
Métodos toJSON()
La firma de los métodos
toJSON()
es
public function toJSON(k:String):*
JSON.stringify()
llama a
toJSON()
, si existe, en cada propiedad pública que encuentra durante su recorrido por el objeto. Una propiedad está formada por una pareja clave-valor. Si
stringify(
) llama a
toJSON()
, transfiere la clave
k
de la propiedad que se está analizando. Una implementación típica de
toJSON()
calcula cada nombre y devuelve la codificación deseada de su valor.
El método
toJSON()
puede devolver un valor de cualquier tipo (indicado con *), no solo de tipo String. Este tipo devuelto variable permite que
toJSON()
devuelva un objeto, si procede. Por ejemplo, si una propiedad de la clase personalizada contiene un objeto de la biblioteca de un tercero, puede devolver dicho objeto cuando
toJSON()
encuentre su propiedad. Entonces JSON lo recorrerá repetidas veces el objeto del tercero. El flujo del proceso de codificación se comporta del modo siguiente:
-
Si
toJSON()
devuelve un objeto que no da como resultado una cadena,
stringify()
recorre repetidas veces dicho objeto.
-
Si
toJSON()
devuelve una cadena,
stringify()
resume dicho valor en otra cadena, devuelve la cadena resumida y, después, pasa al siguiente valor.
En muchos casos, es preferible devolver un objeto a devolver una cadena JSON creada por la aplicación. Devolver un objeto aprovecha el algoritmo de codificación JSON integrado y también permite que JSON pueda ejecutarse repetidas veces en objetos anidados.
El método
toJSON()
no se define en la clase Object ni en la mayor parte de clases nativas. Su ausencia hace que JSON lleve a cabo su recorrido habitual por las propiedades públicas del objeto. Si lo desea, también puede utilizar
toJSON()
para mostrar las propiedades privadas del objeto.
Algunas clases representan retos que las bibliotecas de ActionScript no son capaces de resolver de forma eficaz en todos los casos. Para estas clases, ActionScript proporciona una implementación trivial que los clientes pueden volver a implementar para ajustarla a sus necesidades. Las clases que proporcionan miembros de
toJSON()
triviales son:
-
ByteArray
-
Date
-
Dictionary
-
XML
Puede crear una subclase de la clase ByteArray para anular este método
toJSON()
, o puede redefinir su prototipo. Las clases Date y XML, que se declaran finales, requieren el uso del prototipo de la clase para poder redefinir
toJSON()
. La clase Dictionary se declara dinámica, y se obtiene así libertad adicional a la hora de anular
toJSON()
.
Definición de comportamiento JSON personalizado
Para implementar su propia codificación y descodificación JSON para clases nativas, puede elegir una de las siguientes opciones:
-
Definir o anular
toJSON()
en la subclase personalizada de una clase nativa no final
-
Definir o redefinir
toJSON()
en el prototipo de la clase
-
Definir una propiedad
toJSON
en una clase dinámica
-
Utilizar los parámetros
JSON.stringify() replacer
y
JSON.parser() reviver
Definición de toJSON() en el prototipo de una clase integrada
La implementación nativa de JSON en ActionScript imita el mecanismo JSON de ECMAScript definido en ECMA-262, quinta edición. Dado que ECMAScript no admite clases, ActionScript define el comportamiento de JSON en términos de distribución basada en prototipos. Los prototipos son los precursores de las clases de ActionScript 3.0 y permiten herencia simulada, así como incorporaciones y nuevas definiciones de miembros.
ActionScript permite definir o redefinir
toJSON()
en el prototipo de cualquier clase. Este privilegio se aplica incluso a clases marcadas como finales. Cuando se define
toJSON()
en un prototipo de clase, la definición se aplica a todas las instancias de dicha clase dentro del ámbito de la aplicación. Por ejemplo, así puede definir un método
toJSON()
en el prototipo MovieClip:
MovieClip.prototype.toJSON = function(k):* {
trace("prototype.toJSON() called.");
return "toJSON";
}
Cuando la aplicación llame en ese momento a
stringify()
en cualquier instancia de MovieClip,
stringify()
devuelve la salida del método
toJSON()
:
var mc:MovieClip = new MovieClip();
var js:String = JSON.stringify(mc); //"prototype toJSON() called."
trace("js: " + js); //"js: toJSON"
También puede anular
toJSON()
en clases nativas que definan el método. Por ejemplo, el siguiente código anula
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"
Definición o anulación de toJSON() en el nivel de clase
No siempre se necesitan aplicaciones para usar prototipos para redefinir
toJSON()
. También se puede definir
toJSON()
como miembro de una subclase si la clase principal no está marcada como final. Por ejemplo, puede ampliar la clase ByteArray y definir una función
toJSON()
pública:
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 una clase es dinámica, puede añadir una propiedad
toJSON
a un objeto de dicha clase y asignarle una función del modo siguiente:
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."}
Puede anular, definir o redefinir
toJSON()
en cualquier clase de ActionScript. No obstante, la mayoría de las clases de ActionScript integradas no definen
toJSON()
. Sin embargo, la clase Object no define el método
toJSON
en su prototipo predeterminado ni lo declara como un miembro de clase. Solo unas pocas clases nativas definen el método como una función prototipo. Así, en la mayoría de los casos no es posible anular
toJSON()
en el sentido tradicional.
Las clases nativas que no definen
toJSON()
se serializan en JSON con una implementación interna de JSON. Evite sustituir esta funcionalidad integrada si es posible. Si define un miembro de
toJSON()
, la clase JSON usará su lógica en vez de su propia funcionalidad.
Uso del parámetro replacer de JSON.stringify()
Anular
toJSON()
en el prototipo resulta útil para cambiar el comportamiento de exportación JSON de una clase en toda la aplicación. En algunos casos, no obstante, la lógica de exportación es posible que se aplique solamente a casos especiales en condiciones transitorias. Para poder aceptar cambios con alcance tan limitado, puede utilizar el parámetro
replacer
del método
JSON.stringify()
.
El método
stringify()
aplica la función transferida a través del parámetro
replacer
al objeto que se está codificando. La firma de esta función es similar a la de
toJSON()
:
function (k,v):*
Al contrario que
toJSON()
, la función
replacer
requiere el valor
v
así como la clave
k
. Esta diferencia es necesaria, ya que
stringify()
está definido en el objeto estático JSON y no en el objeto que se está codificando. Cuando
JSON.stringify()
llama a
replacer(k,v)
, su recorre el objeto de entrada original. El parámetro
this
implícito transferido a la función
replacer
hace referencia al objeto que contiene la clave y el valor. Como
JSON.stringify()
no modifica el objeto de entrada original, dicho objeto permanece intacto en el contenedor que se está recorriendo. De este modo, es posible utilizar el código
this[k]
para consultar la clave en el objeto original. El parámetro
v
aloja el valor que convierte
toJSON()
.
Al igual que
toJSON()
, la función
replacer
puede devolver cualquier tipo de valor. Si
replacer
devuelve una cadena, el motor JSON bloquea el contenido entre comillas y envuelve el contenido bloqueado entre comillas también. Esta envoltura garantiza que
stringify()
recibe un objeto de cadena JSON válido que seguirá siendo una cadena en la siguiente llamada a
JSON.parse()
.
El siguiente código utiliza el parámetro
replacer
y el parámetro implícito
this
para devolver los valores de
time
y
hours
de un objeto 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;
});
Uso del parámetro reviver de JSON.parse()
El parámetro
reviver
del método
JSON.parse()
hace lo contrario que la función
replacer
: convierte a una cadena JSON en un objeto de ActionScript utilizable. El argumento
reviver
es una función que toma dos parámetros y devuelve cualquier tipo:
function (k,v):*
En esta función,
k
es una clave y
v
es el valor de
k
. Del mismo modo que
stringify()
,
parse()
recorre los pares de clave/valor de JSON y aplica la función
reviver
a cada par (si existe). Un problema potencial es el hecho de que la clase de JSON no produce un nombre de clase de ActionScript de un objeto como salida. Por lo tanto, puede resultar complicado saber qué tipo de objeto se revive. Este problema es especialmente reseñable en objetos anidados. Al diseñar funciones
toJSON()
,
replacer
y
reviver
puede buscar formas de identificar los objetos de ActionScript exportados sin tocar los objetos originales.
Ejemplos de análisis
El siguiente ejemplo muestra una estrategia para revivir objetos analizados desde cadenas JSON. Este ejemplo define dos clases: JSONGenericDictExample y JSONDictionaryExtnExample. La clase JSONGenericDictExample es una clase personalizada de diccionario. Cada registro contiene el nombre de una persona y su cumpleaños, así como un identificador exclusivo. Cada vez que se llama al constructor JSONGenericDictExample, se añade un objeto recién creado a un conjunto estático interno con un entero que incrementa como su identificador. La clase JSONGenericDictExample también define un método
revive()
que extrae la porción entera del miembro de
id
más largo. El método
revive()
utiliza este entero para buscar y devolver el objeto adecuado.
La clase JSONDictionaryExtnExample amplía la clase Dictionary de ActionScript. Sus registros no tienen ninguna estructura definida y pueden contener cualquier dato. Los datos se asignan después de construir un objeto JSONDictionaryExtnExample, no como propiedades definidas por la clase. Los registros de JSONDictionaryExtnExample utilizan objetos JSONGenericDictExample como claves. Cuando se revive un objeto JSONDictionaryExtnExample, la función
JSONGenericDictExample.revive()
utiliza el identificador asociado a JSONDictionaryExtnExample para recuperar el objeto de clave correcto.
Lo que es más importante, el método
JSONDictionaryExtnExample.toJSON()
devuelve una cadena de marcador además del objeto JSONDictionaryExtnExample. Esta cadena identifica la salida JSON como perteneciente a la clase JSONDictionaryExtnExample. Este marcador no deja ninguna duda sobre el tipo de objeto que se está procesando durante
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;
}
}
}
En tiempo de ejecución, cuando el siguiente script llama a
JSON.parse()
en un objeto JSONDictionaryExtnExample, la función
reviver
llama a
JSONGenericDictExample.revive()
en cada objeto de JSONDictionaryExtnExample. Esta llamada extrae el identificador que representa a la clave del objeto. La función
JSONGenericDictExample.revive()
utiliza este identificador para recuperar y devolver el objeto JSONDictionaryExtnExample almacenado desde un conjunto estático privado.
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]>]
|
|
|