1. 程式人生 > >Java 演算法 JSON Bean相互轉化及JSON生成實體類

Java 演算法 JSON Bean相互轉化及JSON生成實體類

前言:之前解析JSON資料的時候使用的是GSON,相信大家已經非常熟悉,在封裝開源控制元件的時候覺得GSON還是太重了而且別人在使用的時候不一定用這個解析框架,那就自己寫一個解析的工具吧。

一、概述

    將JSON封裝到Bean物件,就是將JSON所對應的資料一一賦值到例項物件上,那麼可以逆向過來,看該Bean物件有哪些欄位,然後用欄位的名稱去JSON中去查詢值,再將查詢到的賦值到該欄位。

二、JSON語法

    也行大家向我之前一樣對於JSON的語法不太瞭解,知道客戶端去訪問後臺提供的介面,後臺返回給我JSON字串,我寫一個實體物件,然後使用工具(GSON、Jackson、Json-lib等)一行程式碼就封裝到了物件上。既然我們要自己編寫解析的話就要簡單瞭解下JSON究竟是怎樣定義的。
    1. JSON 資料的書寫格式
  •     資料在名稱/值對中
  •     資料由逗號分隔
  •     花括號儲存物件
  •     方括號儲存陣列
2. JSON的值
  •     數字(整數或浮點數)
  •     字串(在雙引號中)
  •     邏輯值(true 或 false)
  •     陣列(在方括號中)
  •     物件(在花括號中)
  •     null
3. JSON 物件     JSON 物件在花括號中書寫:     物件可以包含多個名稱/值對:
 { "firstName":"Zhou" , "lastName":"Kevin" }

4. JSON 陣列     JSON 陣列在方括號中書寫:     陣列可包含多個物件:
{
	"employees": [
		{
			"firstName": "Zhou",  "lastName": "Kevin" 
		},
		{
			"firstName": "Li",  "lastName": "Qian" 
		},
		{
			"firstName": "Peter",  "lastName": "Jones" 
		}
	]
}

三、反射的簡單回顧

1. 根據類位元組碼獲取例項     假設有一個類 T 以及它的位元組碼 clazz        
T t = clazz.newInstance();

這樣呼叫的是該類無參的建構函式,建立了例項物件。     上面的方式對於內部類就無計可施了,我們大家都知道建立內部類物件的時候需要外部類引用(指標),
    假設有這麼一個類:
public class Outer {
	public class Inner{
	}
}

那麼我們想要建立Inner的例項物件就要按照以下的方式:
Outer outer = new Outer();
Inner inner = outer.new Inner();

當然在利用反射時候也需要有外部類的引用(指標),如下:
Outer outer = Outer.class.newInstance();
Constructor<?> constructor = Inner.class.getDeclaredConstructors()[0];
constructor.setAccessible(true);
Inner inner = (Inner) constructor.newInstance(outer);

2. 根據類位元組碼獲取所有欄位

Field[] fields = c.getDeclaredFields();

3. 獲取某一欄位的型別名稱     String name = field.getType().getName();     String型別欄位型別名稱:java.lang.String     int型別欄位型別名稱:int     Integer型別欄位型別名稱:java.lang.Integer     boolean型別欄位型別名稱:boolean     Boolean型別欄位型別名稱:java.lang.Boolean     float型別欄位型別名稱:float     Float型別欄位型別名稱:java.lang.Float     double型別欄位型別名稱:double     Double型別欄位型別名稱:java.lang.Double     List型別欄位型別名稱:java.util.List
    ArrayList型別欄位型別名稱:java.util.ArrayList
    其他的為類 型別欄位名稱:如 com.kevin.jsontool.bean.TestBean2$LoopData為內部類
4.設定某一欄位的值     假設有如下類:
public class Person{
	public String name;
	public int age;
}

Pserson p = Person.class.newInstance();
Field[] fields = Person.class.getDeclaredFields();
for (Field field : fields) {
	   field.setAccessible(true);
	   Class<?> type = field.getType();
	   String typeName = type.getName();
	   if(typeName.equalsIgnoreCase("java.lang.String")) {
			field.set(p, "Kevin");
		} else if(typeName.equalsIgnoreCase("int")) {
			field.set(p, 25);
		}
}

四、JSONTool toBean編寫

    在前文提到過,是反射例項物件的欄位然後去JSONObject查詢對應的值,當然也可以在欄位的頭上加上自己的註解這樣可以欄位的名稱可以和JSONObject中名稱(鍵)不嚴格一致,這裡不去做了。
/**
 * 將JSON字串封裝到物件
 * 
 * @param jsonStr 待封裝的JSON字串
 * @param clazz 待封裝的例項位元組碼
 * @return T: 封裝JSON資料的物件
 * @version 1.0
 */
