使用本机 JSON 功能

ActionScript 3.0 提供了一个本机 API,用于采用 JavaScript Object Notation (JSON) 格式对 ActionScript 对象进行编码和解码。JSON 类和支持成员函数遵循 ECMA-262 第 5 版规范,并稍有改动。

JSON API 概述

ActionScript JSON API 由 JSON 类和几个本机类上的 toJSON() 成员函数组成。对于要求任何类提供自定义 JSON 编码的应用程序,ActionScript 框架将提供覆盖默认编码的方法。

JSON 类内部处理任何不提供 toJSON() 成员的 ActionScript 类的导入和导出。对于此类情况,JSON 将遍历所遇到的每一个对象的公共属性。如果一个对象包含其他对象,JSON 将以递归形式访问嵌套对象,并执行相同的遍历。如果任何对象提供 toJSON() 方法,JSON 将使用该自定义方法,而不是其内部算法。

JSON 接口包含一个编码方法 stringify() 和一个解码方法 parse() 。其中每种方法都提供一个参数,您可以将自己的逻辑插入 JSON 编码和解码工作流程。对于 stringify() ,此参数名为 replacer ;对于 parse() ,此参数为 reviver 。这些参数使用以下签名通过两个参数来定义函数:

function(k, v):*

toJSON() 方法

toJSON() 方法的签名为

public function toJSON(k:String):*

JSON.stringify() 在遍历对象期间,对遇到的每个公共属性调用 toJSON() (如果存在)。该属性由一个密钥-值对组成。当 stringify( ) 调用 toJSON() 时,它将传入当前检查的属性的密钥 ( k )。典型的 toJSON() 实现计算每个属性名称,并采用所需编码返回其值。

toJSON() 方法可以返回任何类型的值(用 * 表示),而不仅仅是字符串。此变量返回类型允许 toJSON() 返回一个对象(如果适用)。例如,如果您的自定义类的某个属性包含来自其他第三方库中的对象,当 toJSON() 遇到您的属性时,您可以返回该对象。随后,JSON 以递归形式访问第三方对象。编码处理流的行为如下:

  • 如果 toJSON() 返回一个计算结果不是字符串的对象,则 stringify() 将以递归形式访问该对象。

  • 如果 toJSON() 返回一个字符串,则 stringify() 会将此值包装到另一个字符串中,返回包装后的字符串,然后移到下一个值。

在许多情况下,返回对象操作会优先返回您的应用程序所创建的 JSON 字符串。返回对象操作将充分利用内置 JSON 编码算法,并且还允许 JSON 以递归形式访问嵌套对象。

toJSON() 方法不是在 Object 类中定义,也不是在大多数其他本地类中定义。如果缺少此方法,则 JSON 必须对对象的公共属性执行标准遍历。您也可以根据需要使用 toJSON() 来公开对象的私有属性。

不过,有些本地类可能会出现一些问题,因为 ActionScript 库不能有效地解决所有使用案例。对于这些类,ActionScript 提供一个简单实现,客户端可以重新实现以满足其需求。提供普通 toJSON() 成员的类包括:

  • ByteArray

  • Date

  • Dictionary

  • XML

您可以使 ByteArray 类子类化以覆盖其 toJSON() 方法,或者您也可以重新定义其原型。最后声明的 Date 和 XML 类要求使用类原型来重新定义 toJSON() 。Dictionary 类声明为动态类,您可以使用该类随时覆盖 toJSON()

定义自定义 JSON 行为

如果要对本地类实施您自己的 JSON 编码和解码,则可以选择以下几个选项:

  • 定义或覆盖非最终本地类的自定义子类上的 toJSON()

  • 定义或重新定义类原型上的 toJSON()

  • 定义动态类上的 toJSON 属性

  • 使用 JSON.stringify() replacer JSON.parser() reviver 参数

在内置类的原型上定义 toJSON()

ActionScript 中的本机 JSON 实现镜像 ECMA-262 第 5 版中定义的 ECMAScript JSON 机制。由于 ECMAScript 不支持类,因此 ActionScript 基于原型的调度定义 JSON 行为。原型是 ActionScript 3.0 类的前体,允许模拟继承以及成员添加和重新定义。

ActionScript 允许您在任何类的原型上定义或重新定义 toJSON() 。这种权限也适用于标记为 final 的类。在类原型上定义 toJSON() 时,您的定义将成为应用程序范围内该类的所有实例的最新定义。例如,下面介绍如何在 MovieClip 原型上定义 toJSON() 方法:

MovieClip.prototype.toJSON = function(k):* { 
    trace("prototype.toJSON() called."); 
    return "toJSON"; 
} 

当您的应用程序随后在任何 MovieClip 实例上调用 stringify() 时, stringify() 将返回 toJSON() 方法的输出:

var mc:MovieClip = new MovieClip(); 
var js:String = JSON.stringify(mc); //"prototype toJSON() called." 
trace("js: " + js); //"js: toJSON"

您还可以在定义该方法的本地类中覆盖 toJSON() 。例如,以下代码覆盖 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" 

定义或覆盖类级别的 toJSON()

