# - simple json encoder/decoder for java

[MENU] >>>

## JSONICとは

JSONICは、Java用のシンプルかつ高機能なJSONエンコーダー/デコーダーライブラリです。
Java用のJSONライブラリはすでに多数存在しますが、JSONICはRFC 4627に従った正式なJSON形式でのデコード/エンコードを行いながらも、プログラミング言語に依存する情報をJSON内に含めることなくPOJO(Plain Old Java Object)と自然な変換を行える点に特徴があります。

使い方も非常に簡単です。

    import net.arnx.jsonic.JSON;

    // POJOをJSONに変換します
    String text = JSON.encode(new Hoge());

    // JSONをPOJOに変換します
    Hoge hoge = JSON.decode(text, Hoge.class);

## ダウンロード

ダウンロードはこちらからできます。なお、JSONICのビルド/実行には、Javaバージョン5.0以上が必要です。

## 基本的な使い方

通常の用途では、二つの静的メソッドencode/decodeだけ利用すれば事足ります。

### ■ encodeメソッド - POJOからJSONへの変換

POJOからJSONに変換する場合は、encodeを使います。デフォルトでは、空白などを含まない可読性の低いJSONが出力されますが、二番目の引数をtrueにすることで可読性の高いJSONが出力されるようになります（Pretty Printモード）。

    // 変換対象のPOJOを準備
    Hoge hoge = new Hoge();
    hoge.number = 10;      // public field
    hoge.setString("aaa"); // public property
    hoge.setArray(new int[] {1, 2, 3});

    // POJOをJSONに変換します。戻り値は {"number":10,"string":"aaa","array":[1,2,3]}となります
    String text = JSON.encode(hoge);

    // POJOを可読性の高いJSONに変換します。戻り値は次のような文字列になります
    // {
    //     "number": 10,
    //     "string": "aaa",
    //     "array": [1, 2, 3]
    // }
    String text = JSON.encode(hoge, true);

    // Appendable(StringBuffer, Writerなど)やOutputStreamを出力先にすることもできます(※1)
    JSON.encode(hoge, new FileWriter("hoge.txt"));
    JSON.encode(hoge, new FileOutputStream("hoge.txt"));

(※1) OutputStreamを指定した場合に出力される文字コードはUTF-8固定となります。 また、close処理は自動では行われませんので必要に応じて別途行う必要があります。

POJOからJSONへの変換ルールは次の通りです。

変換元（Java） | 変換先（JSON）
--- | ---
Map, DynaBean(※2) | object
Object(※3)
boolean[], short[], int[], long[], float[], double[], Object[] | array
Iterable (Collection, Listなど)
Iterator, Enumeration
char[], CharSequence | string
char, Character
TimeZone, Pattern, File, URL, URI, Type, Member, Charset
byte[] | string (BASE64)
Locale | string (言語コード-国コードあるいは言語コード-国コード-バリアントコード)
InetAddress | string (IPアドレス)
byte, short, int, long, float, double | number(※4)
Number
Date, Calendar | number (1970年からのミリ秒)
Enum | number (ordinalにより変換)
boolean, Boolean | true/false
null | null

(※2) DynaBeanを利用する場合、Commons BeanUtilsのjarファイルをクラスパスに追加する必要があります。リフレクションを利用して処理を行っているため、利用しない場合は特に含める必要はありません。
(※3) 対象となるインスタンスをパブリック・getterメソッド、パブリック・フィールドの優先順で探索します。staticが付加されたメソッドやフィールド、transientが付加されたフィールドは対象となりません。
(※4) NaN, Infinity, -Infinityに限りそれぞれ文字列"NaN", "Infinity", "-Infinity"に変換されます。

また、org.w3c.dom.Document/ElementからJSONへの変換もサポートしています。詳しくは「高度な使い方 - XMLからJSONへの変換」の項をご覧ください。

なお、JSONはobjectかarrayで始まる必要があるため、直接、intやStringのインスタンスをencodeメソッドの引数に指定した場合エラーとなります。

### ■ decodeメソッド - JSONからPOJOへの変換

JSONからPOJOに変換する場合は、decodeを使います。デフォルトでは、object, array, string, number, true/false, nullをHashMap, ArrayList, String, BigDecimal, Boolean, nullに変換しますが、二番目の引数に変換先のクラスを指定することでそのクラスのインスタンスにデータをセットして返してくれます。また、この処理はパブリック・フィールドやパブリック・プロパティ、配列やコレクションのデータを再帰的に辿り実行されますので、一般的なJavaBeansであればencodeして作られたJSONからの逆変換も可能です（Generics型にも対応しています）。

