JavaのWebアプリケーション開発フレームワークによる、Webサイト開発の顛末記です。

EclipseのMavenを使った、Spring-MVC、Thymeleaf、MyBatis 等のプログラミングテクニックを、
備忘録的に記録しています。実際に動くソースコードを多用して説明していますので、
これからEclipseや、Spring-MVCを始めたいと思っている人にとって、少しでも参考になれば幸いです。
Spring-MVCの散歩道 > 応用の森(JSON編) > JSONデータ構造から動的クラス生成

package jp.dip.arimodoki.common;

import java.io.ByteArrayInputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
import com.google.gson.stream.JsonReader;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.CtMethod;
import javassist.CtNewMethod;
import javassist.Loader;
import javassist.NotFoundException;

/**
 * JSONとJavaオブジェクトのコンバートを行うクラスです。
 * 任意のJSONからJavaオブジェクトへのデシリアライズ(整列化)
 * 任意のJavaオブジェクトからJSONへのシリアライズ(直列化)
 * を行います。
 * gson-x.x.x.jar 必須
 * Javassist.jar 必須
 *
 * 使い方)
 *  import jp.co.xx.yy.zz.JsonConvert
 *
 *  コンバートクラスのインスタンスを作成します
 *  JsonConvert jconv = new JsonConvert();
 *
 *  ***シリアライズ(直列化)処理 Javaオブジェクト⇒ JSON への変換***
 *  とあるインスタンス化されたJavaオブジェクトがると仮定します。
 *  Class AnyCls {
 *          public int id = 100;
 *          public String name = "ABCDEFG";
 *  }
 *  上のJavaオブジェクトをJSONにシリアライズします。
 *  AnyCls cls = new AnyCls();
 *  String json = jconv.Serialize(cls);     //シリアライズ実行
 *  処理結果
 *  json == "{'id' : 100, 'name' : 'ABCDEFG'}  となります。
 *
 *
 *  ***デシリアライズ(整列化)処理 JSON ⇒ Javaオブジェクトへの変換***
 *  ①JSON構造に対応するクラスがプログラムで定義されている場合の処理
 *          上のシリアライズ処理で行った逆パターンを行います
 *          String json = "{'id' : 200, 'name' : 'XYZ'};
 *
 *          AnyCls cls = new AnyCls();          //クラス構造は前述の通り
 *          cls = jconv.Deserialize(json, cls);     //デシリアライズ実行
 *              //こんなやり方もある
 *              ※) AnyCls cls =(AnyCls)jconv.Deserialize(json, AnyCls.class);      //デシリアライズ実行
 *
 *          cls.id に 200, cls.name に "'XYZ"  がセットされます。
 *
 *  ②JSON構造に対応するクラスがプログラムで定義されていない場合の処理
 *      クラス構造を動的に作成するケースです
 *          String json = "{'id' : 300, 'name' : 'DynamicClass', 'subclass':{'key001':'valu001'}};
 *          Object dcls = jcon.Deserialize(json, "AnyRootClass");       //デシリアライズ実行
 *                      //第2引数 "AnyRootClass" は何でも良いが、アプリケーション内でユニークとなるよう注意 
 *          動的にClass(の型)を生成し、クラスインスタンスをdcls に格納します。
 *          結果は全て Object となるため、単純にはクラス構造がわかりません。
 *          プロパティの取り出し方)
 *          デシリアライズされた dcls オブジェクトから、JSON の 'key' でプロパティを探しその値を返します。
 *          ※)当然ながら、JSONの構造はあらかじめわかっているという前提です。
 *                  JSON の構造(key)が見えなければ、getXXX()メソッドに指定するkeyが分からないので探せません。
 *                  あしからず。
 *          int id = jconv.getString(dcls, "id");               //key:id でプロパティを探す
 *          String nm = jconv.getString(dcls, "name");  //key:name でプロパティを探す
 *          Object o = jconv.getString(dcls, "subclass");   //subclass でプロパティを探す
 *          id ⇒ 300 が返ります。
 *          nm ⇒ "DynamicClass" が返ります。
 *          subclassの場合は、dclsのサブクラスが生成されているので、まずサブクラスをオブジェクト o として取り出します。
 *          subclass 内のプロパティを探したい場合は、サブクラスに対してgetXXX()を行います
 *          String vaue = jcon.getString(o, "key001");
 *          value には、サブクラス内の key001 の値、"value001" が返ります。
 */
@Scope("prototype")
@Component
public class JsonConvert implements JsonConvertIf, CConst {

    /** Google Gsonオブジェクト生成 */
    Gson mygson = new Gson();

    /**
     *  Deserialization
     * JSON文字列を 定義済みのJavaオブジェクトへデシリアライズする
     * @param jsonData デシリアライズするJSONフォーマット文字列
     * @param parseObj デシリアライズ結果を格納するJavaオブジェクト(Class)
     *          オブジェクト(Class)の型は、JSONフォーマット構造を包含している必要がある。
     * @return 変換結果を格納したJavaオブジェクトを返す(==obj) エラーの場合はnullを返す
     * @throws Exception
     */
    public Object Deserialize(String jsonData, Object parseObj) throws Exception {
        //対象のJSON文字列が無い場合はエラー
        if(jsonData == null || jsonData.equals("")) return null;
        //デシリアライズ結果を格納するJavaオブジェクトがnullの場合はエラー
        if(parseObj == null) return null;

        InputStreamReader isr = new InputStreamReader( new ByteArrayInputStream(jsonData.getBytes()));
        JsonReader jsr = new JsonReader( isr );
        return (Object) mygson.fromJson( jsr, parseObj.getClass() );
    }

