# JSONIC - 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の現時点での最新バージョンは0.9.0です。

ダウンロードはこちらからできます。なお、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"); // publid 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);

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

変換元（Java） | 変換先（JSON）
--- | ---
Map | object
Object
boolean[], short[], int[], long[], float[], double[], Object[] | array
Collection
char[], CharSequence | string
char, Character
byte[] | string (BASE64)
Locale | string (言語コード-国コード)
byte, short, int, long, float, double | number(※1)
Number
Date, Calendar | number (1970年からのミリ秒)
boolean, Boolean | true/false
null | null

(※1) NaN, Infinity, -Infinityに限りそれぞれ文字列"NaN", "Infinity", "-Infinity"に変換されます。

なお、JSONのルートはobjectかarrayでなければなりません。このため、直接、intやStringのインスタンスをencodeメソッドの引数にしてもエラーとなり変換できません。この制約は後述の拡張モードをONにすることで解除することが可能です。

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

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

なお、JSON文字列が不正な場合にはParseExceptionを発生させますが、型の変換に失敗した場合は例外を発生させず単にnullをセットします（どうしても変換時のエラーを捕捉したい場合には、handConvertErrorをオーバーライドすることにより情報を取得することもできます）。

    // 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);

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

変換元（JSON） | 指定された型 | 変換先（Java）
--- | --- | ---
object | なし, Object, Map | HashMap
SortedMap | TreeMap
その他のMapを継承する型 | 指定された型
CharSequence, String | String
Appendable | StringBuilder
その他のAppendableを継承する型 | 指定された型（文字列化したものをappend）
その他の型 | 指定された型（パブリック・フィールド／プロパティに値をセット)
array | なし, Object, Collection, List | ArrayList
Set | HashSet
SortedSet | TreeSet
その他のCollectionを継承する型 | 指定された型
short[], byte[], int[], long[], float[], double[], Object[] | 指定された型
Locale | Locale（「言語コード」「国コード」「バリアントコード」からなる配列とみなし変換）
string | なし, Object, char, Character, CharSequence, String | String
Appendable | StringBuilder
その他のAppendableを継承する型 | 指定された型（文字列化したものをappend）
Dateを継承する型, Calendarを継承する型 | 指定された型（文字列をDateFormatで変換）
byte, short, int, long, float, double, Byte, Short, Integer, Long, Float, Double, BigInteger | 指定された型（文字列を数値とみなし変換）
byte[] | byte[]（文字列をBASE64とみなし変換）
Locale | Locale（文字列を「言語コード」「国コード」「バリアントコード」が何らかの句読文字で区切られているとみなし変換）
boolean, Boolean | 指定された型（"", "false", "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）
true/false | なし, Object, Boolean | Boolean
float, double, Float, Double | 指定された型（trueの時1.0、falseの時NaN）
byte, short, int, long, Byte, Short, Integer, Long, BigInteger, その他のNumberを継承する型 | 指定された型（trueの時1、falseの時0）
boolean | boolean
null | なし, Object | null
byte, short, int, long, float, double | 0
boolean | false
char | '\u0000'

### 高度な使い方

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

- 継承による機能拡張
- 拡張モード
- JSONを使ったメソッドの実行
- 最大深度の設定

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

JSONICは、フレームワークでの利用を考慮しインスタンスを生成したり、継承して拡張することができるように設計してあります。

    // インスタンスを生成します
    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() {
        // 型cに対するインスタンスを生成します
        protected Object create(Class c) {
            return super.create(c);
        }

        // 値valueを型cに変換します（Generics型の場合にはtypeに対象の型が入ります）
        protected Object convert(Object value, Class c, Type type) {
            return super.convert(value, c, type);
        }
    };

また、エラーを捕捉するためにオーバーライド可能な二つのメソッドが用意されています。

    JSON json = new JSON() {
        // JSONの構文解析でエラーが起こった場合に呼ばれます
        // message: エラーメッセージ
        // s: 入力文字列
        // pos: エラーが発生した入力文字列上の位置
        // line: エラーが発生した行
        // col: エラーが発生した列
        protected void handleParseError(String message, CharSequence s,
            int pos, int line, int col) throws ParseException {
            // デフォルトでParseExceptionを投げます
            super.handleParseError(message, s, pos, line, col);
        }

        // JSONからPOJOの変換時にエラーが起こった場合に呼ばれます
        // key: JavaBeansのproperty/field名
        // value: 変換前の値
        // c: 指定された型
        // type: Genericsの指定型
        // e: 発生した例外
        protected void handleConvertError(String key, Object value, Class c,
            Type type, Exception e) throws Exception {
            // デフォルトでは何もしません
            return super.handleConvertError(e);
        }
    };

#### ■ setExtendedMode - 拡張モードの利用

JSONのRFCに記載された仕様はシンプルで実装しやすい反面、制約がきつすぎる面があります。例えばJavaScriptではObjectのkeyとして文字列だけでなくidentifierが使えますし、文字列もダブルクォートだけでなくシングルクォートも使えますがJSONでは禁止されています。コメントや日付型も定義されていません。また、前述のようにルート要素がobjectとarrayに制限されています。このことは、JSONを設定ファイルとして利用したい場合やAjaxにてJSONを容易に扱いたい場合には不便です。

そこで、JSONICでは拡張モードを用意しています。拡張モードをONにするとformat/parseメソッドの制限が緩くなり下記のことが可能になります（その代わりRFCの仕様からははずれてしまいますが）。

- [format/parse] ルートがobjectとarrayに制限されなくなります
- [format] DateやCalendar型をJSONに変換する場合「new Date(number)」と出力されます
- [format] NaN, Infinity, -Infinityが、stringではなくリテラルとして出力されます
- [parse] ダブルクォートだけでなくシングルクォートのstringが使えるようになります
- [parse] /**/および//形式のコメントを無視します
- [parse] objectのkeyとしてidentifierの使用を許容します

拡張モードをONにするには次のようにsetExtendedModeにtrueをセットするだけです。

    // 拡張モードをONにします
    json.setExtendedMode(true);

#### ■ invoke - JSONを使ったメソッドの実行

JSONでRPC(Remote Procedure Call)を行おうと考えた場合、servletなどから送られてくるパラメータを上手い具合にメソッドの引数に引き渡す必要があります。JSONICでは、簡単にメソッドを実行できるようinvokeメソッドを用意しました。

    // target.test(1, 2, 3)を実行します（引き渡すJSONはarrayでなければなりません）
    json.invoke(target, "test", "[1, 2, 3]");

この時、あるメソッドは実行させたくない場合があります。その際にはlimitメソッドをオーバーライドすることで実行対象のメソッドを制限することができます。デフォルトではObjectクラスのメソッドを実行しないよう制限がかけられていますが、アノテーションが設定されているメソッドのみ実行するといった制限をかけることも容易に実現できます。

    JSON json = new JSON() {
        // methodの実行を制限する場合はtrue
        protected boolean limit(Method method) {
            // デフォルトではObjectクラスのメソッドは実行しないように制限されています
            super.limit(method);
        }
    };

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

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

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

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

## ライセンス

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

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

## バグ・要望の報告先

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

## リリースノート

### 2007/05/27

今まで日記上でほそぼそと公開していたSimple JSON Class for Javaを仕様などを整理し名前も新たに0.9にリバージョンして公開（機能面での変更はありませんが、微妙に変換ルールや仕様を調整してあります）。

Copyright (C) 2007 Hidekatsu Izuno All right reserved.