public static <T> T toBean(String jsonStr, Class<T> clazz) {
	try {
		  JSONObject job = new JSONObject(jsonStr);
		  return parseObject(job, clazz, null);
	} catch (JSONException e) {
		  e.printStackTrace();
	}
	return null;
}

/**
 * JSONObject 封裝到 物件例項
 * 
 * @param job 待封裝的JSONObject
 * @param c 待封裝的例項物件class
 * @param v	待封裝例項的外部類例項物件</br>只有內部類存在,外部類時傳遞null
 * @return T:封裝資料的例項物件
 * @version 1.0 
 * @date 2015-10-9
 * @Author zhou.wenkai
 */
@SuppressWarnings("unchecked")
private static <T, V> T parseObject(JSONObject job, Class<T> c, V v) {
	T t = null;
	try {
		if(null == v) {
			t = c.newInstance();
		} else {
			Constructor<?> constructor = c.getDeclaredConstructors()[0];
			constructor.setAccessible(true);
			t = (T) constructor.newInstance(v);
		}
	} catch (IllegalArgumentException e) {
		e.printStackTrace();
		Log.e(JsonTool.class.getSimpleName(),
				c.toString() + " should provide a default constructor " +
						"(a public constructor with no arguments)");
	} catch (Exception e) {
		if(DEBUG)
			e.printStackTrace();
	}
	
	Field[] fields = c.getDeclaredFields();
	for (Field field : fields) {
		field.setAccessible(true);
		Class<?> type = field.getType();
		String name = field.getName();
		
		// if the object don`t has a mapping for name, then continue
		if(!job.has(name)) continue;
		
		String typeName = type.getName();
		if(typeName.equals("java.lang.String")) {
			try {
				String value = job.getString(name);
				if (value != null && value.equals("null")) {
					value = "";
				}
				field.set(t, value);
			} catch (Exception e) {
				if(DEBUG)
					e.printStackTrace();
				try {
					field.set(t, "");
				} catch (Exception e1) {
					if(DEBUG)
						e1.printStackTrace();
				}
			}
		} else if(typeName.equals("int") ||
				typeName.equals("java.lang.Integer")) {
			try {
				field.set(t, job.getInt(name));
			} catch (Exception e) {
				if(DEBUG)
					e.printStackTrace();
			}
		} else if(typeName.equals("boolean") ||
				typeName.equals("java.lang.Boolean")) {
			try {
				field.set(t, job.getBoolean(name));
			} catch (Exception e) {
				if(DEBUG)
					e.printStackTrace();
			}
		} else if(typeName.equals("float") ||
				typeName.equals("java.lang.Float")) {
			try {
				field.set(t, Float.valueOf(job.getString(name)));
			} catch (Exception e) {
				if(DEBUG)
					e.printStackTrace();
			}
		} else if(typeName.equals("double") || 
				typeName.equals("java.lang.Double")) {
			try {
				field.set(t, job.getDouble(name));
			} catch (Exception e) {
				if(DEBUG)
					e.printStackTrace();
			}
		} else if(typeName.equals("long") || 
				typeName.equals("java.lang.Long")) {
			try {
				field.set(t, job.getLong(name));
			} catch (Exception e) {
				if(DEBUG)
					e.printStackTrace();
			}
		} else if(typeName.equals("java.util.List") ||
				typeName.equals("java.util.ArrayList")){
			try {
				Object obj = job.get(name);
				Type genericType = field.getGenericType();
				String className = genericType.toString().replace("<", "")
						.replace(type.getName(), "").replace(">", "");
				Class<?> clazz = Class.forName(className);
				if(obj instanceof JSONArray) {
					ArrayList<?> objList = parseArray((JSONArray)obj, clazz, t);
					field.set(t, objList);
				}
			} catch (Exception e) {
				if(DEBUG)
					e.printStackTrace();
			}
		} else {
			try {
				Object obj = job.get(name);
				Class<?> clazz = Class.forName(typeName);
				if(obj instanceof JSONObject) {
					Object parseJson = parseObject((JSONObject)obj, clazz, t);
					field.set(t, parseJson);
				}
			} catch (Exception e) {
				if(DEBUG)
					e.printStackTrace();
			}
			
		}
	}

	return t;
}

/**
 * 將 JSONArray 封裝到 ArrayList 物件
 * 
 * @param array 待封裝的JSONArray
 * @param c 待封裝實體位元組碼
 * @param v 待封裝例項的外部類例項物件</br>只有內部類存在,外部類時傳遞null
 * @return ArrayList<T>: 封裝後的實體集合
 * @version 1.0 
 * @date 2015-10-8
 */
