キー名、値の型、ツリー構造が不明(定義を知らされてない)JSON を Google GSON でとりあえず認識させる。
このような要件定義はあり得ないけど、とにかく読み込ませてできる解析をする。
gson の JsonParser から取得する JsonElement を recursively なロジックで処理する。
このロジックは、JsonDeserializer 実装を作るときに参考になると思って作ることにした。
ツリーを表す全てのキーと値の Map への変換、ツリーを表すキーで検索ハンドラを実行するものだ。
import java.io.Reader; import java.io.StringReader; import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BiConsumer; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.google.gson.JsonPrimitive; /** * JsonGenericParser. */ public final class JsonGenericParser{ /** 数値解釈を指定する列挙変数. provide()メソッド引数で指定してインスタンス生成する * デフォルトコンストラクタによるインスタンス生成では、INTEGER が使用される */ public enum NumberParse{ INTEGER , LONG , DOUBLE , BIGDECIMAL , NUMBER , SHORT , FLOAT , BYTE , CHARACTER }; private NumberParse numberparse; /** コンストラクタ. */ public JsonGenericParser(){ numberparse = NumberParse.INTEGER; } private JsonGenericParser(NumberParse numberParse){ numberparse = numberParse; } /** * 数値解釈指定のインスタンス生成. * @param numberParse NumberParse * @return JsonGenericParser */ public static JsonGenericParser provide(NumberParse numberParse){ return new JsonGenericParser(numberParse); } /** * JSONキーマップ取得. キーは"." 区切りのJSONキー、配列は[n]で要素指定 * @param json 解析対象のJSON文字列 * @return Map<String, Object> */ public Map<String, Object> toMap(String json){ return toMap(new StringReader(json)); } /** * JSONキーマップ取得. キーは"." 区切りのJSONキー、配列は[n]で要素指定 * @param reader 解析対象のJSON読込み Reader * @return Map<String, Object> */ public Map<String, Object> toMap(Reader reader){ Map<String, Object> map = new HashMap<String, Object>(); JsonElement je = new JsonParser().parse(reader); if (je.isJsonObject()){ je.getAsJsonObject().entrySet().forEach(entry-> map.putAll(maped(entry.getKey(), entry.getValue(), map))); } return map; } private Map<String, Object> maped(String key, JsonElement je, Map<String, Object> map){ if (je.isJsonNull()){ map.put(key, null); }else if(je.isJsonObject()){ je.getAsJsonObject().entrySet().forEach(entry-> map.putAll(maped(key + "." + entry.getKey(), entry.getValue(), map))); }else if(je.isJsonArray()){ JsonArray jary = je.getAsJsonArray(); map.put(key, jary); int i = 0; for(JsonElement e:jary){ map.putAll(maped(key + "[" + i + "]", e, map)); i++; } }else if(je.isJsonPrimitive()){ JsonPrimitive p = je.getAsJsonPrimitive(); if (p.isNumber()){ map.put(key, p.getAsDouble()); if (numberparse.equals(NumberParse.INTEGER)){ map.put(key, p.getAsInt()); }else if(numberparse.equals(NumberParse.LONG)){ map.put(key, p.getAsLong()); }else if(numberparse.equals(NumberParse.DOUBLE)){ map.put(key, p.getAsDouble()); }else if(numberparse.equals(NumberParse.BIGDECIMAL)){ map.put(key, p.getAsBigDecimal()); }else if(numberparse.equals(NumberParse.NUMBER)){ map.put(key, p.getAsNumber()); }else if(numberparse.equals(NumberParse.SHORT)){ map.put(key, p.getAsShort()); }else if(numberparse.equals(NumberParse.FLOAT)){ map.put(key, p.getAsFloat()); }else if(numberparse.equals(NumberParse.BYTE)){ map.put(key, p.getAsByte()); }else if(numberparse.equals(NumberParse.CHARACTER)){ map.put(key, p.getAsCharacter()); } }else if(p.isString()){ map.put(key, p.getAsString()); }else if(p.isBoolean()){ map.put(key, p.getAsBoolean()); }else if(p.isJsonNull()){ map.put(key, null); } } return map; } private Map<String, BiConsumer<String, Object>> hmap = new HashMap<>(); /** * 検索ハンドラ登録. * @param key "." 区切りのJSONキー、配列は[n]で要素指定、 * @param biconsumer BiConsumer<String, Object> String=key, Object=JSONキーが指すObject * @return JsonGenericParser */ public JsonGenericParser addHandler(String key, BiConsumer<String, Object> biconsumer){ hmap.put(key, biconsumer); return this; } /** * BiConsumer で指定する JSONキーで限定する検索読出しハンドラによる読込実行. * @param json 解析対象のJSON文字列 */ public void search(String json){ search(new StringReader(json)); } /** * BiConsumer で指定する JSONキーで限定する検索読出しハンドラによる読込実行. * (注意)Reader 指定は1回実行したら次回は異なる Reader でなければならない。 * @param reader reader 解析対象のJSON読込み Reader */ public void search(Reader reader){ JsonElement je = new JsonParser().parse(reader); System.out.println(je.isJsonObject()); JsonObject jo = je.getAsJsonObject(); for(Map.Entry<String, JsonElement> entry : jo.entrySet()){ search(entry.getKey(), entry.getValue()); } } private void search(String key, JsonElement je){ if (je.isJsonNull()){ hmap.entrySet().stream().filter(e->key.equals(e.getKey())) .findFirst().ifPresent(e->e.getValue().accept(e.getKey(), null)); }else if(je.isJsonObject()){ je.getAsJsonObject().entrySet().forEach(entry->search(key+"."+entry.getKey(), entry.getValue())); }else if(je.isJsonArray()){ JsonArray jary = je.getAsJsonArray(); hmap.entrySet().stream().filter(he->he.getKey().equals(key)) .findFirst().ifPresent(he->he.getValue().accept(he.getKey(), jary)); AtomicInteger i = new AtomicInteger(0); for(JsonElement e:jary){ String k = key + "[" + i.getAndIncrement() + "]"; hmap.entrySet().stream().filter(he->he.getKey().equals(k) && !e.isJsonPrimitive()).findFirst() .ifPresent(he->he.getValue().accept(he.getKey(), e)); search(k, e); } }else if(je.isJsonPrimitive()){ JsonPrimitive p = je.getAsJsonPrimitive(); if (p.isNumber()){ if (numberparse.equals(NumberParse.INTEGER)){ hmap.entrySet().stream().filter(e->key.equals(e.getKey())) .findFirst().ifPresent(e->e.getValue().accept(e.getKey(), p.getAsInt())); }else if(numberparse.equals(NumberParse.LONG)){ hmap.entrySet().stream().filter(e->key.equals(e.getKey())) .findFirst().ifPresent(e->e.getValue().accept(e.getKey(), p.getAsLong())); }else if(numberparse.equals(NumberParse.DOUBLE)){ hmap.entrySet().stream().filter(e->key.equals(e.getKey())) .findFirst().ifPresent(e->e.getValue().accept(e.getKey(), p.getAsDouble())); }else if(numberparse.equals(NumberParse.BIGDECIMAL)){ hmap.entrySet().stream().filter(e->key.equals(e.getKey())) .findFirst().ifPresent(e->e.getValue().accept(e.getKey(), p.getAsBigDecimal())); }else if(numberparse.equals(NumberParse.NUMBER)){ hmap.entrySet().stream().filter(e->key.equals(e.getKey())) .findFirst().ifPresent(e->e.getValue().accept(e.getKey(), p.getAsNumber())); }else if(numberparse.equals(NumberParse.SHORT)){ hmap.entrySet().stream().filter(e->key.equals(e.getKey())) .findFirst().ifPresent(e->e.getValue().accept(e.getKey(), p.getAsShort())); }else if(numberparse.equals(NumberParse.FLOAT)){ hmap.entrySet().stream().filter(e->key.equals(e.getKey())) .findFirst().ifPresent(e->e.getValue().accept(e.getKey(), p.getAsFloat())); }else if(numberparse.equals(NumberParse.BYTE)){ hmap.entrySet().stream().filter(e->key.equals(e.getKey())) .findFirst().ifPresent(e->e.getValue().accept(e.getKey(), p.getAsByte())); }else if(numberparse.equals(NumberParse.CHARACTER)){ hmap.entrySet().stream().filter(e->key.equals(e.getKey())) .findFirst().ifPresent(e->e.getValue().accept(e.getKey(), p.getAsCharacter())); } }else if(p.isString()){ hmap.entrySet().stream().filter(e->key.equals(e.getKey())) .findFirst().ifPresent(e->e.getValue().accept(e.getKey(), p.getAsString())); }else if(p.isBoolean()){ hmap.entrySet().stream().filter(e->key.equals(e.getKey())) .findFirst().ifPresent(e->e.getValue().accept(e.getKey(), p.getAsBoolean())); }else if(p.isJsonNull()){ hmap.entrySet().stream().filter(e->key.equals(e.getKey())) .findFirst().ifPresent(e->e.getValue().accept(e.getKey(), null)); } }else if(je.isJsonObject()){ je.getAsJsonObject().entrySet().forEach(entry->search(key+"."+entry.getKey(), entry.getValue())); }else if(je.isJsonNull()){ hmap.entrySet().stream().filter(e->key.equals(e.getKey())) .findFirst().ifPresent(e->e.getValue().accept(e.getKey(), null)); } } }
使い方は、例えば、以下のようJSONスクリプトの時、
{ "store": { "book": [ { "category": "reference", "author": "Agel", "title": "Japan-Index", "price": 220 }, { "category": "fiction", "author": "Lamda", "title": "あいう", "price": 330 }, : ] }, "child":[ {"alpha":[101, 102, 103] }, {"beta":[201, 202, 203] } ] }
Mapの生成、、
InputStream in = new FileInputStream("sample.json"); Reader reader = new InputStreamReader(in, "UTF-8"); JsonGenericParser genParser = new JsonGenericParser(); // あるいは、 JsonGenericParser genParser = JsonGenericParser.provide(NumberParse.DOUBLE); Map<String, Object> map = genParser.toMap(reader); map.entrySet().stream().sorted(Map.Entry.comparingByKey()).forEach(e->{ System.out.println("key = " + e.getKey() + " : value = " + e.getValue() ); });
検索ハンドラの実行、、
new JsonGenericParser().addHandler("store.book[3].price", (k, v)->{ System.out.println("key = " + k + " : value = " + v + " class : " + v.getClass().getName() ); }).addHandler("child[1].beta[1]", (k, v)->{ System.out.println("key = " + k + " : value = " + v + " class : " + v.getClass().getName() ); }).search(reader);
JSON 項目全てを Java のクラスオブジェクトに変換する必要性がなくて部分的に解析したい時、
利用できるかもしれない。
ある任意のキーは確定だが、それ以外のキーが、何がくるか解らない時は有効だと思う。