Uso da funcionalidade JSON nativa

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.

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