@SuppressWarnings("unchecked")
private static <T, V> ArrayList<T> parseArray(JSONArray array, Class<T> c, V v) {
	ArrayList<T> list = new ArrayList<T>(array.length());
	try {
		for (int i = 0; i < array.length(); i++) {
			if(array.get(i) instanceof JSONObject) {
				T t = parseObject(array.getJSONObject(i), c, v);
				list.add(t);
			} else {
				list.add((T) array.get(i));
			}
			
		}
	} catch (Exception e) {
		if(DEBUG)
			e.printStackTrace();
	}
	return list;
}

五、JSONTool toJson編寫

    由實體類封裝到JSON資料,就是遍歷該實體類的所以欄位,然後找到欄位的名稱以及欄位值然後封裝JSON資料。
/**
 * 將 物件編碼為 JSON格式
 * 
 * @param t 待封裝的物件
 * @return String: 封裝後JSONObject String格式
 * @version 1.0
 */
public static <T> String toJson(T t) {
	if (t == null) {
		return "{}";  
	}
	return objectToJson(t);
}

/**
 * 將 物件編碼為 JSON格式
 * 
 * @param t 待封裝的物件
 * @return String: 封裝後JSONObject String格式
 * @version 1.0 
 * @date 2015-10-11
 * @Author zhou.wenkai
 */
private static <T> String objectToJson(T t) {
	
	Field[] fields = t.getClass().getDeclaredFields();
	StringBuilder sb = new StringBuilder(fields.length << 4);
	sb.append("{");
	
	for (Field field : fields) {
		field.setAccessible(true);
		Class<?> type = field.getType();
		String name = field.getName();
		
		// 'this$Number' 是內部類的外部類引用(指標)欄位
		if(name.contains("this$")) continue;
		
		String typeName = type.getName();
		if(typeName.equals("java.lang.String")) {
			try {
				sb.append("\""+name+"\":");
				sb.append(stringToJson((String)field.get(t)));
				sb.append(",");
			} catch (Exception e) {
				if(DEBUG)
					e.printStackTrace();
			}
		} else if(typeName.equals("boolean") ||
				typeName.equals("java.lang.Boolean") ||
				typeName.equals("int") ||
				typeName.equals("java.lang.Integer") ||
				typeName.equals("float") ||
				typeName.equals("java.lang.Float") ||
				typeName.equals("double") || 
				typeName.equals("java.lang.Double") ||
				typeName.equals("long") || 
				typeName.equals("java.lang.Long")) {
			try {
				sb.append("\""+name+"\":");
				sb.append(field.get(t));
				sb.append(",");
			} catch (Exception e) {
				if(DEBUG)
					e.printStackTrace();
			}
		} else if(typeName.equals("java.util.List") ||
				typeName.equals("java.util.ArrayList")){
			try {
				List<?> objList = (List<?>) field.get(t);
				if(null != objList && objList.size() > 0) {
					sb.append("\""+name+"\":");
					sb.append("[");
					String toJson = listToJson((List<?>) field.get(t));
					sb.append(toJson);
					sb.setCharAt(sb.length()-1, ']');
					sb.append(",");
				}
			} catch (Exception e) {
				if(DEBUG)
					e.printStackTrace();
			}
		} else {
			try {
				sb.append("\""+name+"\":");
				sb.append("{");
				sb.append(objectToJson(field.get(t)));
				sb.setCharAt(sb.length()-1, '}');
				sb.append(",");
			} catch (Exception e) {
				if(DEBUG)
					e.printStackTrace();
			}
		}
		
	}
	if(sb.length() == 1) {
		sb.append("}");
	}
	sb.setCharAt(sb.length()-1, '}');
	return sb.toString();
}

/**
 * 將 List 物件編碼為 JSON格式
 * 
 * @param objList 待封裝的物件集合
 * @return String:封裝後JSONArray String格式
 * @version 1.0 
 * @date 2015-10-11
 * @Author zhou.wenkai
 */
private static<T> String listToJson(List<T> objList) {
	final StringBuilder sb = new StringBuilder();
	for (T t : objList) {
		if(t instanceof String) {
			sb.append(stringToJson((String) t));
			sb.append(",");
		} else if(t instanceof Boolean || 
				t instanceof Integer ||
				t instanceof Float ||
				t instanceof Double) {
			sb.append(t);
			sb.append(",");
		} else {
			sb.append(objectToJson(t));
			sb.append(",");
		}
	}
	return sb.toString();
}

/**
 * 將 String 物件編碼為 JSON格式,只需處理好特殊字元
 * 
 * @param str String 物件
 * @return String:JSON格式 
 * @version 1.0 
 * @date 2015-10-11
 * @Author zhou.wenkai
 */