なお、JSON文字列が不正な場合にはJSONParseExceptionを投げ、型の変換に失敗した場合はJSONConvertExceptionを投げます。

    // JSONをPOJOに変換します。戻り値としてサイズが4のArrayListが返されます
    List list = (List)JSON.decode("[1, \"a\", {}, false]");

    // JSONをHogeクラスのインスタンスに変換します（キャストは不要です）
    Hoge hoge = JSON.decode("{\"number\": 10, \"array\": [1, 2, 3]}", Hoge.class);

    // ReaderやInputStreamからJSONを読み込むことも可能です(※5)
    Hoge hoge = JSON.decode(new FileReader("hoge.txt"), Hoge.class);
    Hoge hoge = JSON.decode(new FileInputStream("hoge.txt"), Hoge.class);

(※5) InputStreamから読み込む場合の文字コードは、UTF-8/UTF-16BE/UTF-16LE/UTF-32BE/UTF-32LEから自動判別されます。 また、close処理は自動では行われませんので必要に応じて別途行う必要があります。

JSONからPOJOへの変換ルールは次の通りです。

変換元（JSON） | 指定された型 | 変換先（Java）
--- | --- | ---
object | なし, Object, Map | LinkedHashMap
SortedMap | TreeMap
その他のMap派生型 | 指定された型
その他の型 | 指定された型（パブリック・フィールド／プロパティに値をセット)(※6)
array | なし, Object, Collection, List | ArrayList
Set | LinkedHashSet
SortedSet | TreeSet
その他のCollection派生型 | 指定された型
short[], byte[], int[], long[], float[], double[] Object[]派生型 | 指定された型
Locale | Locale（「言語コード」「国コード」「バリアントコード」からなる配列とみなし変換）
Map | インデックスの値をキーとするLinkedHashMap
SortedMap | インデックスの値をキーとするTreeMap
その他のMap派生型 | インデックスの値をキーとする指定された型のMap
string | なし, Object, CharSequence, String | String
char | char（幅0の時は'\u0000', 2文字以上の時は1文字目）
Character | Character（幅0の時はnull, 2文字以上の時は1文字目）
Appendable | StringBuilder
その他のAppendable派生型 | 指定された型（値をappend）
Enum派生型 | 指定された型（値をEnum.valueOfあるいはint型に変換後Enum.ordinal()で変換）
Date派生型, Calendar派生型 | 指定された型（文字列をDateFormatで変換）
byte, short, int, long, float, double, Byte, Short, Integer, Long, Float, Double, BigInteger, BigDecimal | 指定された型（文字列を数値とみなし変換）
byte[] | byte[]（文字列をBASE64とみなし変換）
Locale | Locale（文字列を「言語コード」「国コード」「バリアントコード」が何らかの句読文字で区切られているとみなし変換）
Pattern | Pattern（文字列をcompileにより変換）
Class, Charset | 指定された型（文字列をforNameにより変換）
TimeZone | TimeZone（文字列をTimeZone.getTimeZoneを使い変換）
File, URI, URL | 指定された型（文字列をコンストラクタの引数に指定し変換）
InetAddress | InetAddress（文字列をInetAddress.getByNameで変換）
boolean, Boolean | 指定された型（"", "false", "no", "off", "NaN"の時false、その他の時true）
number | なし, Object, Number, BigDecimal | BigDecimal
byte, short, int, long, float, double, Byte, Short, Integer, Long, Float, Double, BigInteger | 指定された型
Date派生型, Calendar派生型 | 指定された型（数値を1970年からのミリ秒とみなし変換）
boolean, Boolean | 指定された型（0以外の時true、0の時false）
Enum派生型 | 指定された型（int値をEnum.ordinal()に従い変換）
true/false | なし, Object, Boolean | Boolean
char, Character | 指定された型（trueの時'1'、falseの時'0'）
float, double, Float, Double | 指定された型（trueの時1.0、falseの時NaN）
byte, short, int, long, Byte, Short, Integer, Long, BigInteger | 指定された型（trueの時1、falseの時0）
boolean | boolean
Enum派生型 | 指定された型（trueを1、falseを0とみなしEnum.ordinal()に従い変換）
null | なし, Object | null
byte, short, int, long, float, double | 0
boolean | false
char | '\u0000'

