Map<String, Object> の値が更に、 Map<String, Object> になっていて深い木構造であったり、
List が存在したり、List で括られたMap<String, Object> があったりする場合に、
Key のパスでオブジェクトを参照するコードを書くのは辛い物がある。
まず、頭に浮かべるべきコードは再帰呼出しをするメソッドで、
private <T> T deep(Iterator<String> it, Object obj){ if (it.hasNext()){ String key = it.next(); Object o = ((Map<String, Object>)obj).get(key); return deep(it, o); } return obj==null ? null : (T)obj; }
でも、これだけで、List や、Map の入れ子を対応できるわけないのだが、
再帰呼び出すメソッドを考えていく手順として、まずは思い浮かべることが重要な思考パターンだ。
完成させるコードは、、、
// クラス内変数 private Map<String, Object> map; private Pattern aryptn; private Pattern idexptn; // コンストラクタで、map を受け取り、 // 配列指定文字列パターンを用意する aryptn = Pattern.compile("^.+\\[\\d+\\]$"); idexptn = Pattern.compile("\\[\\d+\\]$");
と、準備できたら、、
Map を探索するメソッド
public <T> T search(String path, Character separator){ if (".-+*^$?{}[]()".contains(separator.toString())) { return search(path.split("\\"+ separator.toString())); } return search(path.split(separator.toString())); } public <T> T search(String...keys){ Iterator<String> it = Arrays.asList(keys).iterator(); String key = it.next(); if (aryptn.matcher(key).matches()){ Object o = map.get(key.replaceAll("\\[\\d+\\]$", "")); if (!(o instanceof List)){ throw new IllegalArgumentException(key + " get Obejct is not List"); } Matcher matcher = idexptn.matcher(key); matcher.find(); int i = Integer.valueOf(matcher.group().replaceFirst("\\[", "").replaceFirst("\\]", "")); return deep(it, ((List)o).get(i)); } if (!map.containsKey(key)) { throw new IllegalArgumentException(key + " is not contains Key"); } Object o = map.get(key); return deep(it, o); } @SuppressWarnings("unchecked") private <T> T deep(Iterator<String> it, Object obj){ if (it.hasNext()){ String key = it.next(); if (aryptn.matcher(key).matches()){ Object o = ((Map<String, Object>)obj).get(key.replaceAll("\\[\\d+\\]$", "")); if (!(o instanceof List)){ throw new IllegalArgumentException(key + " get Obejct is not List"); } Matcher matcher = idexptn.matcher(key); matcher.find(); int i = Integer.valueOf(matcher.group().replaceFirst("\\[", "").replaceFirst("\\]", "")); return deep(it, ((List<Object>)o).get(i)); } if (!(obj instanceof Map)){ throw new IllegalArgumentException(key + " is not Path key"); } Object o = ((Map<String, Object>)obj).get(key); return deep(it, o); } return obj==null ? null : (T)obj; }
この処理の仕様は、、
・Key の配列、または、パスで取得したい対象を指定する。
・取得対象が、配列であれば、インデックス [ n ] で指定する。
例) "a[2]" → キー "a" が指すリストのインデックス=2を取得
メソッド使用例
boolean b = search("a/b/c", '/'); // "a" → "b"→"c" が指すオブジェクトを boolean として取得する String name = search("x.y.z[3]", '.’); // "x" → "y"→"z" が指す配列4番目を String として取得する
のように記述する。
何の為にこのようなことが必要なのか?!
JSON をMap<String, Object> に変換した時、Map の閲覧コードを書くのが面倒くさいからだ。
そこで、↑をまとめたのが、以下、StringMapObject なるものです。
整数値は、int ではなく、long または Long 、小数点があれば、double または Double 、
Google gson がJSONからMapに変換したものを簡単に閲覧するのが目的です。
yipuran-gsonhelper/StringMapObject.java at master · yipuran/yipuran-gsonhelper · GitHub
使い方 Wiki
StringMapObject · yipuran/yipuran-gsonhelper Wiki · GitHub