代碼在最后
我個人是不太喜歡http和json,可能是游戲做的多了的原因的,對通信協議和通信方式特敏感,因此即使是做應用我也會選擇rpc而非http,但是有時候因為各種原因,還是不的不處理標准的http+json的東西。
這一次也確實需要處理一大串json,就是將一大堆的json轉換成標准的java pojo。也許小json串我們可以直接用JSONObject去提值就行了,但是如果json是這樣:
一個擁有近百個不一樣的字段的pojo,如果我們需要單獨的去取值估計會瘋掉,這還不是主要的,更蛋疼的是pojo本身的屬性又是array或者其他pojo,這樣依次嵌套,估計我已經瘋了。另外還有一個問題就是如果字段名稱一修改,就的手動去修改get的那個名稱,完全是苦力活。
(ps:上面那個圖,是nutch+es返回的值,我自己弄了一個搜索引擎玩,所有返回有大坨的數據,但是我實際處理的不是這個數據,這里只是用它舉例,自己搞的搜索引擎在:
http://search.bucry.com/ 純粹是為了好玩而已)
但是我又不得不面對這個問題,就是把這一大串json弄成pojo,於是我自然想到偷懶,想用一個東西自動的將它封裝成pojo,自動識別pojo的字段,自動從json中去取,並且自動調用set賦值,那么即使后面修改了字段名稱,又怎樣?無所謂,它本身就是反射,於是開始動手做,我需要解決的問題如下:
1.遍歷pojo的屬性,拿到它的屬性的這個變量的名稱
2.根據屬性的名稱,從JSONObject里面去get值
3.在JSONObject里面get值的時候是需要知道變量的類型的,如果它是一個pojo,那么繼續遞歸調用走 1
4.在JSONObject里面get值的時候如果是一個List活着Array那么使用JSONArray,然后通過String取出值,再判斷String,遞歸走2
5.反射調用set方法賦值
6.包裝成功
要解決上面的問題,首先我想到的是反射,但是反射在將其反射的時候必須知道類全路徑,於是我這個東西有其局限性:
pojo類必須有這個字段:
private String className = RowResponse.class.getName();
也就是服務器在tojson的時候把這個字段傳給客戶端,客戶端在原封不動的傳送給服務器,那么就能夠成功的通過遞歸自動封裝所有的pojo,有人會說這樣多一個字段數據量會增大,會使通信變慢的,這里我想說的是,json已經大到我需要這樣去處理pojo的程度了,還管個卵的速度,這一大坨的東西注定它快不了。
首先我們必須有兩個方法,一個是處理JSONObject,另一個是處理JSONArray 的,然后它們之間會相互交叉調用,它們本身會相互遞歸調用:
public Object translateFromJson(JSONObject jsonObject) throws Exception { JSONType jsonType = JSONType.JSONOBJECT; Class<?> baseClass = Class.forName(jsonObject.getString("className")); Object object = baseClass.newInstance(); Field[] fields = baseClass.getDeclaredFields(); for (Field filed : fields) { Class<?> filedType = filed.getType(); Object filedValue = null; if ("serialVersionUID".equals(filed.getName())) { continue; } if (filedType.getCanonicalName().contains("int") || filedType.getCanonicalName().contains("Integer")) { filedValue = jsonObject.getInt(filed.getName()); } else if (filedType.getCanonicalName().contains("String")) { filedValue = jsonObject.getString(filed.getName()); } else if (filedType.getCanonicalName().contains("List")) { jsonType = JSONType.JSONARRAY; filedValue = jsonObject.getJSONArray(filed.getName()); } else if (filedType.getCanonicalName().contains("Long") || filedType.getCanonicalName().contains("long")) { filedValue = jsonObject.getLong(filed.getName()); } else if (filedType.getCanonicalName().contains("Double") || filedType.getCanonicalName().contains("double")) { filedValue = jsonObject.getDouble(filed.getName()); } else if (filedType.getCanonicalName().contains("Boolean") || filedType.getCanonicalName().contains("boolean")) { filedValue = jsonObject.getBoolean(filed.getName()); } else { jsonType = JSONType.JSONOBJECT; filedValue = jsonObject.getJSONObject(filed.getName()); } if (filedValue == null || filedValue.toString().length() == 0) { continue; } if (!filedValue.toString().contains("[{") && !filedValue.toString().contains("]}") && !filedValue.toString().contains("className")) { String firstMethodNameChar = filed.getName().substring(0, 1); String methodName = "set" + firstMethodNameChar.toUpperCase() + filed.getName().substring(1, filed.getName().length()); Method method = baseClass.getMethod(methodName, filed.getType()); method.invoke(object, filedValue); } else if (filedValue.toString().contains("className")) { Object subClassObject = null; switch (jsonType) { case JSONARRAY: subClassObject = translateFromJson((JSONArray)filedValue); break; case JSONOBJECT: subClassObject = translateFromJson((JSONObject)filedValue); break; } String firstMethodNameChar = filed.getName().substring(0, 1); String methodName = "set" + firstMethodNameChar.toUpperCase() + filed.getName().substring(1, filed.getName().length()); Method method = baseClass.getMethod(methodName, filed.getType()); method.invoke(object, subClassObject); } else { Object subClassObject = null; switch (jsonType) { case JSONARRAY: subClassObject = translateFromJson((JSONArray)filedValue); break; case JSONOBJECT: subClassObject = translateFromJson((JSONObject)filedValue); break; } String firstMethodNameChar = filed.getName().substring(0, 1); String methodName = "set" + firstMethodNameChar.toUpperCase() + filed.getName().substring(1, filed.getName().length()); Method method = baseClass.getMethod(methodName, filed.getType()); method.invoke(object, subClassObject); } } return object; }
public Object translateFromJson(JSONArray jsonObject) throws Exception { List<Object> outputStringList = new LinkedList<Object>(); for(int i=0; i<jsonObject.length(); i++){ String filedValue = jsonObject.get(i).toString(); if (filedValue.contains("className")) { JSONObject jsonObject1 = new JSONObject(filedValue); outputStringList.add(translateFromJson(jsonObject1)); } else { outputStringList.add(filedValue); } } return outputStringList; }
處理過程如下:
1.根據className反射出了這個類的一個實例,由於是進入JSONObjct那么它一定是pojo,否則它就是基本數據類型,是不可能進入該方法的
2.遍歷實例的所有屬性並且從JSONObject去取值
3.通過反射的getType方法獲得對應的類,這里需要區分基本類型與包裝類型
4.如果是List那么就走array的方法,如果是JSONObject那么繼續遞歸自己
5.JSONArray直接解析,如果拿出的 String包含className,那么它是pojo繼續遞歸JSONObject,否則結束,直接add成ArrayList<Object>
6.如果是基本數據類型,那么直接通過反射調用set 賦值
7.如果是List,那么在遞歸后JSONArray會返回一個List<Object> 直接set
到這里,基本搞定了,然后近百號字段也能夠自動封裝了,反正省去了我一大把的去get值的時間。