使用本机 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() replacerJSON.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 对象的 timehours 值:

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