(※6) 対象となるインスタンスに対しパブリックなsetterメソッド、パブリックなフィールドの優先順で探索します。 staticやtransientのメソッド/フィールドは対象となりません。 なお、プロパティ名は、単純比較が失敗した場合、LowerCamel記法に変換したものと比較し、次に末尾に「_」を付けて比較します（これは、予約語に対応するためです）。

## 高度な使い方

JSONICでは、フレームワークなどでの利用を想定していくつかの便利な機能を用意しています。

- 継承による機能拡張
- 総称型を指定してのdecode/parse
- 柔軟な読み込み
- 内部クラスを利用したエンコード/デコード
- 最大深度の設定
- null値の抑制
- XMLからJSONへの変換

### ■ 継承による機能拡張

JSONICは、フレームワークでの利用を考慮しインスタンスを生成したり、継承して拡張することができるように設計してあります。 なお、インスタンスを生成して利用する場合は、encode/decodeメソッドの代わりにformat/parseメソッドを利用します。

    // インスタンスを生成します
    JSON json = new JSON();

    // POJOをJSONに変換します(encodeと同じ機能)
    String text = json.format(new Hoge());

    // POJOを可読性の高いJSONに変換します(Pretty Printモード)
    json.setPrettyPrint(true);
    String text = json.format(new Hoge());

    // JSONをPOJOに変換します(decodeと同じ機能)
    Map map = (Map)json.parse(text);

    // JSONをHogeクラスのインスタンスに変換します(decodeと同じ機能)
    Hoge hoge = json.parse(text, Hoge.class);

DIコンテナなどを使いインスタンスを生成したり、独自の変換を追加するために次のようなオーバーライド可能なメソッドが用意されています。

    JSON json = new JSON() {

      // 引数で指定された内容に従い変換します。例外が投げられた場合、
      // JSONConvertExceptionでラップされ呼び出し元に通知されます。
      protected <T> T convert(Object key, Object value,
        Class<? extends T> c, Type type) throws Exception {

        // JSON arrayをjava.awt.Pointに変換する例です。
        // さらに下の階層を変換する場合はconvertChildメソッドを呼び出してください。
        if (c == Point.class && value instanceof List) {
          return (T)new Point(
          	convertChild(0, ((List)value).get(0), int.class, int.class),
          	convertChild(1, ((List)value).get(1), int.class, int.class)
          );
        }
        return super.convert(key, value, c, type);
      }

      // 型cに対するインスタンスを生成します
      protected Object create(Class c) {
        return super.create(c);
      }

      // Class cにおいて、Member mを無視します（parse/formatの両方で有効です）
      protected boolean ignore(Class c, Member m) {
        // デフォルトでは、static/transparentのメンバおよびObjectクラスで宣言された
        // メンバの場合、trueを返します。
        return super.ignore(c, m);
      }
    };

また、継承して作成した自作クラスをJSON.prototypeにセットすることで、JSON.encodeやJSON.decodeの動作を置き換えることも可能です。

    JSON.prototype = MyJSON.class;

### ■ 総称型を指定してのdecode/parse

Java 5.0で追加された総称型は動的な情報としては利用できないため、decode/parseメソッドのClass型引数として直接指定することができません。その代わり、FieldやMethodを利用することで間接的に指定することができます。

    public class JSONConfigLoader {
      private Map<String, Hoge> config;

      public Map load(Reader reader) throws JSONParseException, IOException {
        JSON json = new JSON();

        // これはコンパイルエラー
        config = json.parse(reader, Map<String, Hoge>.class);

        // これならOK
        config = (Map<String, Hoge>)json.parse(reader,
            getClass().getField("config").getGenericType());

        return config;
      }
    }

### ■ 柔軟な読み込み - 妥当でないJSONのデコード

JSONICはポステルの法則（送信するものに関しては厳密に、受信するものに関しては寛容に）に従い、妥当でないJSONであっても読み込みが可能なように作成されています。RFC 4627に規定された内容との相違点は以下の通りです。

- Cスタイルの複数行コメント（/**/）、C++スタイルの行コメント（//）およびシェルスクリプトスタイルの行コメント（#）をコメントとして認識します。
- ルート要素がobjectの場合、一番外側の'{'と'}'を省略することができます（入力文字列が空白文字列やコメントのみの場合も空のobjectとみなされます）。
- シングルクォートで囲まれた文字列やJavaリテラルを文字列として認識します（ただし、シングルクォートで囲まれた場合はjavascriptと異なりエスケープを処理しません）。
- objectやarrayにおいて各要素が改行で区切られているとき','を省略することができます。
- objectにおいてキーに対する値がobjectの場合、':'を省略することができます。
- string中で改行やタブなどの制御文字を有効な文字として認識します。
- objectやarrayにおいて値が省略された場合、nullとして認識します。

