기본 JSON 기능 사용

ActionScript 3.0에서는 JSON(JavaScript Object Notation) 형식을 사용하여 ActionScript 객체를 인코딩 및 디코딩하기 위한 기본 API를 제공합니다. 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() 메서드를 대체할 수도 있고, 해당 프로토타입을 다시 정의할 수도 있습니다. final로 선언되는 Date 및 XML 클래스를 사용하려면 클래스 프로토타입을 활용하여 toJSON() 을 다시 정의해야 합니다. Dictionary 클래스는 dynamic으로 선언되므로 보다 자유롭게 toJSON() 을 대체하여 사용할 수 있습니다.

사용자 정의 JSON 비헤이비어 정의

기본 클래스에 대한 JSON 인코딩 및 디코딩을 직접 구현하기 위해 다음과 같은 여러 가지 옵션 중에서 선택할 수 있습니다.

  • final이 아닌 기본 클래스의 사용자 정의 하위 클래스에서 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() 을 다시 정의할 필요가 없는 경우도 있습니다. 또한 부모 클래스가 final로 표시되지 않은 경우 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"

클래스가 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.parse() 호출에서 문자열이 유지되는 유효한 JSON 문자열 객체를 수신하게 됩니다.

다음 코드에서는 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 클래스는 사용자 정의 사전 클래스입니다. 각 레코드에는 개인의 이름과 생일은 물론 고유한 ID까지 포함되어 있습니다. 생성자 JSONGenericDictExample이 호출될 때마다 해당 ID가 정적으로 증가하는 정수인 내부 정적 배열에 새로 만들어진 객체가 추가됩니다. 또한 JSONGenericDictExample 클래스는 보다 긴 id 멤버에서 정수 부분만 추출하는 revive() 메서드를 정의합니다. 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]>]