JSON のキーを指定して対象の値を取得

JSON → 任意のクラスオブジェクトに変換させることなく、
JSON のキーを指定して対象の値、valueを取得するものを汎用的なメソッドを用意しようと考えると
先日紹介した
oboe2uran.hatenablog.com
を超える機能のメソッドを作るのは難しい。
紹介した GitHub - json-path/JsonPath: Java JsonPath implementation に機能が追い付かなくてもいいから
Google GSON 利用で書いてみた。

import java.io.Reader;
import java.io.StringReader;
import java.util.Arrays;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
/**
 * JSonValue
 */
public final class JSonValue{
   private Reader reader;
   private Pattern aryPattern = Pattern.compile("\\[\\d+\\]");
   private Pattern decPattern = Pattern.compile("\\d+");

   public JSonValue(Reader reader){
      this.reader = reader;
   }
   public JSonValue(String json){
      this.reader = new StringReader(json);
   }
   /**
    * JSONキー指定 JsonElement オブジェクト取得
    * @param path JSON Key path  表現は "."区切り、配列は [Index]で表現
    * @return JsonElement
    * path にマッチしない場合は nullが返る
    */
   public JsonElement get(String path){
      if (path==null) return null;
      if (path.replaceAll(" ", "").length()==0) return null;
      return get(path, new JsonParser().parse(reader));
   }
   private JsonElement get(String path, JsonElement je){
      String[] pary = path.split("\\.");
      String key = pary[0].replaceAll("\\[\\d+\\]", "");
      if (je.isJsonNull()){
         return null;
      }else if(je.isJsonObject()){
         for(Entry<String, JsonElement> entry:je.getAsJsonObject().entrySet()){
            if (entry.getKey().equals(key)){
               if (entry.getValue().isJsonArray() && aryPattern.matcher(pary[0]).find()){
                  int i = getIndex(pary[0]);
                  JsonArray ja = entry.getValue().getAsJsonArray();
                  if (i < ja.size()){
                     if (pary.length > 1) {
                        return get(Arrays.stream(pary).skip(1)
                                 .collect(Collectors.joining(".")), ja.get(i));
                     }
                     return ja.get(i);
                  }else{
                     return null;
                  }
               }else{
                  if (pary.length > 1) {
                     return get(Arrays.stream(pary).skip(1)
                              .collect(Collectors.joining(".")), entry.getValue());
                  }
                  return entry.getValue();
               }
            }
         }
      }else if(je.isJsonPrimitive()){
         return je;
      }
      return null;
   }
   private int getIndex(String s){
      Matcher m = aryPattern.matcher(s);
      if (m.find()){
         Matcher md = decPattern.matcher(m.group());
         if (md.find()){
            return Integer.parseInt(md.group());
         }
      }
      return -1;
   }
}

コンストラクタ Reader を指定させるのは、可能ならば、BufferedReader で Reader 読み出し位置を mark して
reset() 実行で何回も Reader をそのまま使用するためだが、現実的ではない。

使用例

try(InputStream in = new FileInputStream("sample2.json");
   Reader reader = new InputStreamReader(in, "UTF-8")){
   BufferedReader br = new BufferedReader(new InputStreamReader(in, "UTF-8"));

   JSonValue jsv = new JSonValue(br);

   JsonElement je = jsv.get("store.book[1].title");

}catch(Exception ex){

}

JsonElement を取得できれば、JsonPrimitive JsonArray JsonObject にcastして使用できる