在web開發(fā)過程中離不開數(shù)據(jù)的交互,這就需要規(guī)定交互數(shù)據(jù)的相關(guān)格式,以便數(shù)據(jù)在客戶端與服務(wù)器之間進行傳遞。數(shù)據(jù)的格式通常有2種:1、xml;2、json。通常來說都是使用json來傳遞數(shù)據(jù)。本文正是介紹在java中json與對象之間互相轉(zhuǎn)換時遇到的幾個問題以及相關(guān)的建議。
首先明確對于json有兩個概念:
json對象(javascript object notation,javascript對象表示法)。這看似只存是位javascript所定制的,但它作為一種語法是獨立于語言以及平臺的。只是說通常情況下我們在客戶端(瀏覽器)向服務(wù)器端傳遞數(shù)據(jù)時,使用的是json格式,而這個格式是用于表示javascript對象。它是由一系列的“key-value”組成,如 {“id”: 1, “name”: “kevin”},這有點類似map鍵值對的存儲方式。在java中所述的json對象,實際是指的jsonobject類,這在各個第三方的jsonjar包中通常都以這個名字命名,不同jar包對其內(nèi)部實現(xiàn)略有不同。
json字符串。json對象和json字符串之間的轉(zhuǎn)換是序列化與反序列化的過程,這就是好比java對象的序列化與反序列化。在網(wǎng)絡(luò)中數(shù)據(jù)的傳遞是通過字符串,或者是二進制流等等進行的,也就是說在客戶端(瀏覽器)需要將數(shù)據(jù)以json格式傳遞時,此時在網(wǎng)絡(luò)中傳遞的是字符串,而服務(wù)器端在接收到數(shù)據(jù)后當(dāng)然也是字符串(string類型),有時就需要將json字符串轉(zhuǎn)換為json對象再做下一步操作(string類型轉(zhuǎn)換為jsonobject類型)。
以上兩個概念的明確就基本明確了json這種數(shù)據(jù)格式,或者也稱之為json語法。java中對于json的jar包有許多,最最“常用”的是“net.sf.json”提供的jar包了,本文要著重說的就是這個坑包,雖然坑,卻有著廣泛的應(yīng)用。其實還有其他優(yōu)秀的json包供我們使用,例如阿里號稱最快的json包——fastjson,還有谷歌的gson,還有jackson。盡量,或者千萬不要使用“net.sf.json”包,不僅有坑,而且已經(jīng)很老了,老到都沒法在idea里下載到源碼,maven倉庫里顯示它2010年在2.4版本就停止更新了。下面就談我已知的“net.sf.json”的2個bug(我認為這是bug),以及這2個bug是如何產(chǎn)生的。
java中的json坑包——net.sf.json
1. 在java對象轉(zhuǎn)換json對象時,get開頭的所有方法會被轉(zhuǎn)換
這是什么意思呢,例如現(xiàn)有以下java對象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
package sfjson; import java.util.list; /** * created by kevin on 2017/12/1. */ public class student { private int id; private list< long > courseids; public int getid() { return id; } public void setid( int id) { this .id = id; } public list< long > getcourseids() { return courseids; } public void setcourseids(list< long > courseids) { this .courseids = courseids; } public string getsql() { //此類中獲取sql語句的方法,并沒有對應(yīng)的屬性字段 return "this is sql." ; } } |
在我們將student對象轉(zhuǎn)換成json對象的時候,希望轉(zhuǎn)換后的json格式應(yīng)該是:
1
2
3
4
|
{ "id" : 1 , "courseids" : [ 1 , 2 , 3 ] } |
然而在使用“net.sf.json”包的jsonobject json = jsonobject.fromobject(student); api轉(zhuǎn)換后的結(jié)果卻是:
也就是說可以猜測到的是,“net.sf.json”獲取java對象中public修飾符get開頭的方法,并將其后綴定義為json對象的“key”,而將get開頭方法的返回值定義為對應(yīng)key的“value”,注意是public修飾符get開頭的方法,且有返回值。
我認為這是不合理的轉(zhuǎn)換規(guī)則。如果我在java對象中定義了一個方法,僅僅因為這個方法是“get”開頭,且有返回值就將其作為轉(zhuǎn)換后json對象的“key-value”,那豈不是暴露出來了?或者在返回給客戶端(瀏覽器)時候就直接暴露給了前端的console控制臺?作者規(guī)定了這種轉(zhuǎn)換規(guī)則,我想的大概原因是:既然你定義為了public方法,且命名為get,那就是有意將此方法暴露出來讓調(diào)用它的客戶端有權(quán)獲取。但我仍然認為這不合理,甚至我定義它是一個bug。我這么定義也許也不合理,因為據(jù)我實測發(fā)現(xiàn),不僅是“net.sf.json”包會按照這個規(guī)則進行轉(zhuǎn)換,fastjson和jackson同樣也是照此規(guī)則,唯獨谷歌的gson并沒有按照這個規(guī)則進行對象向json轉(zhuǎn)換。
通過jsonobject json = jsonobject.fromobject(student);將構(gòu)造好的student對象轉(zhuǎn)換為json對象,student如上文所述。 進入此方法后會繼續(xù)調(diào)用fromobject(object, jsonconfig)的重載方法,在此重載方法中會通過instanceof判斷待轉(zhuǎn)換的object對象是否是枚舉、注解等類型,這些特殊類型會有特別的判斷方法。在這里是一個普通的java pojo對象,所以會進入到_fromobject(object, jsonconfig),在這個方法中會有一些判斷,而最后則通過調(diào)用defaultbeanprocessing創(chuàng)建json對象。這個方法是關(guān)鍵,在里面還繼續(xù)會通過propertyutils.getpropertydescriptors(bean)方法獲取“屬性描述符”,實際上就是獲取帶get的方法,它在這里封裝成了propertydescriptor。這student這個類中會獲取4個,分別是:getclass、getid、getcourseids、getsql。
其實propertydescriptor封裝得已經(jīng)很詳細了,什么讀寫方法都已經(jīng)賦值了。
例如這個getsql方法已經(jīng)被解析成了上圖的propertydescriptor。之后的通過這個類將一些方法過濾掉,例如getclass方法不是pojo中的方法,所以并不需要將它轉(zhuǎn)換成json對象。而propertydescriptor的獲取是通過beaninfo#getpropertydescriptors,而beaninfo的獲取則又是通過new introspector(beanclass, null, use_all_beaninfo).getbeaninfo();不斷深入最后就會到達如下方法。
1
2
3
4
5
|
private beaninfo getbeaninfo() throws introspectionexception { … methoddescriptor mds[] = gettargetmethodinfo(); //這個方法中會調(diào)用getpublicdeclaredmethods,可以看到確實是查找public方法,而且是所有public方法,包括wait等 propertydescriptor pds[] = gettargetpropertyinfo(); //按照一定的規(guī)則進行過濾,過濾規(guī)則全在這個方法里了,就是選擇public修飾符帶有g(shù)et前綴和返回值的方法 … |
對net.sf.json的源碼簡要分析了一下,發(fā)現(xiàn)確實如猜想的那樣,具體的源碼比較多篇幅有限需自行查看跟蹤。
2. 在json對象轉(zhuǎn)換java對象時,list<long>會出現(xiàn)轉(zhuǎn)換錯誤
標(biāo)題一句話解釋不清楚,這個問題,我很確定地認為它是一個bug。
現(xiàn)在有{"id": 1, "courseids": [1,2,3]}的json字符串,需要將它轉(zhuǎn)換為上文中提到的student對象,在student對象中有int和list<long>類型的兩個屬性字段,也就是說這個json字符串應(yīng)該轉(zhuǎn)換為對應(yīng)的數(shù)據(jù)類型。
1
2
3
|
string json = "{\"id\": 1, \"courseids\": [1,2,3]}" ; student student = (student) jsonobject.tobean(jsonobject.fromobject(json), student. class ); system.out.println(student.getcourseids().get( 0 ) instanceof long ); |
上面的輸出結(jié)果應(yīng)該是true,然而遺憾的是卻是false。準確來說在編譯時是long型,而在運行時卻是integer。這不得不說就是一個坑了,另外三個json包都未出現(xiàn)這種錯誤。所以我確定它是一個bug。來看看這個bug在net.sf.json是怎么發(fā)生的,同樣需要自行對比源碼進行查看。我在打斷點debug不斷深入的時候發(fā)現(xiàn)了net.sf.json對于整型數(shù)據(jù)的處理時,發(fā)現(xiàn)了這個方法numberutils#createnumber,這個類是從字符串中取出數(shù)據(jù)時判斷它的數(shù)據(jù)類型,本意是想如果數(shù)字后面帶有“l”或“l”則將其處理為long型,從這里來看最后的結(jié)果應(yīng)該是對的啊。
1
2
3
4
5
6
7
8
9
10
11
|
case 'l' : case 'l' : if (dec == null && exp == null && (numeric.charat( 0 ) == '-' && isdigits(numeric.substring( 1 )) || isdigits(numeric))) { try { return createlong(numeric); } catch (numberformatexception var11) { return createbiginteger(numeric); } } else { throw new numberformatexception(str + " is not a valid number." ); } |
的確到目前為止net.sf.json通過數(shù)字后的標(biāo)識符準確地判斷了數(shù)據(jù)類型,問題出就出在獲得了這個值以及它的數(shù)據(jù)類型后需要將它存入jsonobject中,而存入的過程中有jsonutils#transformnumber這個方法的存在,這個方法的存在,至少在目前看來純屬畫蛇添足。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
public static number transformnumber(number input) { if (input instanceof float ) { return new double (input.tostring()); } else if (input instanceof short ) { return new integer(input.intvalue()); } else if (input instanceof byte ) { return new integer(input.intvalue()); } else { if (input instanceof long ) { long max = new long (2147483647l); if (input.longvalue() <= max.longvalue() && input.longvalue() >= -2147483648l) { //就算原類型是long型,但是只要它在integer范圍,那么就最終還是會轉(zhuǎn)換為integer。 return new integer(input.intvalue()); } } return input; } } |
上面的這段代碼很清晰的顯示了元兇所在,不論是long型(integer范圍內(nèi)的long型),包括byte、short都會轉(zhuǎn)換為integer。尚不明白這段代碼的意義在哪里。前面又要根據(jù)數(shù)字后的字母確定準確的數(shù)據(jù)類型,后面又要將準確的數(shù)據(jù)類型轉(zhuǎn)換一次,這就導(dǎo)致了開頭提到的那個bug。這個問題幾乎是無法回避,所以最好的辦法就是不要用。
這兩個坑是偶然間發(fā)現(xiàn),建議還是不要使用早已沒有維護的net.sf.json的json包,另外有一點,net.sf.json包對json格式的校驗并不那么嚴格,如果這樣的格式“{"id": 1, "courseids": "[1,2,3]"}”,在其他三個包是會拋出異常的,但net.sf.json則不會。
以上這篇詳談java中net.sf.json包關(guān)于json與對象互轉(zhuǎn)的坑就是小編分享給大家的全部內(nèi)容了,希望能給大家一個參考,也希望大家多多支持服務(wù)器之家。
原文鏈接:http://www.cnblogs.com/yulinfeng/archive/2017/12/03/7967603.html