并不总是需要应用程序使用原型重新定义 toJSON() 。如果父类不是最终类,您也可以将 toJSON() 定义为子类的成员。例如,您可以扩展 ByteArray 类并定义公用 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"

如果类是动态类,您可以将 toJSON 属性添加到该类的对象中,并为其分配一个函数,如下所示:

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."}

您可以在任何 ActionScript 类上覆盖、定义或重新定义 toJSON() 。但是,大多数内置 ActionScript 类不会定义 toJSON() 。Object 类不会以默认的原型定义 toJSON ,也不会将其声明为类成员。只有其他少数本地类将该方法定义为原型函数。因此,在大多数类中,您不能以传统方式覆盖 toJSON()

不定义 toJSON 的本地类将通过内部 JSON 实施序列化为 JSON。请尽可能避免替换此内置功能。如果您定义 toJSON() 成员,则 JSON 类将使用您的逻辑而不是其自己的函数。

使用 JSON.stringify() replacer 参数

如果需要在整个应用程序中更改类的 JSON 导出行为,则可以选择覆盖原型的 toJSON() 。但是,在某些情况下,导出逻辑可能只适用于瞬态条件下的特殊情况。要适应此小范围的更改,您可以使用 JSON.stringify() 方法的 replacer 参数。

stringify() 方法应用从 replacer 参数传递到要编码的对象的函数。此函数的签名与 toJSON() 的签名类似:

function (k,v):* 

toJSON() 不同, replacer 函数需要值 v 和密钥 k 。此差异是必要的,因为 stringify() 是在静态 JSON 对象而不是要编码的对象上定义的。当 JSON.stringify() 调用 replacer(k,v) 时,它将遍历原始输入对象。传递到 replacer 函数的隐式 this 参数表示保存密钥和值的对象。由于 JSON.stringify() 不修改原始输入对象,因此该对象在要遍历的容器中保持不变。因此,您可以使用代码 this[k] 查询原始对象上的密钥。 v 参数保存 toJSON() 转换的值。

toJSON() 相同, replacer 函数可以返回任何类型的值。如果 replacer 返回字符串,则 JSON 引擎将对引号中的内容进行转义,然后使用引号包装转义内容。此包装可确保 stringify() 接收到有效的 JSON 字符串对象,该对象在对 JSON.parse() 的后续调用中仍然为一个字符串。

以下代码使用 replacer 参数和隐式 this 参数返回 Date 对象的 time hours 值:

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; 
});

使用 JSON.parse() reviver 参数

JSON.parse() 方法的 reviver 参数与 replacer 函数相反:它将 JSON 字符串转换为可使用的 ActionScript 对象。 reviver 参数是一个具有两个参数并返回任何类型的函数:

function (k,v):* 

在此函数中, k 是一个密钥, v k 的值。与 stringify() 一样, parse() 遍历 JSON 密钥-值对,并将 reviver 函数(如果存在)应用于每个对。一个潜在的问题是:事实上,JSON 类不输出对象的 ActionScript 类名称。因此,了解要恢复的对象类型具有一定的难度。如果是嵌套对象,此问题将特别麻烦。在设计 toJSON() replacer reviver 函数时,您可以想办法在保持原对象完整性的同时识别导出的 ActionScript 对象。

解析示例

下面的示例显示了恢复从 JSON 字符串解析的对象的策略。此示例定义 JSONGenericDictExample 和 JSONDictionaryExtnExample 两个类。JSONGenericDictExample 类是一个自定义 Dictionary 类。每个记录都包含一个人的姓名、生日以及唯一 ID。每次调用 JSONGenericDictExample 构造函数时,均会将新创建的对象添加到内部静态数组,并使用一个静态递增的整数作为其 ID。JSONGenericDictExample 类还定义了 revive() 方法,该方法只从较长的 id 成员提取整数部分。 revive() 方法使用此整数来查找并返回正确的可恢复对象。

JSONDictionaryExtnExample 类扩展了 ActionScript Dictionary 类。其记录没有固定的结构,可以包含任何数据。在构建 JSONDictionaryExtnExample 对象之后即为其指定数据,而不是作为类定义的属性来指定。JSONDictionaryExtnExample 记录将 JSONGenericDictExample 对象用作密钥。JSONDictionaryExtnExample 对象恢复时, JSONGenericDictExample.revive() 函数使用与 JSONDictionaryExtnExample 关联的 ID 来检索正确的密钥对象。

最重要的是, JSONDictionaryExtnExample.toJSON() 方法将返回包含 JSONDictionaryExtnExample 对象在内的标记字符串。此字符串将 JSON 输出标识为属于 JSONDictionaryExtnExample 类。通过此标记,我们可以清楚地了解 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; 
        } 
    } 
} 

下列运行时脚本调用 JSONDictionaryExtnExample 对象上的 JSON.parse() 时, reviver 函数将调用 JSONDictionaryExtnExample 中每个对象上的 JSONGenericDictExample revive() 。此调用将提取表示对象密钥的 ID。 JSONGenericDictExample.revive() 函数使用此 ID 从私有静态数组中检索并返回存储的 JSONDictionaryExtnExample 对象。

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