    /**
     *  Deserialization
     * JSON文字列を 動的Javaオブジェクトへデシリアライズする
     * ※制限事項)
     *  rootClassNameで表わされるClassにつながるSubclass の構造は一旦決定されると
     *  後でSubClassの構造は変更できない事とする。(ただしプロパティの追加はOK)
     *  具体的には、ある処理の結果として返されるJSONが処理のたびに
     *  構造が変わるような処理ではデシリアライズできない。
     *  (既存同名クラス変更は、Javassistの機能としてできないわけではないが、
     *   JSONパージングの関係上、処理が複雑かつ重くなるので禁止している)
     *  そのような処理が必要な場合は、rootClassNameを一意とすれば一応可能だが
     *  動的なクラスインスタンスが無限に増える可能性があるため注意すること。
     * @param jsonData デシリアライズするJSONフォーマット文字列
     * @param rootClassName 動的JavaObjectのrootクラス名(システムユニークになれば何でもよい)
     * @return 変換結果を格納した動的Javaオブジェクトを返す エラーの場合はnullを返す
     * @throws Exception
     */
    public Object Deserialize(String jsonData, String rootClassName) throws Exception {
        return this.createJson2DynamicClass(jsonData, rootClassName);
    }

    /**
     * Serialization
     * JavaオブジェクトをJSONフォーマット文字列にシリアライズする
     * @param obj シリアライズ対象クラスオブジェクト
     * @return クラス構造がパースされた、JSONフォーマット文字列を返す エラーの場合はnullを返す
     * @throws Exception
     */
    public String Serialize(Object obj) throws Exception {
        //シリアライズ対象クラスオブジェクトがnullの場合はエラー
        if(obj == null) return null;

        return mygson.toJson(obj);
    }