例えば、次のテキストはRFC 4627では無効ですが、JSONICでは読み込むことが可能です。

    # database settings
    database {
      description: 'ms sql server
    	connecter settings'
      user: sa
      password: xxxx // you need to replace your password.
    }

    /*
      equals to {"database": {
         "description": "ms sql server\n\tconnecter settings",
         "user": "sa", "password": "xxxx"}}
    */

### ■ 内部クラスを利用したエンコード/デコード

JSONの設定ファイルを解析したいような場合は、内部クラスやパッケージ・デフォルトのクラスを利用したいことがあります。
JSONICでは、encode/decode/parse/formatの引数に指定されたクラスと同一パッケージの内部クラスや無名クラスを自動的にアクセス可能に変更します。
ただし、この場合に生成された内部クラスのインスタンスには包含するクラスのインスタンスがセットされていない状態になります。内部クラスから包含するクラスのインスタンスにアクセスしたい場合や引数に指定したクラス以外のコンテキストで実行したい場合は、setContextを利用して明示的に指定してください。

    public class EnclosingClass {
      public void decode() {
        JSON json = new JSON();
        InnerClass ic = json.parse("{\"a\": 100}", InnerClass.class); // このクラスのコンテキストで動作

        System.out.println("ic.a = " + ic.a); // ic.a = 100

        ic.accessEnclosingClass(); // 実行時にNullPointerExceptionが発生

        json.setContext(this);  // コンテキストを設定
        ic = json.parse("{\"a\": 100}", InnerClass.class);

        ic.accessEnclosingClass(); // 正常に動作
      }

      class InnerClass {
        public int a = 0;

        public void accessEnclosingClass() {
          decode();
        }
      }
    }

### ■ setMaxDepth - 最大深度の設定

JSONICは、encode/format時に自分自身を戻すようなフィールドやプロパティ、配列を無視することで再帰による無限ループが発生することを防ぎます。 しかし、そのインスタンスにとって孫に当たるクラスが自分のインスタンスを返す場合にも再帰が発生してしまいます。JSONICでは、このような場合へ対処するため 単純に入れ子の深さに制限を設けています。

なお、最大深度の設定はdecode/parse時にも有効ですので深すぎるデータの取得を避けることも可能となります。

この最大深度は、デフォルトでは32に設定されていますが変更することも可能です。

    // 5階層以下の情報は取得しない
    json.setMaxDepth(5);

### ■ setSuppressNull - null値の抑制

JSONICでは、format時に値がnullになっているJSON objectのメンバの出力を抑制したり、parse時にnull値の代入を抑制することができます。初期値はfalseです。

余計なメンバが大量に出力されてしまう、プロパティの初期値を優先したいなどの場合に有効です。

    // null値の出力や代入を抑制します。
    json.setSuppressNull(true);

### ■ XMLからJSONへの変換

JSONICでは、org.w3c.dom.Document/ElementからJSONへの変換もサポートしています。 方法は、通常と同じようにencode/formatの引数にorg.w3c.dom.Document/Elementのインスタンスを設定するだけです。

    Document doc = builder.parse(new File("sample.xml"));
    String xmljson = JSON.encode(doc);

例えば、下記のXMLの場合

    <feed xmlns="http://www.w3.org/2005/Atom">
      <title>Feed Title</title>
      <entry>
        <title>Entry Title</title>
      </entry>
    </feed>

次のようなJSONが生成されます（実際にはタグ間の空白文字もTextNodeとして出力されます）。

    { "tagName": "feed", "attributes": {
        "xmlns": "http://www.w3.org/2005/Atom"
      },
      "childNodes": [
        { "tagName": "title", "childNodes": [ "Feed Title" ] },
        { "tagName": "entry", "childNodes": [
            { "tagName": "title", "childNodes": [ "Entry Title" ] }
        ]}
      ]
    }

## Web Service Servlet

JSONICには、JSON-RPC1.0によるRPCモードとRestfull APIによるRESTモードの2 Way Web Service機能が用意されています。

### ■ 設定方法

