O ActionScript 3.0 fornece uma API nativa para objetos de codificação e decodificação dos objetos ActionScript que usam o formato JavaScript Object Notation (JSON). A classe JSON e as funções de suporte ao membro seguem a especificação da 5ª edição da ECMA-262 com algumas variações.
O membro da comunidade Todd Anderson fornece uma comparação entre a API JSON nativa e a classe as3corelib JSON de terceiros. Consulte
Trabalhando com o JSON Nativo no Flash Player 11
.
Visão geral da API JSON
A API JSON do ActionScript consiste na classe JSON e nas funções de membro
toJSON()
em algumas classes nativas. Para os aplicativos que exigem uma codificação JSON personalizada para qualquer classe, a estrutura do ActionScrip fornece maneiras de sobrescrever a codificação padrão.
A classe JSON manipula internamente a importação e a exportação de qualquer classe do ActionScript que não forneça um membro
toJSON()
. Para esses casos, o JSON cruza as propriedades públicas de cada objeto que encontra. Caso um objeto contenha outros objetos, a JSON usará a função recursiva nos objetos aninhados e realizará o mesmo cruzamento. Caso qualquer objeto forneça um método
toJSON()
, a JSON usará aquele método customizado em vez de seu algoritmo interno.
A interface JSON consiste em um método de codificação,
stringify()
, e em um método de decodificação,
parse()
. Cada um desses métodos fornece um parâmetro que permite a inserção de sua própria lógica no fluxo de trabalho de codificação e decodificação do JSON. Para
stringify()
, esse parâmetro é denominado
replacer
; para
parse()
, o parâmetro é
reviver
. Esses parâmetros assumem a definição da função com dois argumentos que usam a seguinte assinatura:
function(k, v):*
métodos toJSON()
A assinatura para métodos
toJSON()
é
public function toJSON(k:String):*
JSON.stringify()
chamará
toJSON()
se ele existir, em cada propriedade pública que encontrar durante o cruzamento de um objeto. Uma propriedade consiste em um par de chave e valor. Quando
stringify(
) executar
toJSON()
, enviará a chave
k
da propriedade que está examinando no momento. Uma implementação típica
toJSON()
avalia cada nome de propriedade e retorna a decodificação desejada de seu valor.
O método
toJSON()
pode retornar um valor de qualquer tipo (denominado *)— não somente uma Sequência de caracteres. Esse tipo de retorno variável permite que
toJSON()
retorne um objeto, se for o caso. Por exemplo, caso uma propriedade de sua classe customizada contenha um objeto de outra biblioteca de terceiros, você poderá retornar aquele objeto quando
toJSON()
encontrar sua propriedade. A classe JSON então usará a função recursiva no objeto de terceiros. O fluxo do processo de codificação apresenta o seguinte comportamento:
-
Caso
toJSON()
retorne um objeto que não seja avaliado como uma string,
stringify()
usará a função recursiva naquele objeto.
-
Se
toJSON()
retornar uma string,
stringify()
envolve esse valor em outra string, retorna a string envolvida e, em seguida, move-se para o próximo valor.
Em muitos casos, retornar um objeto é preferível a retornar uma string JSON criada pelo seu aplicativo. Retornar um objeto aproveita o algoritmo de codificação JSON e também permite que JSON usar a função recursiva em objetos aninhados.
O método
toJSON()
não está definido na classe Object nem na maioria das outras classes nativas. Sua ausência indica a JSON que execute sua travessia padrão nas propriedades públicas do objeto. Se desejar, você também pode usar
toJSON()
para exibir as propriedades privadas do objeto.
Algumas classes nativas representam um desafio que as bibliotecas do ActionScript não conseguem resolver de maneira eficaz para todos os casos de uso. Para essas classes, o ActionScript fornece uma implementação comum que os clientes podem reimplementar para se adaptarem a suas necessidades. A classe que fornece membros
toJSON()
triviais incluem:
-
ByteArray
-
Date
-
Dictionary
-
XML
Você pode classificar em uma subclasse a classe ByteArray para sobrescrever o método
toJSON()
ou pode redefinir seu protótipo. As classes Date e XML, que são declaradas como finais, exigem a utilização de um protótipo de classe para redefinir
toJSON()
. A classe Dictionary é declarada dinamicamente, o que permite a liberdade adicional na substituição de
toJSON()
.
Definição do comportamento personalizado de JSON
Para implementar sua própria codificação e decodificação JSON para classes nativas, você pode optar por várias alternativas:
-
Definição ou substituição de
toJSON()
em sua subclasse personalizada de uma classe nativa que não seja final
-
Definição ou redefinição de
toJSON()
no protótipo da classe
-
Definição de uma propriedade de
toJSON
em uma classe dinâmica
-
Uso dos parâmetros
JSON.stringify() replacer
e
JSON.parser() reviver
Definição de toJSON() no protótipo de uma classe integrada
A implementação nativa de JSON no ActionScript reflete o mecanismo de JSON ECMAScript , definido em ECMA-262, 5ª edição. Uma vez que ECMAScript não suporta classes, o ActionScript define o comportamento de JSON em termos de despacho baseado em protótipo. Protótipos são precursores das classes do ActionScript 3.0 que permitem a herança simulada, bem como adições e redefinições de membros.
O ActionScript permite a definição ou redefinição de
toJSON()
no protótipo de qualquer classe. Esse privilégio é aplicado até para classes que são marcadas como finais. Ao definir
toJSON()
em um protótipo de classe, sua definição se torna atual para todas as instâncias daquela classe dentro do escopo de sua aplicação. Por exemplo, essa é uma maneira de definir um método
toJSON()
no protótipo MovieClip:
MovieClip.prototype.toJSON = function(k):* {
trace("prototype.toJSON() called.");
return "toJSON";
}
Quando, em seguida, o aplicativo chamar
stringify()
em qualquer instância do MovieClip, o
stringify()
retornará a saída de seu método
toJSON()
:
var mc:MovieClip = new MovieClip();
var js:String = JSON.stringify(mc); //"prototype toJSON() called."
trace("js: " + js); //"js: toJSON"
Também é possível substituir
toJSON()
nas classes nativas que definem o método. Por exemplo, o seguinte código substitui
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"
Definição ou substituição de toJSON() no nível de classe
Não é necessário que os aplicativos usem protótipos para redefinir
toJSON()
. Você também pode definir
toJSON()
como membro de uma subclasse se a classe pai não estiver marcada como final. Por exemplo, você pode estender a classe ByteArray e definir uma função
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"
Se a classe for dinâmica, você poderá adicionar uma propriedade
toJSON
a um objeto daquela classe e designar uma função para ela como segue:
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."}
É possível substituir, definir ou redefinir
toJSON()
em qualquer classe do ActionScript. No entanto, a maioria das classes incorporadas do ActionScript não definem
toJSON()
. A classe Object não define
toJSON
em seu protótipo padrão nem o declara como um membro da classe. Somente algumas poucas classes nativas definem o método como uma função de protótipo. Dessa forma, na maioria das classes não é possível substituir
toJSON()
da maneira tradicional.
As classes nativas que não definem
toJSON()
são serializadas em JSON pela implementação interna de JSON. Se possível, evite substituir essa funcionalidade incorporada. Se você definir um membro de
toJSON()
, a classe JSON usará sua lógica em vez de sua própria funcionalidade.
Uso do parâmetro replacer JSON.stringify()
A substituição de
toJSON()
no protótipo é útil nas alterações de comportamento de exportação de JSON de uma classe num aplicativo. Em alguns casos, sua lógica de exportação poderá ser aplicada somente para casos especiais sob condições temporárias. Para acomodar essas alterações de escopo tão pequeno, use o parâmetro
replacer
do método
JSON.stringify()
.
O método
stringify()
aplica a função transmitida ao parâmetro
replacer
para o objeto a ser decodificado. A assinatura para essa função é similar à de
toJSON()
:
function (k,v):*
Diferente de
toJSON()
, a função
replacer
exige o valor
v
e a chave,
k
. Essa diferença é necessária porque
stringify()
é definido no objeto JSON estático em vez de no objeto a ser decodificado. Quando
JSON.stringify()
chamar
replacer(k,v)
, ele estará cruzando o objeto de entrada original. O parâmetro implícito
this
enviado à função
replacer
refere-se ao objeto que possui a chave e o valor. Uma vez que
JSON.stringify()
não altera o objeto de entrada original, aquele objeto permanece inalterado no container a ser cruzado. Dessa forma, é possível usar o código
this[k]
para consultar a chave no objeto original. O parâmetro
v
mantém o valor que
toJSON()
converteu.
Como
toJSON()
, a função
replacer
pode retornar qualquer tipo de valor. Se
replacer
retornar uma sequência de caracteres, o mecanismo JSON escapará os conteúdos em citações e, em seguida, agrupará também os conteúdos de escape em citações. Esse agrupamento garante que
stringify()
receba um objeto de sequência de caracteres JSON válido que permaneça como uma sequência de caracteres em uma chamada subsequente para
JSON.parse()
.
O seguinte código usa o parâmetro
replacer
e o parâmetro implícito
this
para retornar os valores de
time
e
hours
de um 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;
});
Utilização do parâmetro reviver JSON.parse()
O parâmetro
reviver
do método
JSON.parse()
realiza o oposto da função
replacer
: ele converte uma sequência de caracteres JSON em um objeto do ActionScript utilizável. O argumento
reviver
é uma função que usa dois parâmetros e retorna qualquer tipo:
function (k,v):*
Nessa função,
k
é uma chave, e
v
é o valor de
k
. Como
stringify()
, o
parse()
cruza os pares de valor-chave de JSON e aplica a função
reviver
- caso ela exista - a cada par. Um problema em potencial é o fato de a classe JSON não fornecer a saída de um nome de classe do ActionScript de um objeto. Assim, pode ser difícil saber que tipo de objeto deve ser revivido. Esse problema pode ser especialmente grave quando os objetos são aninhados. Ao designar as funções
toJSON()
,
replacer
e
reviver
, você pode planejar maneiras de identificar os objetos do ActionScript que são exportados, enquanto mantém os objetos originais intactos.
Exemplo de análise
O seguinte exemplo mostra uma estratégia para reviver objetos analisados de sequências de caracteres do JSON. Este exemplo define duas classes: JSONGenericDictExample e JSONDictionaryExtnExample. A classe JSONGenericDictExample é uma classe de dicionário personalizado. Cada registro contém o nome e a data de aniversário de uma pessoa, assim como uma ID exclusiva. Cada vez que o construtor JSONGenericDictExample é chamado, ele adiciona o objeto recém-criado a uma matriz estática interna com um número inteiro com incremento estático como sua ID. A classe JSONGenericDictExample também define um método
revive()
que extrai somente a parte correspondente ao número inteiro do membro de
id
mais longo. O método
revive()
usa esse número inteiro para buscar e retornar o objeto restaurável correto.
A classe JSONDictionaryExtnExample estende a classe Dictionary do ActionScript. Seus registros não têm uma estrutura definida e podem conter quaisquer dados. Os dados são atribuídos depois que o objeto JSONDictionaryExtnExample é construído, e não como propriedades definidas pela classe. Registros JSONDictionaryExtnExample usam objetos JSONGenericDictExample como chaves. Quando um objeto JSONDictionaryExtnExample é revivido, a função
JSONGenericDictExample.revive()
usa a ID associada a JSONDictionaryExtnExample para recuperar o objeto de chave correto.
O mais importante é que o método
JSONDictionaryExtnExample.toJSON()
retorna uma string de marcador além do objeto JSONDictionaryExtnExample. Essa string identifica a saída de JSON como pertencente à classe JSONDictionaryExtnExample. Esse marcador não deixa dúvidas quanto ao tipo de objeto que está sendo processado 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;
}
}
}
Quando o script de tempo de execução a seguir chama
JSON.parse()
em um objeto JSONDictionaryExtnExample, a função
reviver
chama
JSONGenericDictExample.revive()
em cada objeto em JSONDictionaryExtnExample. Essa chamada extrai a ID que representa a chave do objeto. A função
JSONGenericDictExample.revive()
usa essa ID para recuperar e retornar o objeto JSONDictionaryExtnExample armazenado em uma matriz estática privada.
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]>]
|
|
|