- simple json encoder/decoder for java

## 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モード）。

なお、JSONのフォーマット中に何らかの例外が発生した場合は、JSONExceptionでラップされ通知されます（Beanからの取得時に例外発生など）。

    // 変換対象の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文字列が不正であったり、型の変換に失敗した場合はJSONExceptionが投げられます。

    // 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() {

      // フォーマット可能なクラスに変換します（formatでのみ有効です）。
      // 例外が発生した場合、JSONExceptionでラップされ呼び出し元に通知されます。
      protected Object preformat(Context context, Object value) throws Exception {
        // java.awt.geom.Point2DをJSON arrayにフォーマットする例です。
        if (value instanceof Point2D) {
          Point2D p = (Point2D)value;
          List list = new ArrayList();
          list.add(p.getX());
          list.add(p.getY());
          return list;
        }
        return super.preformat(context, value);
      }

      // 解析されたデータを指定したクラスに変換します（parseでのみ有効です）。
      // 例外が発生した場合、JSONExceptionでラップされ呼び出し元に通知されます。
      // さら下の階層を変換したい場合は、context.convert(キー, 値, 型)を呼び出してください。
      protected <T> T postparse(Context context, Object value,
        Class<? extends T> c, Type t) throws Exception {

        // JSON arrayをjava.awt.geom.Point2Dに変換する例です。
        if (Point2D.class.isAssignableFrom(c) && value instanceof List) {
          List list = (List)value;
          Point2D p = (Point2D)create(context, c);;
          p.setLocation(
            context.convert(0, list.get(0), double.class),
            context.convert(1, list.get(1), double.class)
          );
          return c.cast(p);
        }
        return super.postparse(context, value, c, t);
      }

      // 型cに対するインスタンスを生成します（parseでのみ有効です）。
      protected <T> T create(Context context, Class<? extends T> c) throws Exception {
        if (Point2D.class.isAssignableFrom(c)) {
          return c.cast(new Point2D.Double());
        }
        return super.create(context, c);
      }

      // Class cにおいて、Member mを無視します（parse/formatの両方で有効です）。
      protected boolean ignore(Context context, Class c, Member m) {
        // デフォルトでは、static/transparentのメンバおよびObjectクラスで宣言された
        // メンバの場合、trueを返します。
        return super.ignore(context, 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 JSONException, 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からJsonMLへの変換をサポートしています。 方法は、通常と同じように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として出力されます。不要な場合は、DOM作成時に取り除く必要があります）。

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

### ■ JSONHintアノテーション - 変換時ヒントの付加

場合によってデフォルトの変換方式では不十分な場合があります。JSONICでは、メソッドやフィールドにJSONHintアノテーションを付加することで、 動作を部分的に制御することが可能です。

設定できる属性は次の通りです。

属性名 | 値型 | 説明
--- | --- | ---
name | String | 出力/代入するキー名を変更します
format | String | 対象の型がNumberあるいはDate型の場合は、指定したフォーマットに従って変換します。 フォーマットの書式はそれぞれjava.text.DecimalFormat、java.text.SimpleDateFormatを参照してください。
type | Class | parse時に指定した型のインスタンスを生成します（対象の型のサブクラスを指定する必要があります）。
ignore | boolean | 出力/代入対象から除外します
serialized | boolean | 値がJSONであるものとして扱います。デフォルトはfalseです。 Format時はtoString()の値をそのまま出力(※7) 、Parse時は入力されたJSONをJava Objectに変換し再度formatした文字列が設定されます。

(※7) 出力される文字列は検証されないため妥当でないJSONが出力されてしまう可能性があることに注意してください。 逆に言えば、この機能を使うことでコメントやfunction呼び出しを出力することも可能です。

    public class WithHintBean {
      // format/parse時のキー値を変更
      @JSONHint(name="名前")
      public int keyValue = 100;

      // format/parse時のフォーマットを指定
      @JSONHint(format="yyyy/MM/dd")
      public Date dateValue = new Date();

      // 数値の時は、DecimalForamtとして認識される
      @JSONHint(format="##0.00")
      public int numberValue = 100;

      // 配列やリストでもOK
      @JSONHint(format="yyyy/MM/dd")
      public List dateArray;

      // メソッドにも付与可能（getter/setterで別のヒントを与えることも可）
      @JSONHint(format="yyyy/MM/dd")
      public int getMethodValue() {
        return 100;
      }

      // ArrayListの代わりにLinkedListのインスタンスを生成
      @JSONHint(type=LinkedList.class)
      public List stringList;

      // format/parse時に無視
      @JSONHint(ignore=true)
      public int ignoreValue = 100;

      // 値はJSON
      @JSONHint(serialized=true)
      public String json = "{\"num\": 100, \"func\": sum(100, 200) /*illegal JSON*/}";
    }

## 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}`に置換されます(※8)。 なお、RESTモードの場合、利用されなかった変数は引数としてメソッドに引き渡されます（RPCモードの場合は設定されません。詳しくはRESTモードの説明をご覧ください）。また、`{name:regex}`と記載することで、JSR-311同様パターンを設定することができます。
definitions | `java.util.Map<String, Pattern>` | mappings中の変数の定義を正規表現で設定します。設定されない場合は`[^/()]+`が設定されたものと扱われます。
init | `java.lang.String` | 処理の実行前に呼び出されるメソッド名を設定します。デフォルトは`"init"`です。
destroy | `java.lang.String` | 処理の実行後に呼び出されるメソッド名を設定します。デフォルトは`"destroy"`です。
processor | `net.arnx.jsonic.JSON` | 処理に使用するJSONクラスを設定します。デフォルトは、`net.arnx.jsonic.web.WebServiceServlet.JSON`です。

(※8) 変数名のうち、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で指定したクラス/メソッドが見つからない(※9) | 200 OK |
    {
      "code": -32601,
      "message": "Method not found."
    }
paramsが不適切(※10) | 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()
    }

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

### ■ RESTモード

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

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

引数には送信されたJSONが指定された型に従い変換されセットされます。引数への設定は、JSONの型によって次のような違いがあります。

Request Type | 説明
--- | ---
object | 一番目の引数にパス変数、クエリ変数、リクエストボディの値が順に追加されたJSON objectが設定されます（同じキーが複数出現した場合は配列化されます）。
array | 送信されたJSON arrayを引数リストとして扱います。 なお、第一引数がJSON objectである場合には、上記と同様にクエリ変数、パス変数、対象の引数の順でデータが追加された形で設定されます。 JSON stringなどその他の型であった場合や第二引数以降はそのまま設定されます。

(※11) ブラウザなどでは、PUT/DELETEが使えない場合があります。そのような場合の代替手段として、クエリ変数に「_method=HTTP Method名」を指定することもできます。
(※12) JSONはobjectかarrayより始まる必要があるため、それ以外の要素に変換される型の戻り値（例えば、boolean/int/Dateなど）の場合にはSC_NO_CONTENTが返されます。
(※13) Content-Typeが「application/x-www-form-urlencoded」の場合には、GETの場合と同様にフォームのパラメータ名を「.」で区切られた階層構造とみなして扱います（ただし、この機能は、Seasar2と共に使う場合や一部ServletContainerでは相性の問題で利用できません）。

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

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

(※14) クラス/メソッドが見つからなかった時だけでなく、メソッドからIllegalStateExceptionやUnsupportedOperationExceptionが発生した場合も同じエラーが返されます。
(※15) Convertに失敗した場合だけでなく、メソッドからIllegalStateException、UnsupportedOperationException、Error以外の例外が発生した場合も同じエラーが返されます。 なお、この場合、発生した例外がJSON objectとしてメッセージボディに設定されます（ただし、Throwableクラスのプロパティは除外されます）。

### ■ 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>

### ■ 初期化/終了処理

呼び出し対象となるクラスにinitあるいはdestroyという名前のメソッドがある場合、それぞれ処理の前後によびだされます。(※16)

    public class HogeService {
        // 処理の前に呼び出されます。
        public void init() {
        }

        ...

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

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

### ■ 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"
          // Google Guice対応Container
          "container": "net.arnx.jsonic.web.GuiceContainer"
          ...
        </param-value>
      </init-param>
    </servlet>

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

デフォルトでは最低限の機能のみ持つnet.arnx.jsonic.web.Containerが使われます。このコンテナが持つ機能は次の通りです。

- オブジェクトはClass#newInstance()により毎回生成されます。
- ログは、ServletContext#log()を使って書き出されます。
- JSPの暗黙オブジェクトライクなパブリックフィールドベースの簡易DIを提供します（下記例を参照）。

    public class HogeService {
      // Servlet関係のオブジェクトを設定します。クラスだけでなくフィールド名も合わせる必要があります。
      public ServletConfig config;
      public ServletContext application;
      public HttpServletRequest request;
      public HttpServletResponse response;
      public HttpSession session;
    }

### ■ 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プロジェクトサイトのチケットに報告ください。

## リリースノート

### 2010/02/11 version 1.1.3

- JSONのデコード時に指数部が2桁以上ある数値の解析に失敗していた点を修正しました（JSONIC AS3も同様です）。[#20537]

### 2009/12/02 version 1.1.2

- JSONクラスおよびWebServiceServletにてメソッドを探す際、合成メソッドやブリッジメソッドも対象に含めてしまっていた問題に対応しました[#19766]

### 2009/10/03 version 1.1.1

- [非互換] JSON WebServiceでのmappingキーのフォーマットとして[キー名]の表記を廃止しました。{キー名}を利用してください（1.0との互換性のため残していましたが、[]はURL文字列として利用可能であり問題があると判断し廃止しました）。
- publicでない内部クラスのインスタンスを変換する際、必ずsetContext(Object o)を呼ぶ必要がありましたが、Contextの設定有無に関わらず内部クラスを変換可能にしました。ただし、Decode/Parse時に内部クラスが親のインスタンスを参照している場合には、引き続きContextの設定が必要です。
- 配布用zipファイルに含まれるbuild.xmlにdebug設定が漏れていた点を修正しました。
- build.xmlにdebug設定が漏れていた点を修正しました。
- Decode/Parse時に文字列からClassへの変換でクラスローダがシステムクラスローダに固定されるためjettyでクラスロードが失敗する問題を解消しました。カレント・スレッドのクラスローダからロードに失敗した場合のみ、システムクラスローダからクラスをロードします。
- Decode/Parse時に、変換先としてPropertiesが指定された場合にMap<Object, Object>として扱われる問題を解消しました。なお、キーは「.」で連結されたフラットな形式で格納されます。
- JSON WebServiceでのmappingキーとして、JSR-311と同じく{キー名:正規表現}と記述できるようになりました。なお、definitionsによる指定も可能です。
- JSON WebServiceにてGoogle GuiceをDI Containerとして用いるnet.arnx.jsonic.web.GuiceContainerを追加しました。
- WebServiceServletにてConfigクラスを拡張できるようgetConfigClass()メソッドを追加しました。
- WebServiceServletに呼び出し処理の前処理、後処理を追加できるようにするため、preinvoke/postinvokeメソッドを追加しました。
- サンプルを多少マシにしました。

### 2009/03/01 version 1.1.0

JSONICの基本機能に関する1.0からの変更点：

- 添付するライブラリJARを作成する際、Eclipseでのデバッグを容易にするため、コンパイルのデバッグモードをONにすると共に、ソースコードも同梱するようになりました。デバッグ情報などが不要な場合は、build.xmlのdebugプロパティをfalseに変更して再ビルドしてください。
- 変換・代入の際のヒントを与えるJSONHintアノテーションを追加しました。
- [非互換] Parse時にキー名とプロパティ名を比較する際、予約語に対応するため末尾の「_」を削除して比較するという仕様がありましたが、JSONHint(name="予約語")で対応できるため廃止しました。
- [非互換] convert, convertChildを廃止し、Format用の変換メソッドとしてpreformatをParse用の変換メソッドとしてpostparseを追加しました。
- [非互換] 継承で拡張するpreformat, postparse, create, ignoreの引数として現在の位置を示すContextを設定するようにしました。
- [非互換] 各種JSON***Exceptionを廃止し、JSONExceptionに統合しました。エラー内容は、getErrorCode()で判別できます(AS3版も同様にJSONParseErrorがJSONErrorに変更されています)。
- [非互換] format/encodeの場合でも変換に失敗した場合は、JSONExceptionでラップした例外を通知するよう変更しました。
- [非互換] XMLからJSONへの変換を独自形式からJsonML形式に変更しました。
- java.sql.Date, java.sql.Timeの変換をきちんとしました。
- null値の出力や代入を抑制するsetSuppressNullメソッドを追加しました(AS3版も同様です)。※1.0.4以降にバックポートされています
- Encode/Format時にListの再帰抑制が正しく動作していなかった問題を修正しました。※1.0.4以降にバックポートされています

JSON Web Serviceに関する1.0からの変更点：

- JSON WebServiceで用いるJSONクラスが差し替え可能になりました。
- net.arnx.jsonic.web.ContainerにJSP暗黙オブジェクトライクなパブリックフィールドベースのDI機能を追加しました。
- [非互換] DI機能の追加に伴いメソッド引数へのServlet関連オブジェクト（HttpServletRequestなど）の注入を廃止しました。
- JSON WebServiceのRESTモードにて、Content-Typeが「application/x-www-form-urlencoded」の場合、GETと同様にパラメータを解析した結果を引数にセットしてメソッドを呼び出すように変更しました。
- JSON WebServiceでのmappingキーのフォーマットを[キー名]から{キー名}に変更しました（互換性維持のため、当面[キー名]も利用可能です）。
- [非互換] JSON WebServiceにてオーバーロードされたメソッドの呼び出しに対応しました。ただし、判断は引数の長さでのみ行われ、型での判断は行いません。
- [非互換] JSON WebServiceにて引数や戻り値の存在するinitやdestroyのみが定義されていた場合、IllegalStateExceptionを投げるようにしました。
- [非互換] net.arnx.jsonic.web.Containerのinitメソッドの引数をServletContextからServletConfigに変更しました。
- [非互換] JSON WebServiceのRESTモードにて、JSON Arrayをサーバに対して送信した際、パス変数やクエリ変数は最後の引数として設定していましたが、1番目の引数にマージするようになりました（ただし、1番目の引数がJSON objectでない場合はマージされません）。
- [非互換] JSON WebServiceにてgetParameterを使用後、getInputStream/getReaderを使うことができないServletコンテナに対応しました。
- [非互換] WebServiceServletのRESTモードにて、例外が発生した場合プロパティをメッセージボディに設定するようにしました。ただし、Throwableクラスのメソッドは対象になりません。
- 例外発生時のデバッグメッセージをより詳細なものに変更しました。
- jQueryを使ったRESTモードのサンプルを追加しました。

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