JSON Web Service Servletの設定は簡単です。web.xmlにWebServiceServletを指定し、パスとClassのマッピングなどの設定を行うだけです。

    <servlet>
      <servlet-name>json-ws</servlet-name>
      <servlet-class>net.arnx.jsonic.web.WebServiceServlet</servlet-class>
      <init-param>
        <param-name>config</param-name>
        <param-value>
        {
          "debug": true,
          "encoding": "UTF-8",
          "expire": true,
          "mappings": {
            "/[package]/[class].[ext]": "sample.web.${package}.service.${class}Service",
            "/[class].[ext]": "sample.${class}Service"
          }
        }
        </param-value>
      </init-param>
    </servlet>

    <servlet-mapping>
      <servlet-name>json-ws</servlet-name>
      <url-pattern>*.json</url-pattern>
    </servlet-mapping>

configで設定できる値は次の通りです。

キー | 値型 | 説明
--- | --- | ---
container | `net.arnx.jsonic.web.Container` | クラスのインスタンスを取得するためのコンテナを設定します。デフォルトは、`net.arnx.jsonic.web.Container`です。
encoding | `java.lang.String` | Request/Responseの文字エンコーディングを設定します。デフォルトはUTF-8です。
expire | `java.lang.Boolean` | クライアントキャッシュを抑制するHTTPヘッダを出力します(`Cache-Control: no-cache, Pragma: no-cache, Expires: Tue, 29 Feb 2000 12:00:00 GMT`)。デフォルトはtrueです。
debug | `java.lang.Boolean` | デバッグモードの有効/無効を切り替えます。デフォルトはfalseです。
mappings | `java.util.Map<String, String>` | URLパスとクラスのマッピングを行います。 パス中の`[name]`で囲まれた部分はクラス名の`${name}`に置換されます(※7)。 なお、RESTモードの場合、利用されなかった変数は引数としてメソッドに引き渡されます（RPCモードの場合は設定されません。詳しくはRESTモードの説明をご覧ください）。
definitions | `java.util.Map<String, Pattern>` | mappings中の変数の定義を正規表現で設定します。設定されない場合は`[^/()]+`が設定されたものと扱われます。
init | `java.lang.String` | 処理の実行前に呼び出されるメソッド名を設定します。デフォルトは`"init"`です。
destroy | `java.lang.String` | 処理の実行後に呼び出されるメソッド名を設定します。デフォルトは`"destroy"`です。

(※7) 変数名のうち、classとpackageだけは特殊な扱いがされます。class変数中の文字列はUpperCamelに変換され、package変数中の「/」は「.」に変換されます。 また、URLパスにはコンテキストパスを含める必要はありません。

### ■ RPCモード

JSON-RPCは、JSONを使ったシンプルなRemote Procedure Callプロトコルです。JSON Web Service Servletでは、class変数の値が`"rpc"`であった場合、RPCモードとなります。

RPCモードでは、対象のパスに対し次のようなJSONをPOSTすることで、対象クラスのメソッドを呼び出すことができます（GET/PUT/DELETEは無効です）。paramsに指定された配列の値はメソッドの引数に指定された型に従い自動的に変換されます。なお、クラス名はUpperCamel、メソッド名はLowerCamelに自動的に変換されます。そして、実行後、戻り値がJSONに変換されクライアントに返されます。

    {
      "method": "class.method",
      "params": [ arg1, arg2, ... ],
      "id": request_id
    }

class, methodにはそれぞれ変数の値、argNにはメソッドの引数を設定してください。requesst_idには送受信の同期確認用のキーとしてnull以外の任意の値を設定してください（HTTPでは、送信と受信は同期処理ですのでほとんど意味はありませんが、省略すると通知(Notification)モードとなりレスポンスのメッセージボディが返されませんので必ず値を指定してください）。

例えば、mappingsに "/[package]/[class].[ext]": "boo.${package}.${class}Service" という指定があった場合、 /foo/woo/rpc.jsonというパスに次のJSONがPOSTすると、boo.foo.woo.CalcServiceクラスのint plus(int a, int b)のようなメソッドが呼び出されます。

    {
      "method": "calc.plus",
      "params": [1,2],
      "id": 1
    }

この時、レスポンスのメッセージボディとしては次のような結果が返されます。

    {
      "result": 3,
      "error": null,
      "id": 1
    }

RPCモードでエラーが発生した場合にはレスポンスのメッセージボディでクライアントに通知されます。ステータスコードは、エラーの有無に関わらず200 OKが返されます。

errorの値には code, message, dataの三つのキーを持つJSON objectが設定されます。codeとmessageについては次表を参照してください。dataには投げられた例外のプロパティがセットされます（ただし、Throwableクラスのプロパティは除外されます）。

