使用原生 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 遞迴至巢狀物件。

在 Object 類別或大部分其它原生類別中,均未定義 toJSON() 方法。這告訴 JSON 要在物件的公用屬性上執行標準移動方式。如果有意願的話,您也可以使用 toJSON() 來公開物件的私有屬性。

有一些原生類別連 ActionScript 元件庫也無法有效解決所有的使用情況。對於這些類別,ActionScript 提供一種簡單的實作方式,用戶端能夠重複實作以符合其需求。提供簡單 toJSON() 成員的類別包括:

  • ByteArray

  • Date

  • Dictionary

  • XML

您可以利用 ByteArray 類別的子類別來覆寫 toJSON() 方法,或是重新定義它的原型。宣告為 final 的 Date 和 XML 類別會要求您使用類別原型來重新定義 toJSON() 。Dictionary 類別宣告為 dynamic,讓您有額外的自由來覆寫 toJSON()

定義自訂 JSON 行為

若要針對原生類別實作自己的 JSON 編碼和解碼,您可以從幾個選項選擇:

  • 定義或覆寫非 final 原生類別之自訂子類別中的 toJSON()

  • 定義或重新定義類別原型上的 toJSON()

  • 定義 dynamic 類別上的 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() 定義為子類別的成員 (如果父類別不是標記為 final 的話)。例如,您可以擴充 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"

如果類別是 dynamic,您可以依照下列方式將 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 參數

在原型上覆寫 toJSON() 適用於變更整個應用程式中類別的 JSON 匯出行為。不過,在某些情況中,您的匯出邏輯可能只會短暫套用到特殊案例。若要容納這類小範圍的變更,您可以使用 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 參數,傳回 Data 物件的 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 物件, JSONDictionaryExtnExample.toJSON() 方法還會傳回標記字串。這個字串會將 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]>]