ネスト深い可能性のある Map<String, Object> を探索する。

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