詳談Java中net.sf.json包關(guān)于JSON與對(duì)象互轉(zhuǎn)的坑
在Web開(kāi)發(fā)過(guò)程中離不開(kāi)數(shù)據(jù)的交互,這就需要規(guī)定交互數(shù)據(jù)的相關(guān)格式,以便數(shù)據(jù)在客戶(hù)端與服務(wù)器之間進(jìn)行傳遞。數(shù)據(jù)的格式通常有2種:1、xml;2、JSON。通常來(lái)說(shuō)都是使用JSON來(lái)傳遞數(shù)據(jù)。本文正是介紹在Java中JSON與對(duì)象之間互相轉(zhuǎn)換時(shí)遇到的幾個(gè)問(wèn)題以及相關(guān)的建議。
首先明確對(duì)于JSON有兩個(gè)概念:
JSON對(duì)象(JavaScript Object Notation,JavaScript對(duì)象表示法)。這看似只存是位JavaScript所定制的,但它作為一種語(yǔ)法是獨(dú)立于語(yǔ)言以及平臺(tái)的。只是說(shuō)通常情況下我們?cè)诳蛻?hù)端(瀏覽器)向服務(wù)器端傳遞數(shù)據(jù)時(shí),使用的是JSON格式,而這個(gè)格式是用于表示JavaScript對(duì)象。它是由一系列的“key-value”組成,如 {“id”: 1, “name”: “kevin”},這有點(diǎn)類(lèi)似Map鍵值對(duì)的存儲(chǔ)方式。在Java中所述的JSON對(duì)象,實(shí)際是指的JSONObject類(lèi),這在各個(gè)第三方的JSONjar包中通常都以這個(gè)名字命名,不同jar包對(duì)其內(nèi)部實(shí)現(xiàn)略有不同。
JSON字符串。JSON對(duì)象和JSON字符串之間的轉(zhuǎn)換是序列化與反序列化的過(guò)程,這就是好比Java對(duì)象的序列化與反序列化。在網(wǎng)絡(luò)中數(shù)據(jù)的傳遞是通過(guò)字符串,或者是二進(jìn)制流等等進(jìn)行的,也就是說(shuō)在客戶(hù)端(瀏覽器)需要將數(shù)據(jù)以JSON格式傳遞時(shí),此時(shí)在網(wǎng)絡(luò)中傳遞的是字符串,而服務(wù)器端在接收到數(shù)據(jù)后當(dāng)然也是字符串(String類(lèi)型),有時(shí)就需要將JSON字符串轉(zhuǎn)換為JSON對(duì)象再做下一步操作(String類(lèi)型轉(zhuǎn)換為JSONObject類(lèi)型)。
以上兩個(gè)概念的明確就基本明確了JSON這種數(shù)據(jù)格式,或者也稱(chēng)之為JSON語(yǔ)法。Java中對(duì)于JSON的jar包有許多,最最“常用”的是“net.sf.json”提供的jar包了,本文要著重說(shuō)的就是這個(gè)坑包,雖然坑,卻有著廣泛的應(yīng)用。其實(shí)還有其他優(yōu)秀的JSON包供我們使用,例如阿里號(hào)稱(chēng)最快的JSON包——fastjson,還有谷歌的GSON,還有jackson。盡量,或者千萬(wàn)不要使用“net.sf.json”包,不僅有坑,而且已經(jīng)很老了,老到都沒(méi)法在IDEA里下載到源碼,Maven倉(cāng)庫(kù)里顯示它2010年在2.4版本就停止更新了。下面就談我已知的“net.sf.json”的2個(gè)bug(我認(rèn)為這是bug),以及這2個(gè)bug是如何產(chǎn)生的。
Java中的JSON坑包——net.sf.json
1. 在Java對(duì)象轉(zhuǎn)換JSON對(duì)象時(shí),get開(kāi)頭的所有方法會(huì)被轉(zhuǎn)換
這是什么意思呢,例如現(xiàn)有以下Java對(duì)象。
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() { //此類(lèi)中獲取sql語(yǔ)句的方法,并沒(méi)有對(duì)應(yīng)的屬性字段 return "this is sql."; } }
在我們將Student對(duì)象轉(zhuǎn)換成JSON對(duì)象的時(shí)候,希望轉(zhuǎn)換后的JSON格式應(yīng)該是:
{ "id": 1, "courseIds": [1, 2, 3] }
然而在使用“net.sf.json”包的JSONObject json = JSONObject.fromObject(student); API轉(zhuǎn)換后的結(jié)果卻是:
也就是說(shuō)可以猜測(cè)到的是,“net.sf.json”獲取Java對(duì)象中public修飾符get開(kāi)頭的方法,并將其后綴定義為JSON對(duì)象的“key”,而將get開(kāi)頭方法的返回值定義為對(duì)應(yīng)key的“value”,注意是public修飾符get開(kāi)頭的方法,且有返回值。
我認(rèn)為這是不合理的轉(zhuǎn)換規(guī)則。如果我在Java對(duì)象中定義了一個(gè)方法,僅僅因?yàn)檫@個(gè)方法是“get”開(kāi)頭,且有返回值就將其作為轉(zhuǎn)換后JSON對(duì)象的“key-value”,那豈不是暴露出來(lái)了?或者在返回給客戶(hù)端(瀏覽器)時(shí)候就直接暴露給了前端的Console控制臺(tái)?作者規(guī)定了這種轉(zhuǎn)換規(guī)則,我想的大概原因是:既然你定義為了public方法,且命名為get,那就是有意將此方法暴露出來(lái)讓調(diào)用它的客戶(hù)端有權(quán)獲取。但我仍然認(rèn)為這不合理,甚至我定義它是一個(gè)bug。我這么定義也許也不合理,因?yàn)閾?jù)我實(shí)測(cè)發(fā)現(xiàn),不僅是“net.sf.json”包會(huì)按照這個(gè)規(guī)則進(jìn)行轉(zhuǎn)換,fastjson和jackson同樣也是照此規(guī)則,唯獨(dú)谷歌的GSON并沒(méi)有按照這個(gè)規(guī)則進(jìn)行對(duì)象向JSON轉(zhuǎn)換。
通過(guò)JSONObject json = JSONObject.fromObject(student);將構(gòu)造好的Student對(duì)象轉(zhuǎn)換為JSON對(duì)象,Student如上文所述。 進(jìn)入此方法后會(huì)繼續(xù)調(diào)用fromObject(Object, JsonConfig)的重載方法,在此重載方法中會(huì)通過(guò)instanceOf判斷待轉(zhuǎn)換的Object對(duì)象是否是枚舉、注解等類(lèi)型,這些特殊類(lèi)型會(huì)有特別的判斷方法。在這里是一個(gè)普通的Java POJO對(duì)象,所以會(huì)進(jìn)入到_fromObject(Object, JsonConfig),在這個(gè)方法中會(huì)有一些判斷,而最后則通過(guò)調(diào)用defaultBeanProcessing創(chuàng)建JSON對(duì)象。這個(gè)方法是關(guān)鍵,在里面還繼續(xù)會(huì)通過(guò)PropertyUtils.getPropertyDescriptors(bean)方法獲取“屬性描述符”,實(shí)際上就是獲取帶get的方法,它在這里封裝成了PropertyDescriptor。這Student這個(gè)類(lèi)中會(huì)獲取4個(gè),分別是:getClass、getId、getCourseIds、getSql。
其實(shí)PropertyDescriptor封裝得已經(jīng)很詳細(xì)了,什么讀寫(xiě)方法都已經(jīng)賦值了。
例如這個(gè)getSql方法已經(jīng)被解析成了上圖的PropertyDescriptor。之后的通過(guò)這個(gè)類(lèi)將一些方法過(guò)濾掉,例如getClass方法不是POJO中的方法,所以并不需要將它轉(zhuǎn)換成JSON對(duì)象。而PropertyDescriptor的獲取是通過(guò)BeanInfo#getPropertyDescriptors,而B(niǎo)eanInfo的獲取則又是通過(guò)new Introspector(beanClass, null, USE_ALL_BEANINFO).getBeanInfo();不斷深入最后就會(huì)到達(dá)如下方法。
private BeanInfo getBeanInfo() throws IntrospectionException { … MethodDescriptor mds[] = getTargetMethodInfo(); //這個(gè)方法中會(huì)調(diào)用getPublicDeclaredMethods,可以看到確實(shí)是查找public方法,而且是所有public方法,包括wait等 PropertyDescriptor pds[] = getTargetPropertyInfo(); //按照一定的規(guī)則進(jìn)行過(guò)濾,過(guò)濾規(guī)則全在這個(gè)方法里了,就是選擇public修飾符帶有g(shù)et前綴和返回值的方法 …
對(duì)net.sf.json的源碼簡(jiǎn)要分析了一下,發(fā)現(xiàn)確實(shí)如猜想的那樣,具體的源碼比較多篇幅有限需自行查看跟蹤。
2. 在JSON對(duì)象轉(zhuǎn)換Java對(duì)象時(shí),List<Long>會(huì)出現(xiàn)轉(zhuǎn)換錯(cuò)誤
標(biāo)題一句話解釋不清楚,這個(gè)問(wèn)題,我很確定地認(rèn)為它是一個(gè)bug。
現(xiàn)在有{"id": 1, "courseIds": [1,2,3]}的JSON字符串,需要將它轉(zhuǎn)換為上文中提到的Student對(duì)象,在Student對(duì)象中有int和List<Long>類(lèi)型的兩個(gè)屬性字段,也就是說(shuō)這個(gè)JSON字符串應(yīng)該轉(zhuǎn)換為對(duì)應(yīng)的數(shù)據(jù)類(lèi)型。
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。準(zhǔn)確來(lái)說(shuō)在編譯時(shí)是Long型,而在運(yùn)行時(shí)卻是Integer。這不得不說(shuō)就是一個(gè)坑了,另外三個(gè)JSON包都未出現(xiàn)這種錯(cuò)誤。所以我確定它是一個(gè)bug。來(lái)看看這個(gè)bug在net.sf.json是怎么發(fā)生的,同樣需要自行對(duì)比源碼進(jìn)行查看。我在打斷點(diǎn)debug不斷深入的時(shí)候發(fā)現(xiàn)了net.sf.json對(duì)于整型數(shù)據(jù)的處理時(shí),發(fā)現(xiàn)了這個(gè)方法NumberUtils#createNumber,這個(gè)類(lèi)是從字符串中取出數(shù)據(jù)時(shí)判斷它的數(shù)據(jù)類(lèi)型,本意是想如果數(shù)字后面帶有“L”或“l(fā)”則將其處理為L(zhǎng)ong型,從這里來(lái)看最后的結(jié)果應(yīng)該是對(duì)的啊。
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通過(guò)數(shù)字后的標(biāo)識(shí)符準(zhǔn)確地判斷了數(shù)據(jù)類(lèi)型,問(wèn)題出就出在獲得了這個(gè)值以及它的數(shù)據(jù)類(lèi)型后需要將它存入JSONObject中,而存入的過(guò)程中有JSONUtils#transformNumber這個(gè)方法的存在,這個(gè)方法的存在,至少在目前看來(lái)純屬畫(huà)蛇添足。
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) { //就算原類(lèi)型是Long型,但是只要它在Integer范圍,那么就最終還是會(huì)轉(zhuǎn)換為Integer。 return new Integer(input.intValue()); } } return input; } }
上面的這段代碼很清晰的顯示了元兇所在,不論是Long型(Integer范圍內(nèi)的Long型),包括Byte、Short都會(huì)轉(zhuǎn)換為Integer。尚不明白這段代碼的意義在哪里。前面又要根據(jù)數(shù)字后的字母確定準(zhǔn)確的數(shù)據(jù)類(lèi)型,后面又要將準(zhǔn)確的數(shù)據(jù)類(lèi)型轉(zhuǎn)換一次,這就導(dǎo)致了開(kāi)頭提到的那個(gè)bug。這個(gè)問(wèn)題幾乎是無(wú)法回避,所以最好的辦法就是不要用。
這兩個(gè)坑是偶然間發(fā)現(xiàn),建議還是不要使用早已沒(méi)有維護(hù)的net.sf.json的JSON包,另外有一點(diǎn),net.sf.json包對(duì)JSON格式的校驗(yàn)并不那么嚴(yán)格,如果這樣的格式“{"id": 1, "courseIds": "[1,2,3]"}”,在其他三個(gè)包是會(huì)拋出異常的,但net.sf.json則不會(huì)。
以上這篇詳談Java中net.sf.json包關(guān)于JSON與對(duì)象互轉(zhuǎn)的坑就是小編分享給大家的全部?jī)?nèi)容了,希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
spring?boot自帶的page分頁(yè)問(wèn)題
這篇文章主要介紹了spring?boot自帶的page分頁(yè)問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03Spring?MVC中JSON數(shù)據(jù)處理方式實(shí)戰(zhàn)案例
Spring MVC是個(gè)靈活的框架,返回JSON數(shù)據(jù)的也有很多五花八門(mén)的方式,下面這篇文章主要給大家介紹了關(guān)于Spring?MVC中JSON數(shù)據(jù)處理方式的相關(guān)資料,需要的朋友可以參考下2024-01-01java實(shí)現(xiàn)圖片上傳至本地實(shí)例詳解
我們給大家分享了關(guān)于java實(shí)現(xiàn)圖片上傳至本地的實(shí)例以及相關(guān)代碼,有需要的朋友參考下。2018-08-08SpringBoot設(shè)置靜態(tài)資源訪問(wèn)控制和封裝集成方案
這篇文章主要介紹了SpringBoot靜態(tài)資源訪問(wèn)控制和封裝集成方案,關(guān)于springboot靜態(tài)資源訪問(wèn)的問(wèn)題,小編是通過(guò)自定義webconfig實(shí)現(xiàn)WebMvcConfigurer,重寫(xiě)addResourceHandlers方法,具體完整代碼跟隨小編一起看看吧2021-08-08JavaWeb實(shí)戰(zhàn)之編寫(xiě)單元測(cè)試類(lèi)測(cè)試數(shù)據(jù)庫(kù)操作
這篇文章主要介紹了JavaWeb實(shí)戰(zhàn)之編寫(xiě)單元測(cè)試類(lèi)測(cè)試數(shù)據(jù)庫(kù)操作,文中有非常詳細(xì)的代碼示例,對(duì)正在學(xué)習(xí)javaweb的小伙伴們有很大的幫助,需要的朋友可以參考下2021-04-04mybatis?查詢(xún)返回Map<String,Object>類(lèi)型
本文主要介紹了mybatis?查詢(xún)返回Map<String,Object>類(lèi)型,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-03-03