エラー内容 | HTTP Status Code | JSON error
--- | --- | ---
JSONリクエストがJSON-RPCのリクエストとして不正 | 200 OK |
    {
      "code": -32600,
      "message": "Invalid Request."
    }
methodで指定したクラス/メソッドが見つからない(※8) | 200 OK |
    {
      "code": -32601,
      "message": "Method not found."
    }
paramsが不適切(※9) | 200 OK |
    {
      "code": -32602,
      "message": "Invalid params."
    }
JSONの解析に失敗した | 200 OK |
    {
      "code": -32700,
      "message": "Parse error."
    }
その他のエラー | 200 OK |
    {
      "code": -32603,
      "message": "Internal error."
                 or Exception.getMessage()
    }

(※8) クラス/メソッドが見つからなかった時だけでなく、メソッドからIllegalStateExceptionやUnsupportedOperationExceptionが投げられた場合も同じエラーが返されます。
(※9) Convertに失敗した場合だけでなく、メソッドからIllegalArgumentExceptionが投げられた場合も同じエラーが返されます。

### ■ RESTモード

class変数の値が"rpc"以外の場合はRESTモードになります。RESTモードでは、GET/POST/PUT/DELETEの四つのHTTP Methodに従い対象となったクラスの次の名前のメソッドが呼び出されます(※10)。そして、実行後、戻り値がJSONに変換されクライアントに返されます(※11)。

HTTP Method | Java メソッド名 | 引数
--- | --- | ---
GET | find | クエリ変数名を「.」で区切られた階層構造とみなし引数の型に従い変換し設定
POST | create | メッセージボディのJSONを引数の型に従い変換し設定
PUT | update | メッセージボディのJSONを引数の型に従い変換し設定
DELETE | delete | メッセージボディのJSONを引数の型に従い変換し設定

なお、メッセージボディがJSON objectの場合、その値はリクエストパラメータ、パス変数の順で上書きされます。JSON arrayの場合は上書きされずに最後尾に追加されます。

(※10) ブラウザなどでは、PUT/DELETEが使えない場合があります。そのような場合の代替手段として、リクエストパラメータに「_method=メソッド名」を指定することもできます。
(※11) JSONはobjectかarrayより始まる必要があるため、それ以外の要素に変換される型の戻り値（例えば、boolean/int/Dateなど）の場合にはSC_NO_CONTENTが返されます。

RESTモードでは、エラーの発生はHTTP Status Codeによりクライアントに通知されます。

エラー内容 | HTTP Status Code
--- | ---
クラス/メソッドが見つからない(※12) | 404 Not found
送信されたJSONの解析/変換に失敗した(※13) | 400 Bad request
その他のエラー | 500 Internal Server Error

(※12) クラス/メソッドが見つからなかった時だけでなく、メソッドからIllegalStateExceptionやUnsupportedOperationExceptionが投げられた場合も同じエラーが返されます。
(※13) Convertに失敗した場合だけでなく、メソッドからIllegalArgumentExceptionが投げられた場合も同じエラーが返されます。

### ■ JSONP

RESTモードでかつHTTP MethodがGETの場合、リクエストパラメータとしてcallback=Function名を指定することでJSONPによる返答を返すことができるようになります。

    <script type="text/javascript" >
      function call(value) {
        alert(value);
      }
    </script>

    ...

    <script type="text/javascript" src="http://host/hoge.json?callback=call"></script>

### ■ 初期化/Servletパラメータへのアクセス

呼び出し対象となるクラスにinitあるいはdestroyという名前のメソッドがある場合、それぞれ処理の前後によびだされます。(※14) このとき引数にServletContext, HttpServletRequest, HttpServletResponse, HttpSessionを受け取れるように設定するとそれぞれのインスタンスが設定されます。

    public class HogeService {
        // 処理の前に呼び出されます。
        public void init(HttpServletRequest request, HttpServletResponse response) {
            // HttpServletRequest, HttpServletResponseにアクセス
            request.getInitParameter("test");
        }

        ...

        // 処理の後に呼び出されます。
        public void destroy() {
        }
    }

(※14) 呼び出されるメソッド名は設定で変更可能です。

### ■ DI Container対応

