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);
Version 1.2.6 からは、JavaScript内での直接出力用に escapeScript が追加されました。JSONでは許されていない string, number など値の出力やXSS脆弱性を防ぐ<>のエスケープも行われます
// POJOをJavaScriptに変換します() var value = <%= JSON.escapeScript(value) %>;
JSONICには、JSON操作APIだけでなく、JSONを使ったWebサービスが簡単に構築できるサーブレットも用意されています。詳しくはWebサービスAPIのドキュメントを御覧ください。
ダウンロード
ダウンロードはこちらからできます。なお、JSONICのビルド/実行には、Java 5.0以上が必要です。
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"));
POJOからJSONへの変換ルールは次の通りです。
変換元(Java) | 変換先(JSON) |
---|---|
Map, DynaBean(※2) | object |
Object(※3) | |
boolean[], short[], int[], long[], float[], double[], Object[] | array |
Iterable (Collection, Listなど) | |
Iterator, Enumeration | |
java.sql.Array, java.sql.Struct | |
char[], CharSequence | string |
char, Character | |
TimeZone, Pattern, File, URL, URI, Type, Member, Charset, UUID | |
byte[] | string (BASE64エンコード) |
java.sql.RowId | 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 |
(※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メソッドの引数に指定した場合エラーとなります。
JSONデコーダー
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); // クラスの配列型への変換も可能です。 Hoge[] data = JSON.decode("[{ \"id\": 1 }, { \"id\": 2 }, { \"id\": 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);
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を使い変換) | |
UUID | UUID(文字列をUUID.fromStringで変換) | |
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' |
高度な使い方
JSONICでは、フレームワークなどでの利用を想定していくつかの便利な機能を用意しています。
- 継承による機能拡張
- 総称型を指定してのdecode/parse
- 柔軟な読み込み
- JSONの検証
- JavaScriptに親和的な出力
- 日時/数値書式の指定
- プロパティ名/列挙型出力書式の指定
- 内部クラスを利用したエンコード/デコード
- 最大深度の設定
- 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<Double> list = new ArrayList<Double>(); 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
decodeやparseの引数にはJava 5.0で追加された総称型も指定できます。しかし、総称型はコンパイル時に削除されてしまうため、decode/parseメソッドの引数として直接的に指定することができません。総称型を使う場合はルート要素をJSON objectにして、対応するクラス定義の中で総称型を使うことをおすすめします。
public class Config() { public static class RowData { public String id; public String name; } public List<RowData> rows; public static Config load(Reader reader) throws IOException { return JSON.decode(reader, Config.class); } }
多少トリッキーですが、FieldやMethodや無名クラスからリフレクションで総称型を取得することで間接的に指定する方法もあります。
private Map<String, Hoge> config; // Filedを使って総称型を指定 public Map<String, Hoge> load(Reader reader) throws IOException { return JSON.decode(reader, this.getClass().getField("config").getGenericType()); } // 総称型を継承した無名クラスを使って総称型を指定 public List<RowData> load(Reader reader) throws IOException { return JSON.decode("[ { ... }, { ... } ]", (new ArrayList<RowData>() {}).getClass().getGenericSuperclass()); }
■ 柔軟な読み込み - TRADITIONALモード
JSONICはポステルの法則(送信するものに関しては厳密に、受信するものに関しては寛容に)に従い、デフォルトでは、妥当でないJSONであっても読み込みが可能なTRADITIONALモードで動作するように作成されています。
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"}} */
この動作はsetMode(Mode.STRICT)
を指定することで、RFCに準じた妥当性チェックを行なうよう変更することができます。
■ JSONの検証 - STRICTモード
JSONICでは、従来柔軟な読み込みができる反面、RFC 4627に厳密に沿ったJSONであるか判定することができませんでした。 JSONIC 1.2.1からはSTRICTモードが用意され、厳密な検証動作が可能となりました。
モードを変更する場合は、JSONインスタンスのコンストラクタに設定するか、setModeメソッドを呼ぶか、JSON.prototypeにModeを変更したクラスを設定します。
JSON json = new JSON(JSON.Mode.STRICT); json.setMode(JSON.Mode.STRICT); JSON.prototype = (new JSON() { { setMode(JSON.Mode.STRICT); } }).getClass();
また、データのデコードを行わず検証のみを行うvalidateメソッドも用意されています(これは、setDepth(0)、setMode(Mode.STRICT)を指定した時と同じです)。
JSON.validate(new FileInputStream("test.json"));
■ JavaScriptに親和的な出力 - SCRIPTモード
JSONは、可搬性あるデータ連携フォーマットとしてだけでなく、HTML内に書かれるJavaScript内にJavaオブジェクトの内容をインライン出力するためにも便利です。
JSONICでは、このような場合に使いやすいようSCRIPTモードを用意しています。
RFC 4627に規定された内容との相違点は以下の通りです。
- [エンコード] 引数にStringやIntegerなど、ルート要素が object や array にならないオブジェクトを指定し、JSONの断片を出力することができます。
- [エンコード] HTMLやXML中ではエスケープが必要な「<」「>」が文字列中に見つかった場合、それぞれ「\u003C」「\u003E」と出力します。
- [エンコード] java.util.Date 型を new Date(ミリ秒) で出力します。
- [エンコード] NaN、POSITIVE_INFINITY, NEGATIVE_INFINITY を文字列ではなく、Number.NaN、Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITYとして出力します。
- [デコード] 引数にstring、number、true/false/nullといったJSONの断片を指定し単純型の値を取得することができます。
- [デコード] Cスタイルの複数行コメント(/**/)、C++スタイルの行コメント(//)をコメントとして認識します(TRADITIONALモードと異なり#はコメントとして認識しません)。
- [デコード] シングルクォートで囲まれた文字列をstringとして認識します(TRADITIONALモードと異なりダブルクォートで囲った場合と同様エスケープを認識します)。
- [デコード] object のキーに限りシングルクォートで囲まれていないリテラルを文字列として認識します。
モードを変更する場合は、JSONインスタンスのコンストラクタに設定するか、setModeメソッドを呼ぶか、JSON.prototypeにModeを変更したクラスを設定します。また、1.2.6からは、JSON.escapeScript を通じて簡単に使うことが可能です。
JSON json = new JSON(JSON.Mode.SCRIPT); json.setMode(JSON.Mode.SCRIPT); JSON.prototype = (new JSON() { { setMode(JSON.Mode.SCRIPT); } }).getClass(); JSON.escapeScript(...);
■ 日時/数値書式の指定 - setDateFormat/setNumberFormat
日付型や数値型は、デフォルトではJSON numberとして出力されますが、JSONIC 1.2.8以降ではsetDateFormat/setNumberFormatを指定することでデフォルトの日時/数値書式を設定できます。フォーマットの書式はそれぞれjava.text.DecimalFormat、java.text.SimpleDateFormatを参照してください(※7)。なお、書式は JSONHint を使うことで上書きすることができます。
JSON json = new JSON(); // デフォルトの日時書式を指定 json.setDateFormat("yyyy/MM/dd"); // デフォルトの数値書式を指定 json.setNumberFormat("###,##0.00"); // 戻り値は { "date": "2011/01/01", "number": "1,000.00" ] となります json.format(new Object() { public Date date = new Date(2011, 0, 1); public int number = 1000; });
■ プロパティ名/列挙型出力書式の指定 - setPropertyStyle/setEnumStyle
プロパティ名は、デフォルトではプロパティ名をJSON stringとして、列挙型は序数をJSON numberとして出力しますが、JSONIC 1.2.8以降ではsetPropertyStyle/setEnumStyleを使用することで出力書式を設定できます。
JSON json = new JSON(); // プロパティ名を、アッパーキャメル記法に変換して出力 json.setPropertyStyle(NamingStyle.UPPER_UNDERSCORE); // 列挙値を、小文字アンダースコア区切りに変換して出力 json.setEnumStyle(NamingStyle.LOWER_CAMEL); // 戻り値は { "JSON_MODE": "halfEven" } となります json.format(new Object() { public RoundingMode jsonMode = RoundingMode.HALF_EVEN; });
■ 内部クラスを利用したエンコード/デコード
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を参照してください(※8)。 |
type | Class | parse時に指定した型のインスタンスを生成します(対象の型のサブクラスを指定する必要があります)。 |
ignore | boolean | 出力/代入対象から除外します |
serialized | boolean | 値がJSONであるものとして扱います。デフォルトはfalseです。 Format時はtoString()の値をそのまま出力(※9) 、Parse時は入力されたJSONをJava Objectに変換し再度formatした文字列が設定されます。 |
anonym | String | 単純値型からMapや複合型に変換するときに単純値型を設定するプロパティ名を指定します。anonymを指定しない場合、Mapの場合はnullキーの値として設定されますが、複合型を指定した場合はエラーとなります。 |
ordinal | int | JSON objectへの変換する際のキーの出力順を昇順で指定します。デフォルトはキー値の自然順序順(=負値指定)です。 |
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<Date> dateArray; // メソッドにも付与可能(getter/setterで別のヒントを与えることも可) @JSONHint(format="yyyy/MM/dd") public int getMethodValue() { return 100; } // ArrayListの代わりにLinkedListのインスタンスを生成 @JSONHint(type=LinkedList.class) public List<String> 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*/}"; }
■ JSONHintによるString指定 - データの文字列化
JSONHintアノテーションのtype属性にString
を指定することで、データをtoString()およびString型を引数にとるコンストラクタを取る文字列相当型として扱うことができるようになります。
public class TestBean { @JSONHint(type=String.class) public StringBean sb; } public class StringBean { // decode時は、String型を引数に取るコンストラクタが呼ばれます public StringBean(String str) { ... } // encode時は、toStringが呼ばれます public String toString() { ... } }
■ JSONHintによるSerializable指定 - データの部分シリアル化
JSONHintアノテーションのtype属性にjava.io.Serializable
を指定することで、データをObjectInputStream/ObjectOutputStreamによりシリアル化されたバイト列データとして取り扱うことができます(バイト列はBase64でエンコードされJSON stringとして出力されます)。
public class TestBean { @JSONHint(type=Serializable.class) public SerializableBean sb; }
この機能を使うことで、JSON化が困難なオブジェクトもJSON-RPCなどでやり取りすることが可能となります。
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>
FAQ
- Q. RESTServletでHTTP GETを使うと日本語が文字化けします
- A. 入力文字エンコーディングの問題です。特にApache Tomcat5以降は仕様を厳密に解釈した結果、GETがsetCharacterEncodingを無視するという問題がありますのでuseBodyEncodingForURIを設定し回避する必要があります(JSONIC 1.1 ではGETパラメータを独自に解析していたため、この問題は発生していませんでした)。
- Q. 大量データを読み込むとOutOfMemoryErrorで落ちます。
- A. JSONICは、ブラウザをクライアントとしたような大規模データの送受信を行わないシステムでの利用を想定して作成されています。XMLにおいてDOMに対してSAXやStAXがあるように、大量データを取り扱う場合にはストリームAPIを利用すべきですがJSONICには現状用意されておりませんので、Twitter Streaming APIのように行区切りのJSONで送受信するか、jacksonなど代替ライブラリの採用を検討ください。
- Q. Resin サーバで RESTServletやRPCServletが動作しません。
- A. Resin サーバでは、web.xml中にある ${...} を変数として扱うため、\${...} と書かないといけないようです。
Mavenリポジトリ
JSONICは、1.2.7以降 Maven Central Repository に登録されるようになりました。groupId、artifactIdは次の通りです。
<groupId>net.arnx</groupId> <artifactId>jsonic</artifactId>
それ以前のバージョンに関しては、 Seasar.orgのMavenリポジトリ などをご利用ください。
ライセンス
JSONICは、Apache License, Version 2.0下で配布します。
自分のライブラリへの組み込んでいただいたり、その際にパッケージ名の変更や処理の変更など行っていただいて構いません。保障はありませんが、ライセンスの範囲内でご自由にお使いください。
バグ・要望の報告先
バグや要望などはJSONICプロジェクトサイトのチケットに報告ください。
リリースノート
2012/1/17 version 1.2.11
- [不具合] JDK5.0でサポートされていないメソッドが使われていた問題を修正しました(#27563)
- [不具合] setEnumStyle が適切に動作していなかった問題を修正しました。
- [不具合] Web Service API にて引数の型として public 型しかとれない問題を修正しました。
- [機能追加] NamingStyle に文字列をそのまま戻す NOOP を追加しました。
2012/1/17 version 1.2.10
- [不具合] JSONHint にて serialized = true を指定してdecode/parseした場合、2階層目以降のオブジェクトが正しく書式化されない問題を修正しました(#27430)
- [不具合] 1.2.6 以降で normalize() メソッドをオーバーライドしても有効になっていなかった問題を修正しました。
- [不具合] encode/format時、JSONHint の name 属性で指定した名前と同じプロパティが存在するとキーが重複して出力される問題を修正しました。
- [不具合] encode/format時、「\u2028」「\u2029」が文字列中にあると、その前の文字が一部出力されない問題を修正しました。
- [不具合] 例外発生時に表示されるカラム位置がずれる問題を修正しました。
- JSONIC 1.3.0 で予定されている高速化対応のうち、小さな影響で対応できる点のみマージしたことにより decode/parseの速度が向上しました。
2012/1/17 version 1.2.9
- [不具合] マルチスレッド環境下で使用した場合に NullPointerExceptionが発生する問題を修正しました(#26993)
- [不具合] 同名のフィールドとGetter/Setterメソッドを持つ場合、フィールド側にJSONHint アノテーションを付けても無視される問題を修正しました。この問題のため name や ignore を設定した場合に 1.2.6 ~ 1.2.8 の動作は、それ以前の動作が異なる結果を返していました(#26992)
- [不具合/仕様変更] PropertyCaseStyle を指定すると、 JSONHint アノテーションにて name に指定された名前にも適用されてしまう問題を修正しました。
- 1.2.8にて修正された JSON Web Service にてデフォルトのコンテナを使って HttpServletRequest, HttpServletResponse をDIすると他スレッドのオブジェクトが取得されてしまう脆弱性について注意書きをドキュメントに追加しました。
2011/12/17 version 1.2.8
- [不具合] JSONHint にて serialized = true の指定時、単純型を受け取れない問題を修正しました(#26577)
- [不具合] STRICTモードでparse時に文字列中のシングルクォートが消えてしまう問題を修正しました。
- [不具合] JSON Web Service の設定にて encoding を設定してもパラメータが文字化けしてしまう問題を修正しました(#26567)
- [不具合] JSON Web Service にてデフォルトのコンテナを使って HttpServletRequest, HttpServletResponse をDIすると他スレッドのオブジェクトが取れてしまう場合がある問題を修正しました。
- [仕様変更] 上記、不具合への対応のため、Containerクラスからrequest/responseを削除しました。
- デフォルトの日時/数値フォーマットを指定する setDateFormat、setNumberFormat を追加しました。
- デフォルトのプロパティ名/列挙型の変換方式を指定する setPropertyStyle、setEnumStyle を追加しました。
- タイムゾーンを変更可能にしました。
- JSONHint アノテーションにプロパティ名の出力順序を指定する ordinal を追加しました。
- decode/parse 時に UnsupportedOperationException が起こった際のメッセージを追加しました。
2011/10/9 version 1.2.7
- [仕様変更] JSON Web Serviceにて、パス名と一致するファイルがある場合、実ファイルを優先する機能が実装されておりましたが、マッピング制限が緩い場合、JSPなど意図せぬソースが公開される恐れがあるため削除しました(#26032)
- [不具合] encode/format 時に Calendar、TimeZone、Charset, java.sql.Array, Document, Element を List や Map などに詰めていた場合、ClassCastException が発生していた問題を修正しました(#26256)
- [不具合] JSON Web Serviceにて、SpringContainer を使っている場合、コンポーネントが見つからなかった場合、500 Internal Server Error が戻されていましたが、404 Not Found を返すよう修正しました。
- [不具合] JSON Web Serviceにて、web.xml に Hot Deploy 対象の例外クラスを指定していた場合、ClassCastException が発生していた問題を修正しました。
- JSON Web Serviceにて、可変長引数による呼び出しをサポートしました。
- JSON Web Serviceにて、オーバーロードされたメソッドの呼び出しをサポートしました。
- jar ファイルを Maven Central Repository に登録しました。
- JavadocをJDK7で生成するようにしました。
2011/8/7 version 1.2.6
- [不具合] preformatの中でループ中に変換先クラスを変えた場合、formatに失敗する問題を修正しました(#24129)
- [不具合] プロパティ名に漢字など英数でない名称をしていた場合、formatやparseに失敗していた問題を修正しました(#25884)
- [不具合] 具体的な型が指定された総称型を継承したListやMapの型変換が正しく行われない問題を修正しました。
- JSONクラスにScriptモードでエスケープする escapeScript を追加しました。scriptタグ中に変数を出力する場合はencodeではなく、こちらを用いると便利です。
- 1.2.5まではjsonic-1.2.6.jarにソースコードを含めていましたが、jsonic-1.2.6-sources.jarに分離しました。
2010/12/19 version 1.2.5
- java.util.UUIDの双方向変換に対応しました。
- encode/formatの速度が50%ほど改善しました。
- decode/parseの速度が25%ほど改善しました。
- decode/parseにて文字列をDate型に変換する際、存在しない日付でも受け入れていた問題を修正しました(詳しくは、SimpleDateFormatのsetLenientを参照してください)。
- JavaScriptのevalでエラーとなるU+2028、U+2029をエスケープするよう修正しました。
- JSON Web Serviceにて、warファイルのまま実行すると配置済みjsonファイルを返さない問題を修正しました。
2010/10/19 version 1.2.4
- [不具合] Google App Engine で java.net.InetAdress が利用できないためエラーが発生する問題を解消しました。
- [不具合] Google App Engine で ClassLoader.getSystemClassLoader() が例外を発生するため、実行に失敗する問題を解消しました。
- [不具合] ベータ1 ~ 2で発生していた様々な不具合を修正しました(#23436、#23472)
2010/10/05 version 1.2.3
- [不具合] JSON.encode(Object obj, OutputStream out)などでflushされず文字が出力されないことがある(#23342)
2010/10/02 version 1.2.2
- [不具合] JSONのデコードにて型変換の際、JSON string あるいは true/false から Map あるいは Object 型に変換する場合に hint 句がないと NullPointerException が発生する問題を修正しました(#23055)
- [不具合] クラスパスにBeanUtilsが含まれていない場合やJava5を使用している場合に、Class.forNameの失敗によりエンコードやコンバートが著しく遅くなる問題を修正しました(#23146)
- encode/parseの処理順や文字列のエスケープ方式を見直したことにより処理速度がやや向上しました。
2010/09/04 version 1.2.1
- [不具合] JSONのエンコード時に改行やタブなど記号が割り当てられていない制御文字がエスケープされていなかった問題を修正しました(AS3版も同様です)。
- [不具合] クラスキャッシュが残っているため、Slim3などでホットリローディングに失敗する問題にある程度対応しました。
- [不具合] JSON Web ServiceにてServletContext#getRealPathがnullを返すとき、NullPointerException が発生していた問題を修正しました(#22982)
- [仕様変更] Objectを戻すdecode/parseメソッドの戻り値をタイプパラメータ化し、キャストを不要にしました。
- JSONのparseやformat時の挙動を変えるモード変更機能を追加し、厳密なチェックを行う STRICT モードとインラインでのJavaScript記述に便利な SCRIPT モードを追加しました。
- JSONの検証を行うために JSON.validate メソッドを追加しました。
- やや高速化を図りました。
2010/04/04 version 1.2.0
- [不具合] JSONクラスにて親クラスの型パラメータ変数を持つプロパティの取り扱いに失敗していた問題にある程度対応しました。
- [不具合] JSONクラスおよびWebサービスAPIにてメソッドを探す際、合成メソッドやブリッジメソッドも対象に含めてしまっていた問題に対応しました(バージョン1.0.5、1.1.2でも修正/リリース済みです)[#19766]
- [不具合] JSONのデコード時に指数部が2桁以上ある数値の解析に失敗していた点を修正しました(JSONIC AS3およびバージョン1.0.5、1.1.3でも修正/リリース済みです)。[#20537]
- [不具合] ストリームから読み込む際、JSONException#getErrorOffset()にて正しくないオフセット値が戻される不具合を修正しました。
- [不具合] WebサービスAPIにて、パス変数に「.」を指定できるため、意図せぬクラスの呼び出しが可能になる場合があったため、デフォルトのパターンを
[^/().]+
に変更しました。 - JSONクラスがjava.beans.Introspectorに依存しているため、Androidで実行できない問題を解消しました(他にもあれば教えてください)。
- JSONHintアノテーションにて、formatの日付書式でISO8601形式のタイムゾーンを扱うZZを指定できるようにしました。
- JSONHintアノテーションにanonymを追加し、単純値型からMapや複合型への変換を可能にしました。
- JSON encoder/decoderにて1処理中に限りメソッド情報をキャッシュするように変更しました。
- JSON.encode(or format)およびJSON.decode(or parse)にて
JSONHint(type=String.class)
を指定した場合にはtoString
およびStringを引数に取るコンストラクタを使い文字列の相互変換を行うようにしました。 - JSON.encode(or format)およびJSON.decode(or parse)にて
JSONHint(type=Serializable.class)
を指定した場合にはObjectInputStream/ObjectOutputStream
を使いシリアライズによる変換を行うようにしました。 - JSON.encode(or format)およびJSON.decode(or parse)にて
java.sql.RowId
の相互変換に対応しました。 - JSON.encode(or format)にて
java.sql.Array, java.sql.Struct
のJSON化に対応しました(逆方向には対応していません。相互変換が必要な場合はJSONHint(type=Serializable.class)
を指定しシリアル化してください)。 - AS3版JSONICにて、JSON.encode(or format)時にmine変数を無視するように変更しました。
- JSON-RPC2.0をサポートするRPCServletと強化されたREST操作を可能にするRESTServletを新設しました。WebServiceServletは互換性のため残されていますが非推奨となり機能追加もほとんど行われていません(Version 1.1互換)。RPCServletやRESTServletとWebServiceServletの各モードとの違いは、JSONIC 1.1 RPCモードからの変更点、JSONIC 1.1 RESTモードからの変更点を参照してください。
- WebサービスAPIにて各種DI Container用Containerクラスのパッケージを
net.arnx.jsonic.web
からnet.arnx.jsonic.web.extension
に変更しました。ご注意ください(ただし、WebServiceServletに限り互換性維持のため、指定されたパッケージ名が旧来のものでも動作します)。 - WebサービスAPIにてContainerにリクエストの最初と最後に呼ばれるstartメソッドとendメソッドを追加しました(WebServiceServletでも利用できます)。
- sampleに付属のjquery.jsのバージョンを1.4.2に更新しました。