SourceForge.jp

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

ダウンロードはこちらからできます。なお、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)
Mapobject
Object(※1)
boolean[], short[], int[], long[], float[], double[], Object[]array
Collection
char[], CharSequencestring
char, Character
TimeZone, Pattern, Type, Member
byte[]string (BASE64)
Localestring (言語コード-国コード)
byte, short, int, long, float, doublenumber(※2)
Number
Date, Calendarnumber (1970年からのミリ秒)
Enumnumber (ordinalにより変換)
boolean, Booleantrue/false
nullnull
(※1) 対象となるインスタンスをパブリック・getterメソッド、パブリック・フィールドの優先順で探索します。staticが付加されたメソッドやフィールド、transientが付加されたフィールドは対象となりません。
(※2) 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を投げます(変換に失敗した際の動作を変更したい場合には、handleConversionFailureをオーバーライドします)。

// 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, MapLinkedHashMap
SortedMapTreeMap
その他のMap派生型指定された型
その他の型指定された型(パブリック・フィールド/プロパティに値をセット)(※3)
arrayなし, Object, Collection, ListArrayList
SetLinkedHashSet
SortedSetTreeSet
その他のCollection派生型指定された型
short[], byte[], int[], long[], float[], double[]
Object[]派生型
指定された型
LocaleLocale(「言語コード」「国コード」「バリアントコード」からなる配列とみなし変換)
Mapインデックスの値をキーとするLinkedHashMap
SortedMapインデックスの値をキーとするTreeMap
その他のMap派生型インデックスの値をキーとする指定された型のMap
stringなし, Object, CharSequence, StringString
charchar(幅0の時は'\u0000', 2文字以上の時は1文字目)
CharacterCharacter(幅0の時はnull, 2文字以上の時は1文字目)
AppendableStringBuilder
その他のAppendable派生型指定された型(値をappend)
Enum派生型指定された型(値をEnum.valueOfで変換)
Date派生型,
Calendar派生型
指定された型(文字列をDateFormatで変換)
byte, short, int, long, float, double,
Byte, Short, Integer, Long, Float, Double,
BigInteger, BigDecimal
指定された型(文字列を数値とみなし変換)
byte[]byte[](文字列をBASE64とみなし変換)
LocaleLocale(文字列を「言語コード」「国コード」「バリアントコード」が何らかの句読文字で区切られているとみなし変換)
PatternPattern(文字列をパターンとみなし変換)
ClassClass(文字列をClass.forNameを使い変換)
TimeZoneTimeZone(文字列をTimeZone.getTimeZoneを使い変換)
boolean, Boolean指定された型("", "false", "no", "off", "NaN"の時false、その他の時true)
numberなし, Object, Number, BigDecimalBigDecimal
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, BooleanBoolean
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)
booleanboolean
nullなし, Objectnull
byte, short, int, long, float, double0
booleanfalse
char'\u0000'
(※3) 対象となるインスタンスに対しパブリックなsetterメソッド、パブリックなフィールドの優先順で探索します。 staticやtransientのメソッド/フィールドは対象となりません。 なお、プロパティ名は、単純比較が失敗した場合、LowerCamel記法に変換したものと比較し、次に末尾に「_」を付けて比較します(これは、予約語に対応するためです)。

高度な使い方

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

■ 継承による機能拡張

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 arrrayを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);
  }    
};

■ Reader/InputStreamによるparse

staticメソッドのJSON.decodeでは文字列からPOJOへの変換しか対応できませんが、parseメソッドの場合にはjava.io.Readerやjava.io.InputStreamからJSONデータを読み込むことができます。

// ReaderからのJSONの読み込み
Hoge hoge = (new JSON()).parse(new FileReader("sample.json"), Hoge.class);

// InputStreamからのJSONの読み込み(ファイルはUnicodeである必要があります(※4)
Hoge hoge = (new JSON()).parse(new FileInputStream("sample.json"), Hoge.class);
(※4) UTF-8/UTF-16BE/UTF-16LE/UTF-32BE/UTF-32LEから自動判別します。

■ 総称型を指定しての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に規定された内容との相違点は以下の通りです。

例えば、次のテキストは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"}}
*/