    /**
     * URLエンコードされたJSON文字列をdecodeする
* 文字コードはUTF-8決め打ちとする * @param encStr URLエンコードされた文字列 * @return decodeされた文字列を返す エラーの場合はnullを返す * @throws Exception */
public String decode(String encStr) throws Exception { //エンコードされた文字列が無い場合はエラー if(encStr == null || encStr.equals("")) return null; return this.decode(encStr,"UTF-8"); } /** * URLエンコードされたJSON文字列をdecodeする * JSON文字コードはUTF-8を期待する(その他の文字コードでの保証はなし) * @param encStr URLエンコードされた文字列 * @param charcode decodeする文字コードを指定する * @return decodeされた文字列を返す エラーの場合はnullを返す * @throws Exception */ public String decode(String encStr, String charcode) throws Exception { //エンコードされた文字列が無い場合はエラー if(encStr == null || encStr.equals("")) return null; //文字コードが無い場合はエラー if(charcode == null || charcode.equals("")) return null; String decStr = ""; //エンコード文字列のデコード String decparam = URLDecoder.decode(encStr,charcode); int len = decparam.length(); int last = decparam.lastIndexOf('='); if(last == len-1) { //末尾が = の場合 (ブラウザからJSONが送られてきた場合 decStr = decparam.substring(0, len-1); } else { //末尾 != = ならそのまま返却 decStr = decparam; } return decStr; } /** * javassist And リフレクションを使用して * JSON文字列から動的にクラス(の型)を生成し、クラスオブジェクトにデシリアライズする。 * ※注1)現状のjavassistは、プロパティ/メソッドの型として Listなど配列型がサポートされていない *    (StringはOK) *    List型は、Object型で代用する(使う側はListのつもりで使ってね) * ※注2)JSONから動的にクラスを構築するので、同時多重に同じクラス構築処理が実行されると問題があるため *     synchronizedでメソッドの排他制御を行う。 * @param jsonData デシリアライズ対象のJSON文字列 * @param clsName 生成するtopNodeの動的クラス名(Classの型毎にシステムユニークであることを期待する) * @return 生成されたクラスオブジェクトを返す 作成失敗時はnullを返す * @throws Exception */ private synchronized Object createJson2DynamicClass(String jsonData, String clsName) throws Exception { //対象のJSON文字列が無い場合はエラー if(jsonData == null || jsonData.equals("")) return null; //対象のClass名が無い場合はエラー if(clsName == null || clsName.equals("")) return null; //1回目のループ&ネスト(createJson2DynamicClass)で、動的クラスの型作成(プロパティ名とメソッド生成) //2回目のループ&ネスト(convertJson2DynamicClass)で、動的クラスのインスタンスを生成し、リフレクションによりプロパティ値のセットを実施 //1回目のクラスの型生成と同時にインスタンスを生成してしまうと、クラスが固定化(Freez)されてしまい //以降のプロパティ名Orメソッド生成でエラーとなる(Class Frozen Error) int[] depth = {0}; //オブジェクトツリー階層判定初期化 //動的クラスを作成 this.createJson2DynamicClass(jsonData,clsName, "", depth); //動的クラスにデータを設定 return this.convertJson2DynamicClass(jsonData,clsName, ""); } /** * JSON文字列から動的にクラス構造を生成する * @param jsonData デシリアライズ対象のJSON文字列 * @param clsName 生成するtopNodeの動的クラス名(Classの型毎にシステムユニークであること) * @param pClassName 親のクラス名(クラス名をユニークにするため) * @param depth オブジェクトツリー階層判定(1だとTOPツリー) * @return 生成されたクラスオブジェクトを返す * @throws Exception */ private Object createJson2DynamicClass(String jsonData, String clsName, String pClassName, int[] depth) throws Exception { CtClass cc = null; String cname = ""; String propName = ""; String methodName = ""; String mStr = ""; depth[0]++; //階層を一つ下げる logger.log_dbg(this, "depth["+depth[0]+"]"); //動的クラス名の設定(同名のクラス名を与えるとエラーになるので注意するべし) String fullClassName = ""; if(pClassName.equals("")) { fullClassName = clsName; } else { fullClassName = pClassName + "." + clsName; } //クラスの作成(クラス名でClassプール)から探す logger.log_dbg("makeClass["+fullClassName+"]"); try { cc= ClassPool.getDefault().get(fullClassName); /** * 既に動的クラスが存在しても、そのクラス構造が * 後で変更になる(プロパティ追加など)になるかもしれない * なので、存在してたらそのまま処理を続行する */ } catch(NotFoundException ne) { logger.log_info(this, "createJson2DynamicClass create clsName["+fullClassName+"]"); //クラス名が見つからなかったら新規に動的クラスを作る cc= ClassPool.getDefault().makeClass(fullClassName); logger.log_info("new makeClass["+fullClassName+"]"); } // GSONパーサーを取得 JsonParser jp = new JsonParser(); // JSON文字列をパース JsonElement je = null; Set<Map.Entry<String, JsonElement>> entrySet = null; Set<Map.Entry<String, JsonElement>> chkentrySet = null; try { logger.log_info("Parse["+jsonData+"]"); //JSON文字列をパースする je = jp.parse(jsonData); // key と value を取得する為に、JsonObject から、entrySet メソッドを実行 entrySet = je.getAsJsonObject().entrySet(); } catch(Exception e) { //jsonDataが、[val01, val02]形式の中身の "val01" で渡ってくるような場合ここに飛び込む //基本的にここに来ることはないはずだが念のため logger.log_error(e, "JSON parse error["+jsonData+"]"); return null; } // Mapのイテレータを取得 Iterator<Map.Entry<String, JsonElement>> it = entrySet.iterator(); logger.log_dbg("Map entrySet Size["+entrySet.size()+"]"); CtMethod get_method = null; CtMethod set_method = null; boolean newFlag = false; // イテレータを回す while(it.hasNext()) { newFlag = false; Map.Entry<String, JsonElement> entry = it.next(); //キーを取得 String key = entry.getKey(); //値を取得 JsonElement value = entry.getValue(); logger.log_dbg("key["+key+"]"); if ( value.isJsonNull() ) { //あり得ないはずだが念のためログを出す logger.log_warn("JsonValue Is Null key["+key+"]"); } else if ( value.isJsonObject() ) { //? ⇒ ' "key1":{"key001":val001", "key002":val"002} ' 形式 logger.log_dbg("JsonObject key["+key+"],value["+value.toString()+"]"); //再帰的に処理 if(createJson2DynamicClass( value.toString(), key, fullClassName, depth ) == null) { continue; } propName = "private Object "+key+" = null;"; //Object型のプロパティでListを表すことにする logger.log_dbg("JsonObject propName["+propName+"]"); newFlag = false; try { //既存のクラスで、プロパティが追加されることがあるかもしれない if(cc.isFrozen()) { //固定化されてたら cc.defrost(); //解凍する } //プロパティが存在するかチェック try { cc.getField(key); } catch(NotFoundException e) { //フィールドが見つからない ⇒ 初めてのプロパティ //プロパティを作成 CtField field = CtField.make(propName, cc); cc.addField(field); newFlag = true; } } catch(Exception e) { logger.log_error(this, e); } ///////////////// //メソッドの追加 ///////////////// //プロパティ名の先頭1文字を大文字化 String first = key.substring(0, 1).toUpperCase(); methodName = first + key.substring(1); //プロパティ名 //getterメソッドの追加 mStr = "public Object get"+methodName+"() {return "+key+ ";}"; try { if(newFlag) { get_method = CtNewMethod.make(mStr, cc); cc.addMethod(get_method); logger.log_dbg(this, "JsonObject getter methodName["+fullClassName+"."+mStr+"]"); } } catch(Exception e) { logger.log_error(this, e); } //setterメソッドの追加 mStr = "public void set"+methodName+"(Object value) {"+key+ "=value;}"; try { if(newFlag) { set_method = CtNewMethod.make(mStr, cc); cc.addMethod(set_method); } } catch(Exception e) { logger.log_error(this, e); } } else if ( value.isJsonArray() ) { //配列 [] logger.log_dbg("JsonArray key["+key+"],value["+value.toString()+"]"); cname = key + "List"; //クラス名  key+"List" とする JsonArray ar = value.getAsJsonArray(); //配列の中身を取り出す boolean jsonObjFlag = false; //配列の中身を処理する for(JsonElement el : ar) { logger.log_dbg("el.toString()["+el.toString()+"]"); //配列の中身がパースできるかチェックしてみる //case1:ar==[{key01,val01},{key02,val02}]] ⇒ ならパース可 //case2:ar==[val01, val02] ⇒ ならパース不可 try { JsonElement jeckeck = jp.parse(el.toString()); chkentrySet = jeckeck.getAsJsonObject().entrySet(); } catch(Exception e) { logger.log_dbg("el.toString()["+el.toString()+"]"); //case2 の場合 //対象フィールドは配列の中のフィールド値 [key:value] ではなく [val01, val02,...] みたいな jsonObjFlag = true; } //再帰処理(JSONの子ノードを辿る) if(!jsonObjFlag) { if(createJson2DynamicClass( el.toString(), cname, fullClassName, depth ) == null) { continue; //続ける } } } ////////////////////////////////////////////////// //プロパティの追加 //ただし、現状のjavassistはArray型(Listなど)をサポートしていないため、Objectとする //String long double などは大丈夫 ////////////////////////////////////////////////// propName = "private Object "+key+" = null;"; //Object型のプロパティでListを表すことにする logger.log_dbg("JsonArray propName["+fullClassName+"."+propName+"]"); newFlag = false; try { //既存のクラスで、プロパティが追加されることがあるかもしれない if(cc.isFrozen()) { //固定化されてたら cc.defrost(); //解凍する } //プロパティが存在するかチェック try { cc.getField(key); } catch(NotFoundException e) { //フィールドが見つからない ⇒ 初めてのプロパティ //プロパティを作成 CtField field = CtField.make(propName, cc); cc.addField(field); newFlag = true; } } catch(Exception e) { logger.log_error(this, e); } ///////////////// //メソッドの追加 ///////////////// //プロパティ名の先頭1文字を大文字化 String first = key.substring(0, 1).toUpperCase(); methodName = first + key.substring(1); //プロパティ名 //getterメソッドの追加 mStr = "public Object get"+methodName+"() {return "+key+ ";}"; try { if(newFlag) { get_method = CtNewMethod.make(mStr, cc); cc.addMethod(get_method); logger.log_dbg(this, "getter methodName["+fullClassName+"."+mStr+"]"); } } catch(Exception e) { logger.log_error(this, e); } //setterメソッドの追加 mStr = "public void set"+methodName+"(Object value) {"+key+ "=value;}"; try { if(newFlag) { set_method = CtNewMethod.make(mStr, cc); cc.addMethod(set_method); } } catch(Exception e) { logger.log_error(this, e); } } else if ( value.isJsonPrimitive() ) { //値 {"key" : "value"} の value 部 logger.log_dbg("JsonPrimitive key["+key+"],value["+value.getAsString()+"]"); JsonPrimitive jval = value.getAsJsonPrimitive(); //////////////////// //プロパティの追加 //////////////////// if(jval.isBoolean()) { //Bool型 propName = "private boolean " + key + "=false;"; //FldType = CtClass.booleanType; } else if(jval.isNumber()) { //数値型(実数はdouble型、整数はint型とする) String val = value.toString(); logger.log_dbg("prop val["+val+"]"); if(val.indexOf(".")>=0) { //小数点があったらdouble propName = "private double " + key + "=0.0;"; //FldType = CtClass.doubleType; } else { //それ以外はlong propName = "private long " + key + "=0L;"; //FldType = CtClass.longType; } } else { //文字列型 else if(jval.isString()) { propName = "private String " + key + "=\"\";"; } logger.log_dbg("JsonPrimitive propName["+fullClassName+"."+propName+"]"); try { //既存のクラスで、プロパティが追加されることがあるかもしれない if(cc.isFrozen()) { //固定化されてたら cc.defrost(); //解凍する } //プロパティが存在するかチェック try { cc.getField(key); } catch(NotFoundException e) { //フィールドが見つからない ⇒ 初めてのプロパティ CtField field = CtField.make(propName, cc); cc.addField(field); newFlag = true; } } catch(CannotCompileException e) { logger.log_error(this, e); } //////////////////// //メソッドの追加 //////////////////// //プロパティ名の先頭1文字を大文字化 String first = key.substring(0, 1).toUpperCase(); methodName = first + key.substring(1); //getterメソッドの追加 if(jval.isBoolean()) { //Bool型 try { get_method = CtNewMethod.make("public boolean get"+methodName+"() {return "+key+ ";}", cc); } catch(Exception e) { logger.log_warn("Make Bool getter Method"); } } else if(jval.isNumber()) { //数値型(実数はdouble型、整数はint型とする) String val = value.toString(); if(val.indexOf(".")>=0) { //小数点があったらdouble型 try { get_method = CtNewMethod.make("public double get"+methodName+"() {return "+key+ ";}", cc); } catch(Exception e) { logger.log_warn("Make Float getter Method"); } } else { //それ以外はlong型 try { get_method = CtNewMethod.make("public long get"+methodName+"() {return "+key+ ";}", cc); } catch(Exception e) { logger.log_warn("Make Long getter Method"); } } } else { //文字列型 try { get_method = CtNewMethod.make("public String get"+methodName+"() {return "+key+ ";}", cc); } catch(Exception e) { logger.log_warn("Make String getter Method"); } } try { logger.log_dbg("getter_methodName["+fullClassName+"."+get_method+"]"); if(newFlag) cc.addMethod(get_method); } catch(Exception e) { logger.log_error(this, e); } //setterメソッドの追加 if(jval.isBoolean()) { //Bool型 try { set_method = CtNewMethod.make("public void set"+methodName+"(boolean value) {"+key+ "=value;}", cc); } catch(Exception e) { logger.log_warn("Make Bool setter Method"); } } else if(jval.isNumber()) { //数値型(実数はdouble型、整数はint型とする) String val = value.toString(); if(val.indexOf(".")>=0) { //小数点があったらdouble型 try { set_method = CtNewMethod.make("public void set"+methodName+"(double value) {"+key+ "=value;}", cc); } catch(Exception e) { logger.log_warn("Make Float setter Method"); } } else { //それ以外はlong型 try { set_method = CtNewMethod.make("public void set"+methodName+"(long value) {"+key+ "=value;}", cc); } catch(Exception e) { logger.log_warn("Make Long setter Method"); } } } else { //文字列型 try { set_method = CtNewMethod.make("public void set"+methodName+"(String value) {"+key+ "=value;}", cc); } catch(Exception e) { logger.log_warn("Make String setter Method"); } } try { logger.log_dbg("setter_methodName["+fullClassName+"."+set_method+"]"); if(newFlag) cc.addMethod(set_method); } catch(Exception e) { logger.log_warn("addMethod"); } } } logger.log_dbg(this, "ESCAPE depth["+depth[0]+"]"); depth[0]--; //階層を一つ戻す //生成したクラスをオブジェクトとして返す Loader cl = new Loader(ClassPool.getDefault()); return cc.toClass(cl, cl.getClass().getProtectionDomain()); } /** * private Method * クラス構造及びデータを生成する。 * あらかじめ、createJson2DynamicClassメソッドで動的クラスを作成している事 * @param jsonData 処理対象のJSON文字列 * @param clsName 処理対象動的クラス名(createJson2DynamicClassメソッドで生成済み) * @param pClassName 親のクラス名(クラス名をユニークにするため) * @return 構築結果のroot(Class)オブジェクトを返す * @throws Exception */ private Object convertJson2DynamicClass(String jsonData, String clsName, String pClassName) throws Exception { Class<?> cclass = null; Object newclass = null; Object nchild = null; String cname = ""; //動的クラス名の設定(createJson2DynamicClassメソッドで生成されたクラス名) String fullClassName = ""; if(pClassName.equals("")) { fullClassName = clsName; } else { fullClassName = pClassName + "." + clsName; } //動的クラスをロードする logger.log_dbg("fullClassName ["+fullClassName+"]"); try { ClassPool pool = ClassPool.getDefault(); Loader cl = new Loader(pool); //クラスのロード cclass = cl.loadClass(fullClassName); newclass = (Object)cclass.newInstance(); //ロードしたクラスのインスタンス生成 } catch(Exception e) { logger.log_error(e); logger.log_error(this, "convertJson2DynamicClass newInstance Error"); //見つからなかったらエラー return null; } finally { //logger.log_dbg("ClassPool.getDefault Block"); } // GSONパーサーを取得 JsonParser jp = new JsonParser(); // 文字列をパース JsonElement je = null; Set<Map.Entry<String, JsonElement>> entrySet = null; Set<Map.Entry<String, JsonElement>> chkentrySet = null; logger.log_dbg("parse ["+jsonData+"]"); try { je = jp.parse(jsonData); // key と value を取得する為に、JsonObject から、entrySet メソッドを実行 entrySet = je.getAsJsonObject().entrySet(); } catch(Exception e) { logger.log_dbg("parse ERRRRROOOOORRR["+jsonData+"]"); return null; } logger.log_dbg("entrySet size ["+entrySet.size()+"]"); // イテレータを取得 Iterator<Map.Entry<String, JsonElement>> it = entrySet.iterator(); // イテレータを回す while(it.hasNext()) { Map.Entry<String, JsonElement> entry = it.next(); String key = entry.getKey(); //keyの取得 JsonElement value = entry.getValue(); //値の取得 logger.log_dbg("key["+key+"]"); if ( value.isJsonNull() ) { //あり得ないはずだが念のためログを出す logger.log_warn("JsonValue Is Null key["+key+"]"); } else if ( value.isJsonObject() ) { //? ⇒ ' "key1":{"key001":val001", "key002":val"002} ' 形式 logger.log_dbg("JsonObject key["+key+"],value["+value.toString()+"]"); //再帰的に処理 nchild = convertJson2DynamicClass( value.toString(), key, fullClassName ); if(nchild != null) { logger.log_dbg("JsonObjectsetObject key["+key+"]"); //リフレクションでプロパティにObjectをセットする this.setObject(newclass, key, nchild); } } else if ( value.isJsonArray() ) { //配列 [] logger.log_dbg("JsonArray key["+key+"]"); cname = key + "List"; //クラス名  key+"List" とする JsonArray ar = value.getAsJsonArray(); List<Object> carray = new ArrayList<>(); //子ノードのオブジェクトをセットするリストを生成 //[val01,val02,,,] 形式はこれ以上下れないのでチェック boolean leafChek = false; JsonPrimitive jval = null; //配列の中身がパースできるかチェックしてみる //case1:ar==[{key01,val01},{key02,val02}]] ⇒ ならパース可 //case2:ar==[val01, val02] ⇒ ならパース不可 for(JsonElement el : ar) { try { je = jp.parse(el.toString()); // key と value を取得する為に、JsonObject から、entrySet メソッドを実行 chkentrySet = je.getAsJsonObject().entrySet(); } catch(Exception e) { //case2 の場合 //対象フィールドは配列の中のフィールド値 [key:value] ではなく [val01, val02,...] みたいな jval = el.getAsJsonPrimitive(); leafChek = true; break; } } //case2:ar==[val01,val02,,,] 形式はプロパティをセットする if(leafChek) { //プロパティの追加 if(jval.isBoolean()) { //Bool型 Boolean[] b = new Boolean[ar.size()]; int i = 0; for(JsonElement el : ar) { b[i] = el.getAsBoolean(); i++; } //リフレクションでプロパティにObject(List)をセットする this.setObject(newclass, key, b); } else if(jval.isNumber()) { //数値型 String val = value.toString(); if(val.indexOf(".")>=0) { //小数点があったらダブル Double[] d = new Double[ar.size()]; int i = 0; for(JsonElement el : ar) { d[i] = el.getAsNumber().doubleValue(); i++; } //リフレクションでプロパティにObject(List)をセットする this.setObject(newclass, key, d); } else { Long[] l = new Long[ar.size()]; int i = 0; for(JsonElement el : ar) { l[i] = el.getAsNumber().longValue(); i++; } //リフレクションでプロパティにObject(List)をセットする this.setObject(newclass, key, l); } } else { //文字列型 else if(jval.isString()) { String[] s = new String[ar.size()]; int i = 0; for(JsonElement el : ar) { s[i] = el.getAsString(); i++; } //リフレクションでプロパティにObject(List)をセットする this.setObject(newclass, key, s); } //case1:ar==[{key01,val01},{key02,val02}]] 形式は再帰的に処理を続ける } else { for(JsonElement el : ar) { logger.log_dbg("JsonArray Element key["+key+"],value["+el.toString()+"]"); //再帰処理(JSONの子ノードを辿る) nchild = convertJson2DynamicClass( el.toString(), cname, fullClassName ); if(nchild == null) { continue; } carray.add(nchild); //子ノードオブジェクトをListに追加 } //リフレクションでプロパティにObject(List)をセットする this.setObject(newclass, key, carray); } } else if ( value.isJsonPrimitive() ) { //値 {"key" : "value"} の value 部 logger.log_dbg("JsonPrimitive key["+key+"], value["+value.getAsString()+"]"); JsonPrimitive jval = value.getAsJsonPrimitive(); //プロパティの追加 if(jval.isBoolean()) { //Bool型 //リフレクションでプロパティにBOOL値をセットする this.setBoolean(newclass, key, value.getAsBoolean()); } else if(jval.isNumber()) { //数値型 String val = value.toString(); logger.log_dbg("long value ["+val+"]"); if(val.indexOf(".")>=0) { //小数点があったらダブル //リフレクションでプロパティにDouble値をセットする this.setDouble(newclass, key, value.getAsNumber().doubleValue()); } else { //リフレクションでプロパティにint値をセットする this.setLong(newclass, key, value.getAsNumber().longValue()); } } else { //文字列型 else if(jval.isString()) { //リフレクションでプロパティにString値をセットする this.setString(newclass, key, value.getAsString()); } } } return newclass; //クラスのインスタンスを、Object型として返す } /** * JSON解析メソッド * JSON文字列をパースして、key:value を取得する * @param jsonData 解析対象JSON文字列 */ public void print_json(String jsonData) throws Exception { // パーサーを取得 JsonParser jp = new JsonParser(); // 文字列をパース JsonElement je = jp.parse(jsonData); // key と value を取得する為に、JsonObject から、entrySet メソッドを実行 Set<Map.Entry<String, JsonElement>> entrySet = je.getAsJsonObject().entrySet(); // イテレータを取得 Iterator<Map.Entry<String, JsonElement>> it = entrySet.iterator(); // 一覧表示 while(it.hasNext()) { Map.Entry<String, JsonElement> entry = it.next(); String key = entry.getKey(); JsonElement value = entry.getValue(); if ( value.isJsonNull() ) { return; } else if ( value.isJsonObject() ) { logger.log_info(this, "JsonObject key["+key+"],value["+value.toString()+"]"); } else if ( value.isJsonArray() ) { logger.log_info(this, "JsonArray key["+key+"],value["+value.toString()+"]"); JsonArray ar = value.getAsJsonArray(); for(JsonElement el : ar) { logger.log_info(this, "JsonArray Element key["+key+"],value["+el.toString()+"]"); print_json( el.toString() ); } } else if ( value.isJsonPrimitive() ) { logger.log_info(this, "JsonPrimitive key["+key+"],value["+value.getAsString()+"]"); } else { if ( value.isJsonNull() ) { logger.log_info(this, "NULL"); } else { logger.log_info(this, "JsonValue["+value.toString()+"]"); } } // value.toString() だと、個別の文字列の場合 " で挟まれた文字列が取得されました } } /** * 対象オブジェクト(o) のプロパティの値を取得する * @param o 取得対象オブジェクト(クラス) * @param propertyName 値を取得するプロパティ名 * @return 取得したプロパティ値を返す * @throws Exception */ private Object getValue(Object o, String propertyName) throws Exception { //オブジェクトからクラスを取得する Class<?> cls = o.getClass(); //読み込んだバイト配列からリフレクションでクラスを構築する Method method = null; Object data = null; //リフレクションメソッドの戻り値 //メソッドの引数パターン(引数なしなのでnullとする) //プロパティ名から目getterメソッドを生成 String getterName = "get"; getterName += propertyName.substring(0, 1).toUpperCase(); getterName += propertyName.substring(1); logger.log_dbg("getterName["+getterName+"]"); try { //クラスのリフレクションメソッドを取得する method = cls.getDeclaredMethod(getterName); } catch(Exception e) { logger.log_error(e); throw new Exception(e); } //メソッド引数値生成(引数なしなのでnullとする) Object[] args = null; try { //リフレクションメソッド(getter)を実行 data = method.invoke(o, args); } catch(Exception e) { logger.log_error(e); throw new Exception(e); } return data; } /** * 対象オブジェクト(o) の文字列型プロパティの値を取得する * @param o 取得対象オブジェクト(クラス) * @param propertyName 値を取得するプロパティ名 * @return 取得したプロパティ値を返す * @throws Exception */ public String getString(Object o, String propertyName) throws Exception { Object ret = this.getValue(o, propertyName); return (String)ret.toString(); } /** * 対象オブジェクト(o) の整数型プロパティの値を取得する * @param o 取得対象オブジェクト(クラス) * @param propertyName 値を取得するプロパティ名 * @return 取得したプロパティ値を返す * @throws Exception */ public int getInt(Object o, String propertyName) throws Exception { Object ret = this.getValue(o, propertyName); return Integer.parseInt(ret.toString()); } /** * 対象オブジェクト(o) の整数型プロパティの値を取得する * @param o 取得対象オブジェクト(クラス) * @param propertyName 値を取得するプロパティ名 * @return 取得したプロパティ値を返す * @throws Exception */ public long getLong(Object o, String propertyName) throws Exception { Object ret = this.getValue(o, propertyName); return Long.parseLong(ret.toString()); } /** * 対象オブジェクト(o) の実数型プロパティの値を取得する * @param o 取得対象オブジェクト(クラス) * @param propertyName 値を取得するプロパティ名 * @return 取得したプロパティ値を返す * @throws Exception */ public double getDouble(Object o, String propertyName) throws Exception { Object ret = this.getValue(o, propertyName); return Double.parseDouble(ret.toString()); } /** * 対象オブジェクト(o) のObject型プロパティの値を取得する * @param o 取得対象オブジェクト(クラス) * @param propertyName 値を取得するプロパティ名 * @return 取得したプロパティ値を返す * @throws Exception */ public Object getObject(Object o, String propertyName) throws Exception { return (Object)this.getValue(o, propertyName); } /** * 対象オブジェクト(o) のBOOL型プロパティの値を取得する * @param o 取得対象オブジェクト(クラス) * @param propertyName 値を取得するプロパティ名 * @return 取得したプロパティ値を返す * @throws Exception */ public boolean getBoolean(Object o, String propertyName) throws Exception { return (boolean)this.getValue(o, propertyName); } /** * 対象オブジェクト(o) のプロパティの値をセットする * @param o 取得対象オブジェクト(クラス) * @param propertyName 値をセットするプロパティ名 * @param value プロパティにセットするパラメータ値 * @param param リフレクションメソッドのパラメータ属性 * @throws Exception */ private void setValue(Object o, String propertyName, Object value, Class<?>[] param) throws Exception { //プロパティ名からsetterメソッド名を生成 String setterName = "set"; setterName += propertyName.substring(0, 1).toUpperCase(); setterName += propertyName.substring(1); //リフレクションでsetterメソッドを取得 Class<?> c = o.getClass(); Method method = c.getMethod(setterName, param); try { //パラメータ値をセットする Object[] args = new Object[]{ value }; //リフレクションメソッド(setter)実行 method.invoke(o, args); } catch (Exception e) { logger.log_error(e); } } /** * 対象オブジェクト(o) の文字列型プロパティの値をセットする * @param o 取得対象オブジェクト(クラス) * @param propertyName 値を取得するプロパティ名 * @param value プロパティにセットする文字列型パラメータ値 * @throws Exception */ public void setString(Object o, String propertyName, String value) throws Exception { Class<?>[] param = new Class[]{String.class}; this.setValue(o, propertyName, value, param); } /** * 対象オブジェクト(o) の整数型プロパティの値をセットする * @param o 取得対象オブジェクト(クラス) * @param propertyName 値を取得するプロパティ名 * @param value プロパティにセットする整数型パラメータ値 * @throws Exception */ public void setInt(Object o, String propertyName, int value) throws Exception { Class<?>[] param = new Class[]{int.class}; this.setValue(o, propertyName, value, param); } /** * 対象オブジェクト(o) の整数型プロパティの値をセットする * @param o 取得対象オブジェクト(クラス) * @param propertyName 値を取得するプロパティ名 * @param value プロパティにセットする整数型パラメータ値 * @throws Exception */ public void setLong(Object o, String propertyName, long value) throws Exception { Class<?>[] param = new Class[]{long.class}; this.setValue(o, propertyName, value, param); } /** * 対象オブジェクト(o) の実数型プロパティの値をセットする * @param o 取得対象オブジェクト(クラス) * @param propertyName 値を取得するプロパティ名 * @param value プロパティにセットする実数型パラメータ値 * @throws Exception */ public void setDouble(Object o, String propertyName, double value) throws Exception { Class<?>[] param = new Class[]{double.class}; this.setValue(o, propertyName, value, param); } /** * 対象オブジェクト(o) のリスト型プロパティの値をセットする * @param o 取得対象オブジェクト(クラス) * @param propertyName 値を取得するプロパティ名 * @param value プロパティにセットするLIST型パラメータ値 * @throws Exception */ public void setList(Object o, String propertyName, List<?> value) throws Exception { Class<?>[] param = new Class[]{List.class}; this.setValue(o, propertyName, value, param); } /** * 対象オブジェクト(o) のBOOL型プロパティの値をセットする * @param o 取得対象オブジェクト(クラス) * @param propertyName 値を取得するプロパティ名 * @param value プロパティにセットするBOOL型パラメータ値 * @throws Exception */ public void setBoolean(Object o, String propertyName, boolean value) throws Exception { Class<?>[] param = new Class[]{boolean.class}; this.setValue(o, propertyName, value, param); } /** * 対象オブジェクト(o) のBOOL型プロパティの値をセットする * @param o 取得対象オブジェクト(クラス) * @param propertyName 値を取得するプロパティ名 * @param value プロパティにセットするBOOL型パラメータ値 * @throws Exception */ public void setObject(Object o, String propertyName, Object value) throws Exception { Class<?>[] param = new Class[]{Object.class}; this.setValue(o, propertyName, value, param); } /** * private Method * 動的クラスを破棄する * @param jsonData デシリアライズ対象のJSON文字列 * @param clsName 生成するtopNodeの動的クラス名(Classの型毎にシステムユニークであること) * @param pClassName 親のクラス名(クラス名をユニークにするため) * @throws Exception */ private void removeJson2DynamicClass(String jsonData, String clsName, String pClassName) throws Exception { Class<?> cclass = null; CtClass cc = null; String cname = ""; String propName = ""; String methodName = ""; String mStr = ""; String fullClassName = ""; if(pClassName.equals("")) { fullClassName = clsName; } else { fullClassName = pClassName + "." + clsName; } //クラスの作成(クラス名でClassプール)から探す logger.log_dbg("remove target FullClass["+fullClassName+"]"); try { cc= ClassPool.getDefault().get(fullClassName); logger.log_dbg("detach FullClass["+fullClassName+"]"); //ClassPoolからクラスを破棄する cc.detach(); //detach()自体はエラーにはならないが、createJson2DynamicClass()メソッドの toClass()で //以下のExceptionが発生する ⇒ ClassPollからはいなくなったが、Classのインスタンスは存在している? //Class:javassist.CannotCompileException //Detail:by java.lang.LinkageError: loader (instance of org/apache/catalina/loader/WebappClassLoader): attempted duplicate class definition for name: "get_field/root" //⇒ // createJson2DynamicClassメソッドの cc.toClass()を使うとダメ // cc.toClass(java.lang.ClassLoader loader, java.security.ProtectionDomain domain); // だとまともに動く } catch(Exception ne) { //なければ何もしない return; } // GSONパーサーを取得 JsonParser jp = new JsonParser(); // JSON文字列をパース JsonElement je = null; Set<Map.Entry<String, JsonElement>> entrySet = null; Set<Map.Entry<String, JsonElement>> chkentrySet = null; try { //JSON文字列をパースする je = jp.parse(jsonData); // key と value を取得する為に、JsonObject から、entrySet メソッドを実行 entrySet = je.getAsJsonObject().entrySet(); } catch(Exception e) { //jsonDataが、[val01, val02]形式の中身の "val01" で渡ってくるような場合ここに飛び込む //基本的にここに来ることはないはずだが念のため logger.log_error(e); return; } // Mapのイテレータを取得 Iterator <Map.Entry<String, JsonElement>> it = entrySet.iterator(); logger.log_dbg("Map entrySet Size["+entrySet.size()+"]"); CtMethod get_method = null; CtMethod set_method = null; boolean newFlag = false; // イテレータを回す while(it.hasNext()) { newFlag = false; Map.Entry<String, JsonElement> entry = it.next(); //キーを取得 String key = entry.getKey(); //値を取得 JsonElement value = entry.getValue(); logger.log_dbg("key["+key+"]"); if ( value.isJsonNull() ) { //あり得ないはずだが念のためログを出す logger.log_warn("JsonValue Is Null key["+key+"]"); } else if ( value.isJsonObject() ) { //? ⇒ ' "key1":{"key001":val001", "key002":val"002} ' 形式 logger.log_dbg("JsonObject key["+key+"],value["+value.toString()+"]"); //再帰的に処理 removeJson2DynamicClass( value.toString(), key, fullClassName); } else if ( value.isJsonArray() ) { //配列 [] logger.log_dbg("JsonArray key["+key+"],value["+value.toString()+"]"); cname = key + "List"; //クラス名  key+"List" とする JsonArray ar = value.getAsJsonArray(); //配列の中身を取り出す boolean jsonObjFlag = false; //配列の中身を処理する for(JsonElement el : ar) { logger.log_dbg("el.toString()["+el.toString()+"]"); //配列の中身がパースできるかチェックしてみる //case1:ar==[{key01,val01},{key02,val02}]] ⇒ ならパース可 //case2:ar==[val01, val02] ⇒ ならパース不可 try { JsonElement jeckeck = jp.parse(el.toString()); chkentrySet = jeckeck.getAsJsonObject().entrySet(); } catch(Exception e) { logger.log_dbg("el.toString()["+el.toString()+"]"); //case2 の場合 //対象フィールドは配列の中のフィールド値 [key:value] ではなく [val01, val02,...] みたいな jsonObjFlag = true; } //再帰処理(JSONの子ノードを辿る) if(!jsonObjFlag) { removeJson2DynamicClass( el.toString(), cname, fullClassName); } } } else if ( value.isJsonPrimitive() ) { //値 {"key" : "value"} の value 部 ; //値は無視 } } return; } }