JSONRPCServletは、内部のコンテナを切り替えることで呼び出し対象のインスタンスを任意のDI Containerにて管理することが可能です。

    <servlet>
      <servlet-name>JSON-WebService</servlet-name>
      <servlet-class>net.arnx.jsonic.web.WebServiceServlet</servlet-class>
      <init-param>
        <param-name>config</param-name>
        <param-value>
          // "container": (net.arnx.jsonic.web.Containerを実装したクラス)
          // Seasar2対応Container
          "container": "net.arnx.jsonic.web.S2Container"
          // Spring Framework対応Container
          "container": "net.arnx.jsonic.web.SpringContainer"
          ...
        </param-value>
      </init-param>
    </servlet>

JSONICでは、Seasar2およびSpring Frameworkに対応したContainerを標準添付しています。このContainerを利用すると、DI Container上で管理されているコンポーネントをWebServiceとして利用することができるようになります。

### ■ Gateway Filter

JSONICでは、おまけ機能としてJSONを使ってServletで良く使う各種の機能を実装したFilterを提供しています。JSONICの書式を使えるため手軽に設定が可能です。

最初にマッチしたパスの設定が使われますが、そこで設定が行われなわれていない場合、ルートの設定が初期値として利用されます。パスには正規表現が利用できます。

    <filter>
      <filter-name>Gateway Filter</filter-name>
      <filter-class>net.arnx.jsonic.web.GatewayFilter</filter-class>
      <init-param>
        <param-name>config</param-name>
        <param-value>
          // 共通設定
          encoding: 'UTF-8'          // 文字コード設定
          locale: 'en'               // Responseのロケールを設定
          compression: true          // GZip圧縮

          // 拡張子がjsonのパスを対象
          '.+\.json': {
            expire: true           // クライアントキャッシュを無効化
          }

          // 例：日本向け設定
          '/ja/([^.]+)': {
            forward: '/$1.json'     // JSON Web Serviceに転送
            encoding: 'SHIFT_JIS'
            expire: true
            locale: 'ja-JP'
            access: ['jpuser']    // アクセス可能なロール
          }
        </param-value>
      </init-param>
    </filter>

    <filter-mapping>
      <filter-name>Gateway Filter</filter-name>
      <url-pattern>/*</url-pattern>
    </filter-mapping>

configで設定できる値は次の通りです。なお、これらの設定はフィルタの設定に関わらず一度だけしか適用されません。

キー | 値型 | 説明
--- | --- | ---
encoding | `java.lang.String` | Request/Responseの文字エンコーディングを設定します。デフォルトはnullです。
compression | `java.lang.Boolean` | クライアントから`Accept-Encoding: gzip or x-gzip`が送られる場合、ResponseをGZip圧縮します。
expire | `java.lang.Boolean` | クライアントキャッシュを抑制するHTTPヘッダを出力します(`Cache-Control: no-cache, Pragma: no-cache, Expires: Tue, 29 Feb 2000 12:00:00 GMT`)。デフォルトは`false`です。
forward | `java.lang.String` | 指定されたパスに転送します（パスはコンテキストパス以下を指定します。正規表現の置換変数が利用できます）。
access | `java.util.Set<String>` | アクセス可能なアプリケーションロールを配列で指定します（認証そのものはコンテナの機能などを使う必要があります）。
locale | `java.util.Locale` | Responseのロケールを設定します。

なお、encodingとexpireに関してはWebServiceServlet側にも同様の設定が用意されていますが、基本的にWebServiceServlet側の設定が優先されます。 ただし、WebServiceServlet側が未設定の場合は、GatewayFilter側の設定が利用されます。

## JSONIC for AS3

JSONICでは、クライアントにAdobe FlexやAdobe Airを使用する場合を想定し、AS3版のJSONクラスとRemoteObjectライクなJSON-RPCクライアントライブラリを同梱しています。

### ■ ActionScript3版 JSONライブラリ

Java版JSONクラスとほぼ同じです。ただし、decode/parseメソッドでの特定クラスへの変換はサポートしていません（動的型で十分だと思いますので……）。

    import net.arnx.jsonic.JSON;

    // インスタンスをJSONに変換します
    var text:String = JSON.encode(new Hoge());

    // JSONをObjectに変換します
    var hoge:Object = JSON.decode(text);

### ■ ActionScript3版 JSON-RPCクライアントライブラリ

RemoteObjectライクなJSON-RPCクライアントクラスです。使い方はmx:RemoteObjectやmx:WebServiceなどを参考にしてください（lastResultのBindingも可能です）。

    <mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml"
      xmlns:js="http://arnx.net/jsonic">

      <!-- sourceにコンポーネント名、Operationのnameにメソッド名を指定します。 -->
      <js:WebService id="remote"
        endpoint="http://localhost:8080/sample/rpc/rpc.json"
        source="calc"
        makeObjectsBindable="false"
        showBusyCursor="true"
        result="onResult(event)"
        fault="onFault(event)">

        <js:Operation name="plus" result="onResult(event)" fault="onFault(event)" />
        ...
      </js:WebService>

      <mx:Button label="実行" click="remote.plus(100, 200)" />
      <mx:TextInput id="output" text="{remote.plus.lastResult}" />

      <mx:Script>
        <![CDATA[
          private function onResult(event:ResultEvent) {
            trace(event.result); // 100+200 => 300
          }

          private function onFault(event:FaultEvent) {
            trace(event.fault.message);
          }
        ]]>
      </mx:Script>
    </mx:WindowedApplication>

## ライセンス

JSONICは、Apache License, Version 2.0下で配布します。

なお、書くまでもないことですが自分のライブラリへの組み込みやその際にパッケージ名や処理の変更など行っていただいて一向に構いません。保障はありませんが、ライセンスの範囲内でご自由にお使いください。

## バグ・要望の報告先

バグや要望などはJSONICプロジェクトサイトのトラッキング情報あるいはフォーラムまでご連絡ください。

## リリースノート

### 2009/02/15 version 1.0.4

- Decode/Format時にListの再帰抑制が正しく動作していなかった問題を修正しました。
- AS3版JSONICでエラー時の行番号, 列番号, オフセット値を設定していなかった問題を修正しました。
- setSuppressNullを1.1.0ベータ2からバックポートしました(AS3版も同様)。

### 2008/10/19 version 1.0.3

- Encode/Parse時にJSON stringにて\uXXXX形式で文字を指定した場合に変換に失敗していた問題を修正しました。
- JSON stringとjava.io.Fileの相互変換に対応しました。
- Decode/Format時にMapのkeyがnullだった場合、NullPointerExceptionが発生していた問題を修正しました（単に値を無視します）
- Encode/Parse時にJSON objectのキーとしてリテラルが指定された場合、値と同様に処理するように変更しました。
- Convert時にJSON arrayの変換がうまくいかない場合、先頭の値を対象として変換するように変更しました。
- Convert時にJSON objectの変換がうまくいかない場合、nullキーの値を対象として変換するように変更しました（主にWebServiceServletでの利用を想定しています）。
- Convert時にJSON objectからList/Arrayへの変換に対応しました。キー値の順番にソートされた値のリストとして扱います。
- Convert時にstatic inner classのインスタンス生成に失敗する問題を修正しました[bug:13459]
- DynaBeanのformatに対応しました。リフレクションにより処理を行っているため、DynaBeanを利用しない場合commons beanutilsのライブラリをクラスパスに追加する必要はありません。

### 2008/08/15 version 1.0.2

- java.lang.Class#getPackage()がnullを返す場合エラーになっていた問題を修正しました。
- WebServiceServletのRPCモードにて、エラー時に返されるerrorオブジェクトのdataプロパティに発生した例外をセットするようになりました。ただし、Throwableクラスのメソッドは対象になりません。
- Hot DeployのでClassの変更に失敗していた問題を修正しました。
- JSONIC-RESTのサンプルがIE7で動作しない問題を修正しました。
- 同梱のprototype.jsを1.6.0.2に更新しました。

### 2008/07/12 version 1.0.1

- JSON stringからEnum派生型へ変換時、頭一文字が数値の場合はint型とみなしEnum.ordinal()にて変換するよう変更しました。
- JSON true/falseからEnum派生型への変換に対応しました。
- JSON object中で値としてリテラル値が使われる場合、maxDepthの設定が使用されない問題を修正しました。
- java.util.LocaleからJSON stringへの変換時にバリアントコードも変換されるように変更しました。
- ルートの'{'が省略され、かつキーのみしか見つからないまま終了した場合にエラーとならない問題を修正しました。
- encode/formatにOutputStreamを引数にとるメソッドを追加しました。
- JSONクラスのstaticメソッドで使用するクラスを交換可能にするためprototypeフィールドを追加しました。
- ActionScript3.0版のJSONICライブラリ（JSONクラスおよびJSON-RPCクライアントクラス）を添付しました。

### 2008/05/15 version 1.0

- Collectionだけでなく、Iterable, Iterator, EnumerationからJSON arrayへの変換に対応しました。

Copyright (C) 2007-2008 Hidekatsu Izuno All right reserved.