※version 0.9.3まであった拡張モードは0.9.4以降廃止されました。

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

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

■ 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" ] }
    ]}
  ]
}

JSON Web Service Servlet

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

※JSONRPCServletは0.9.6以降net.arnx.jsonic.web.WebServiceServletに置き換えられました。

■ 設定方法

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",
      "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で設定できる値は次の通りです。

キー値型説明
containernet.arnx.jsonic.web.Containerクラスのインスタンスを取得するためのコンテナを設定します。デフォルトは、net.arnx.jsonic.web.Containerです。
debugBooleanデバッグモードの有効/無効を切り替えます。デフォルトはfalseです。
encodingjava.lang.StringRequest/Responseの文字エンコーディングを設定します。デフォルトはUTF-8です。
mappingsjava.util.Map<String, String>URLパスとクラスのマッピングを行います。 パス中の[name]で囲まれた部分はクラス名の${name}に置換されます(※5)。 なお、RESTモードの場合、利用されなかった変数はメッセージボディとして送られてきたJSON objectの値として設定されメソッドに引き渡されます(RPCモードの場合は設定されません)。
definitionsjava.util.Map<String, Pattern>mappings中の変数の定義を正規表現で設定します。設定されない場合は[^/]+が設定されたものと扱われます。
(※5) 変数名のうち、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
}

例えば、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モードでエラーが発生した場合にはHTTP Status Codeとレスポンスボディの両方でクライアントに通知されます。

エラー内容HTTP Status CodeJSON error
JSONリクエストがJSON-RPCのリクエストとして不正 400 Bad request
{
  "code": -32600,
  "message": "Invalid Request."
}
methodで指定したクラス/メソッドが見つからない(※6) 404 Not found
{
  "code": -32601,
  "message": "Method not found."
}
paramsが不適切(※7) 400 Bad request
{
  "code": -32602,
  "message": "Invalid params."
}
JSONの解析に失敗した 400 Bad request
{
  "code": -32700,
  "message": "Parse error."
}
その他のエラー 500 Internal Server Error
{
  "code": -32603,
  "message": "Internal error."
             or Exception.getMessage()
}
(※6) クラス/メソッドが見つからなかった時だけでなく、メソッドからUnsupportedOperationExceptionが投げられた場合も同じエラーが返されます。
(※7) Convertに失敗した場合だけでなく、メソッドからIllegalArgumentExceptionが投げられた場合も同じエラーが返されます。

■ RESTモード

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

HTTP MethodJava メソッド名引数
GETfindURLのQueryを「.」で区切られた階層構造とみなし引数の型に従い変換し設定
POSTcreateメッセージボディのJSONを引数の型に従い変換し設定
PUTupdateメッセージボディのJSONを引数の型に従い変換し設定
DELETEdeleteメッセージボディのJSONを引数の型に従い変換し設定

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

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

RESTモードでエラーが発生した場合にはHTTP Status Codeにてクライアントに通知されます。

エラー内容HTTP Status Code
クラス/メソッドが見つからない(※8) 404 Not found
送信されたJSONの解析/変換に失敗した(※9) 400 Bad request
その他のエラー 500 Internal Server Error
(※8) クラス/メソッドが見つからなかった時だけでなく、メソッドからUnsupportedOperationExceptionが投げられた場合も同じエラーが返されます。
(※9) 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>

■ 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"
      ...
    </param-value>
  </init-param>
</servlet>

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

ライセンス

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

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

バグ・要望の報告先

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

リリースノート

2008/03/02 version 0.9.7

下記の機能追加・変更とバグ修正が行われています。

2008/02/18 version 0.9.6

下記の機能追加・変更とバグ修正が行われています。

2008/01/28 version 0.9.5

下記の機能追加・変更とバグ修正が行われています。

2008/01/25 version 0.9.4.1

パッケージミスと記載ミスの修正のみおこなわれています。

2007/09/09 version 0.9.4

下記の機能追加とバグ修正が行われています。

2007/06/10 version 0.9.3

主に機能追加、メソッドの整理が変更点です。

2007/06/03 version 0.9.2

下記のバグ修正と機能追加を行いました。

2007/06/02 version 0.9.1

下記のバグ修正と機能追加を行いました。

2007/05/27 version 0.9.0

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