Simple JSON encoder/decoder written in java
2018/7/1 JSONIC は、リポジトリを GitHub に移動するとともに今後機能強化が行われることがないメンテナンスモードに移行します。機能、パフォーマンス共に優れた jackson への移行をおすすめいたします。
JSONICは、Java用のシンプルかつ高機能なJSONエンコーダー/デコーダーライブラリです。
Java用のJSONライブラリはすでに多数存在しますが、JSONICはRFC 7159に従った正式な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のドキュメントを御覧ください。
Maven Central Repository から取得できます。
<dependency>
<groupId>net.arnx</groupId>
<artifactId>jsonic</artifactId>
<version>1.3.10</version>
</dependency>
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, Path, Type, Member, Charset, UUID, java.timeの各クラス | |
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 | string (デフォルトは名前で文字列化。setEnumStyle にて動作の変更が可能)number ( setEnumStyle に null を指定すると Enum.ordinal により変換) |
Optional型 | isPresent() が false を返す時 null、その他の時、保持値 |
boolean, Boolean | true/false |
null | null |
なお、JSONはobjectかarrayで始まる必要があるため、直接、intやStringのインスタンスをencodeメソッドの引数に指定した場合エラーとなります。
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で変換) | |
java.time の各クラス | 指定された型 | |
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, Path | 指定された型(文字列をコンストラクタの引数に指定し変換) | |
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()に従い変換) | |
Optional型 | 保持値(nullの場合 empty()の値が設定されます) | |
null | なし, Object | null |
byte, short, int, long, float, double | 0 | |
boolean | false | |
char | '\u0000' |
JSONICでは、フレームワークなどでの利用を想定していくつかの便利な機能を用意しています。
JSONICは、フレームワークでの利用を考慮しインスタンスを生成したり、継承して拡張することができるように設計してあります。 なお、インスタンスを生成して利用する場合は、encode/decodeメソッドの代わりにformat/parseメソッドを利用します。
// インスタンスを生成します
JSON json = new JSON();
// POJOをJSONに変換します(encodeと同じ機能)
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の引数にはJava 5.0で追加された総称型も指定できます。しかし、総称型はコンパイル時に削除されてしまうため、decode/parseメソッドの引数として直接的に指定することができません。総称型を使う場合は TypeReference を使って型を埋めこむか、ルート要素をJSON objectにして対応するクラス定義の中で総称型を使います。
class Config() {
// TypeReference を使う
public static List<RowData> load(Reader reader) throws IOException {
return JSON.decode(reader, new TypeReference<List<RowData>>() {});
}
// 型を定義してその中で総称型を利用する
public static Config load(Reader reader) throws IOException {
return JSON.decode(reader, Config.class);
}
public static class RowData {
public String id;
public String name;
}
public List<RowData> rows;
}
多少トリッキーですが、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());
}
JSONICでは、encode の第二引数に true を渡すか、setPrettyPrint() メソッドを使うことでインデントや改行などが付いた可動性の高いJSONを出力することができます。
// encode の第二引数に true を設定
JSON.encode(obj, true);
// setPrettyPrint に true を設定
JSON json = new JSON();
json.setPrettyPrint(true);
json.format(obj);
デフォルトでは、初期インデントがなく、タブを使用してインデントを出力しますが、setInitialIndent() メソッドや setIndentText() メソッドを使うことでインデントの書式を変更することができます(これらの設定は、setPrettyPrint に true を設定した場合のみ有効となります)。
// 初期インデントを 1、インデントとして空白4文字を使用
JSON json = new JSON();
json.setPrettyPrint(true);
json.setInitialIndent(1);
json.setIndentText(" ");
json.format(obj);
JSONICはポステルの法則(送信するものに関しては厳密に、受信するものに関しては寛容に)に従い、デフォルトでは、妥当でないJSONであっても読み込みが可能なTRADITIONALモードで動作するように作成されています。 RFC 4627に規定された内容との相違点は以下の通りです。
- [デコード] Cスタイルの複数行コメント(/**/)、C++スタイルの行コメント(//)をコメントとして認識します。
- [デコード] ルート要素がobjectの場合、一番外側の'{'と'}'を省略することができます(入力文字列が空白文字列やコメントのみの場合も空のobjectとみなされます)。
- [デコード] シングルクォートで囲まれた文字列やJavaリテラルを文字列として認識します。
- [デコード] 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に準じた妥当性チェックを行なうよう変更することができます。
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"));
JSONは、可搬性あるデータ連携フォーマットとしてだけでなく、HTML内に書かれるJavaScript内にJavaオブジェクトの内容をインライン出力するためにも便利です。 JSONICでは、このような場合に使いやすいようSCRIPTモードを用意しています。 RFC 4627に規定された内容との相違点は以下の通りです。
- [エンコード] 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として認識します。
- [デコード] 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(...);
JSONICのencode/format は、Javaオブジェクトを構築後、JSONに出力するため、大規模な JSON を出力する場合、 メモリを大量に消費してしまう問題がありました。 version 1.3.1 では、この問題に対応するため、JSONの encode とStringBuffer 的な順次出力を組み合わせた JSONWriter クラスを提供します。
// JSONWriter を取得
Writer out = new OutputStreamWriter(new FileOutputStream(...));
JSONWriter writer = new JSON().getWriter(out);
// JSON object の出力
writer.beginObject();
// 名前と値の出力
writer.name("string").value("value");
// 値の出力は、decode と同様に任意のオブジェクトを指定可能です。
Object o = new Object() {
public DataType type = DataType.ANY;
public String text = "あああ";
public int index = 100;
};
writer.name("object").value(o);
// JSON array の出力
writer.name("array");
writer.beginArray();
writer.value(1);
writer.value(2);
writer.value(3);
writer.endArray();
writer.endObject();
// JSONの完成前に途中で出力する場合は、flushを呼ぶ必要があります(通常は不要です)。
writer.flush();
// クローズは自動では行われません。明示的にクローズ処理を行ってください。
out.close();
JSONICのdecode/parse は、通常 XMLでの DOM(Document Object Model)に当たる API となっており、JSON を読み取りJavaオブジェクトモデルを構築しますが、この方式で大規模なJSONファイルを扱うと、メモリを大量に消費してしまい OutOfMemoryError が発生してしまいます。
version 1.3 では、この問題に対応するために、StAX(Streaming API for XML) に相当する JSONReader クラスを提供しています。JSONReader は、readerメソッドを介して取得します(reader メソッドの ignoreWhitespace を false にすることで、コメントや空白も取得できます)。
// JSONReader を取得
JSONReader reader = new JSON().getReader("[1, 2, 3, 4, 5]");
JSONEventType type;
// next で次のトークンを読み取り
while ((type = reader.next()) != null) {
switch (type) {
case START_OBJECT:
System.out.println("{");
break;
case END_OBJECT:
System.out.println("}");
break;
case START_ARRAY:
System.out.println("[");
break;
case END_ARRAY:
System.out.println("]");
break;
case NAME:
System.out.print(reader.getString() + ": ");
break;
case STRING:
System.out.println(reader.getString());
break;
case NUMBER:
System.out.println(reader.getNumber());
break;
case BOOLEAN:
System.out.println(reader.getBoolean());
break;
case NULL:
System.out.println("null");
break;
}
}
// ignoreWhitespace を false にするとコメントやスペースも取得可能
JSONReader reader = new JSON().getReader("[1, 2, 3, 4, 5]", false);
while ((type = reader.next()) != null) {
switch (type) {
case WHITESPACE:
System.out.println(reader.getString());
break;
case COMMENT:
System.out.println(reader.getString());
break;
}
}
JSONReader には、単独の値を取得するだけでなく、現在位置以下のツリーをひとかたまりで取得し Java オブジェクトに変換する getValue メソッドも用意されています。
// オブジェクトの配列を処理する
JSONReader reader = new JSON().getReader("[{...}, {...}, {...}, {...}, {...}]");
List<FooBean> list = new ArrayList<FooBean>();
JSONEventType type;
while ((type = reader.next()) != null) {
if (type == JSONEventType.START_OBJECT) {
// 現在位置のオブジェクトを取得して FooBean に変換
list.add(reader.getValue(FooBean.class));
}
}
JSONReader の JSON 解釈は、設定された JSON.Mode に準じます1が、複数の連続した JSON も処理できるようになっていますので、Twitter API で返されるような、改行で区切られた JSON Streaming を扱うことができます。
// オブジェクトの配列を処理する
JSONReader reader = new JSON().getReader("{...}\n{...}\n{...}\n{...}\n{...}");
JSONEventType type;
while ((type = reader.next()) != null) {
if (type == JSONEventType.START_OBJECT) {
// 現在位置のオブジェクトを取得して Tweet に変換
System.out.println(reader.getValue(Tweet.class));
}
}
日付型や数値型は、デフォルトではJSON numberとして出力されますが、JSONIC 1.2.8以降ではsetDateFormat/setNumberFormat を指定することでデフォルトの日時/数値書式を設定できます。フォーマットの書式は Number型の場合 java.text.DecimalFormat、Date型の場合 java.text.SimpleDateFormat2、 Java8 Date/Time API の場合 java.time.format.DateTimeFormatter に従ってフォーマットされます。書式は 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;
});
プロパティ名は、デフォルトではプロパティ名を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();
}
}
}
JSONICは、encode/format時に自分自身を戻すようなフィールドやプロパティ、配列を無視することで再帰による無限ループが発生することを防ぎます。 しかし、そのインスタンスにとって孫に当たるクラスが自分のインスタンスを返す場合にも再帰が発生してしまいます。JSONICでは、このような場合へ対処するため 単純に入れ子の深さに制限を設けています。
なお、最大深度の設定はdecode/parse時にも有効ですので深すぎるデータの取得を避けることも可能となります。
この最大深度は、デフォルトでは32に設定されていますが変更することも可能です。
// 5階層以下の情報は取得しない
json.setMaxDepth(5);
JSONICでは、format時に値がnullになっているJSON objectのメンバの出力を抑制できます。初期値はfalseです。余計なメンバが大量に出力されてしまう、プロパティの初期値を優先したいなどの場合に有効です。
// null値の出力を抑制します。
json.setSuppressNull(true);
なお、Version 1.2 系では、parse 時や Map の format に対しても null 値が抑制されていましたが、不適切な場合が多いため1.3系では抑制しないよう変更されました。
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"],
]
]
場合によってデフォルトの変換方式では不十分な場合があります。JSONICでは、メソッドやフィールドにJSONHintアノテーションを付加することで、 動作を部分的に制御することが可能です。
設定できる属性は次の通りです。
属性名 | 値型 | 説明 |
---|---|---|
name | String | 出力/代入するキー名を変更します |
format | String | 対象の型がNumberあるいはDate型の場合は、指定したフォーマットに従って変換します。 フォーマットの書式はそれぞれjava.text.DecimalFormat、java.text.SimpleDateFormatを参照してください[^9]。 |
type | Class | parse時に指定した型のインスタンスを生成します(対象の型のサブクラスを指定する必要があります)。 |
ignore | boolean | 出力/代入対象から除外します |
serialized | boolean | 値がJSONであるものとして扱います。デフォルトはfalseです。 Format時はtoString()の値をそのまま出力[^10] 、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アノテーションの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アノテーションのtype属性にjava.io.Serializableを指定することで、データをObjectInputStream/ObjectOutputStreamによりシリアル化されたバイト列データとして取り扱うことができます(バイト列はBase64でエンコードされJSON stringとして出力されます)。
public class TestBean {
@JSONHint(type=Serializable.class)
public SerializableBean sb;
}
この機能を使うことで、JSON化が困難なオブジェクトもJSON-RPCなどでやり取りすることが可能となります。
-
Q. RESTServletでHTTP GETを使うと日本語が文字化けします
-
A. 入力文字エンコーディングの問題です。特にApache Tomcat5以降は仕様を厳密に解釈した結果、GETがsetCharacterEncodingを無視するという問題がありますのでuseBodyEncodingForURIを設定し回避する必要があります(JSONIC 1.1 ではGETパラメータを独自に解析していたため、この問題は発生していませんでした)。
-
Q. 大量データを読み込むとOutOfMemoryErrorで落ちます。
-
A. JSON.decode() や JSON.parse() は、JSON文字列をオブジェクトツリーとして生成するため、大量のデータを取り扱うとメモリを大量に消費してしまいます。そのような場合には、JSON.getReader() メソッドを使うことでデータをひとつずつ読み込み処理することができます。
-
Q. Resin サーバで RESTServletやRPCServletが動作しません。
-
A. Resin サーバでは、web.xml中にある ${...} を変数として扱うため、${...} と書かないといけないようです。
JSONICは、Apache License, Version 2.0下で配布します。
自分のライブラリへの組み込んでいただいたり、その際にパッケージ名の変更や処理の変更など行っていただいて構いません。保障はありませんが、ライセンスの範囲内でご自由にお使いください。