private static String stringToJson(final String str) {
	if(str == null || str.length() == 0) {
		return "\"\"";
	}
	final StringBuilder sb = new StringBuilder(str.length() + 2 << 4);
	sb.append('\"');
	for (int i = 0; i < str.length(); i++) {
		final char c = str.charAt(i);

		sb.append(c == '\"' ? "\\\"" : c == '\\' ? "\\\\"
				: c == '/' ? "\\/" : c == '\b' ? "\\b" : c == '\f' ? "\\f"
						: c == '\n' ? "\\n" : c == '\r' ? "\\r"
								: c == '\t' ? "\\t" : c);
	}
	sb.append('\"');
	return sb.toString();
}

六、JSONTool createBean編寫

    由JSON資料生成實體類,這裡生成的實體類為String型別。原理及時遍歷JSON資料,根據資料的型別以及名稱來生成實體類。
/**
 * 由JSON字串生成Bean物件
 *  
 * @param jsonStr
 * @param className 待生成Bean物件的名稱
 * @return String:
 * @version 1.0
 */
public static String createBean(String jsonStr, String className) {
	try {
		JSONObject job = new JSONObject(jsonStr);
		return createObject(job, className, 0);
	} catch (JSONException e) {
		e.printStackTrace();
	}
	return "";
}

	/**
	 * 由JSONObject生成Bean物件
	 * 
	 * @param job
	 * @param className 待生成Bean物件的名稱
	 * @param outCount 外部類的個數
	 * @return LinkedList<String>: 生成的Bean物件
	 * @version 1.0
	 * @date 2015-10-16
	 * @Author zhou.wenkai
	 */
	private static String createObject(JSONObject job, String className, int outCount) {
		final StringBuilder sb = new StringBuilder();
		String separator = System.getProperty("line.separator");
		
		// 生成的Bean類前部的縮排空間
		String classFrontSpace = "";
		// 生成的Bean類欄位前部的縮排空間
		String fieldFrontSpace = "    ";
		for (int i = 0; i < outCount; i++) {
			classFrontSpace += "    ";
			fieldFrontSpace += "    ";
		}
		
		sb.append(classFrontSpace + "public class " + className + " {");
		
		Iterator<?> it = job.keys();
		while (it.hasNext()) {
            String key = (String) it.next();
            try {
				Object obj = job.get(key);
				if(obj instanceof JSONArray) {
					// 判斷類是否為基本資料型別,如果為自定義類則欄位型別取將key的首字母大寫作為內部類名稱
					String fieldType = ((JSONArray)obj).get(0) instanceof JSONObject ?
							"" : ((JSONArray)obj).get(0).getClass().getSimpleName();
					if(fieldType == "") {
						fieldType = String.valueOf(Character.isUpperCase(key.charAt(0)) ? 
								key.charAt(0) : Character.toUpperCase(key.charAt(0))) + key.substring(1);
					}
					sb.append(separator);
					sb.append(fieldFrontSpace + "public List<" + fieldType + "> " + key + ";");
					
					// 如果欄位型別為自定義類型別,則取JSONArray中第一個JSONObject生成Bean
					if(((JSONArray)obj).get(0) instanceof JSONObject) {
						sb.append(separator);
						sb.append(separator);
						sb.append(fieldFrontSpace + "/** "+ fieldType +" is the inner class of "+ className +" */");
						sb.append(separator);
						sb.append(createObject((JSONObject)((JSONArray)obj).get(0), fieldType, outCount+1));
					}
				} else if(obj instanceof JSONObject) {
					String fieldType = String.valueOf(Character.isUpperCase(key.charAt(0)) ? 
							key.charAt(0) : Character.toUpperCase(key.charAt(0))) + key.substring(1);
					sb.append(separator);
					sb.append(fieldFrontSpace + "public List<" + fieldType + "> " + key + ";");
					sb.append(separator);
					sb.append(separator);
					sb.append(fieldFrontSpace + "/** "+ fieldType +" is the inner class of "+ className +" */");
					sb.append(separator);
					sb.append(createObject((JSONObject)obj, fieldType, outCount+1));
				} else {
					String type = obj.getClass().getSimpleName();
					sb.append(separator);
					sb.append(fieldFrontSpace + "public " + type + " " + key + ";");
				}
			} catch (JSONException e) {
				e.printStackTrace();
			}
        }
		
		sb.append(separator);
		sb.append(classFrontSpace + "}");
		sb.append(separator);
		
		return sb.toString();
	}

七、原始碼及示例

    另外,歡迎 star or f**k me on github!  

八、一行引入庫

如果您的專案使用 Gradle 構建, 只需要在您的build.gradle檔案新增下面一行到dependencies:

compile 'com.kevin:jsontool:1.0.0'

九、結語

    相信大家瞭解了JSON格式以及和實體類的封裝原理後也能輕鬆編寫出自己的解析。