溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

詳談Java中net.sf.json包關于JSON與對象互轉的坑

發(fā)布時間:2020-08-26 08:17:13 來源:腳本之家 閱讀:209 作者:OKevin 欄目:編程語言

在Web開發(fā)過程中離不開數(shù)據(jù)的交互,這就需要規(guī)定交互數(shù)據(jù)的相關格式,以便數(shù)據(jù)在客戶端與服務器之間進行傳遞。數(shù)據(jù)的格式通常有2種:1、xml;2、JSON。通常來說都是使用JSON來傳遞數(shù)據(jù)。本文正是介紹在Java中JSON與對象之間互相轉換時遇到的幾個問題以及相關的建議。

首先明確對于JSON有兩個概念:

JSON對象(JavaScript Object Notation,JavaScript對象表示法)。這看似只存是位JavaScript所定制的,但它作為一種語法是獨立于語言以及平臺的。只是說通常情況下我們在客戶端(瀏覽器)向服務器端傳遞數(shù)據(jù)時,使用的是JSON格式,而這個格式是用于表示JavaScript對象。它是由一系列的“key-value”組成,如 {“id”: 1, “name”: “kevin”},這有點類似Map鍵值對的存儲方式。在Java中所述的JSON對象,實際是指的JSONObject類,這在各個第三方的JSONjar包中通常都以這個名字命名,不同jar包對其內部實現(xiàn)略有不同。

JSON字符串。JSON對象和JSON字符串之間的轉換是序列化與反序列化的過程,這就是好比Java對象的序列化與反序列化。在網(wǎng)絡中數(shù)據(jù)的傳遞是通過字符串,或者是二進制流等等進行的,也就是說在客戶端(瀏覽器)需要將數(shù)據(jù)以JSON格式傳遞時,此時在網(wǎng)絡中傳遞的是字符串,而服務器端在接收到數(shù)據(jù)后當然也是字符串(String類型),有時就需要將JSON字符串轉換為JSON對象再做下一步操作(String類型轉換為JSONObject類型)。

以上兩個概念的明確就基本明確了JSON這種數(shù)據(jù)格式,或者也稱之為JSON語法。Java中對于JSON的jar包有許多,最最“常用”的是“net.sf.json”提供的jar包了,本文要著重說的就是這個坑包,雖然坑,卻有著廣泛的應用。其實還有其他優(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對象轉換JSON對象時,get開頭的所有方法會被轉換

這是什么意思呢,例如現(xiàn)有以下Java對象。

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語句的方法,并沒有對應的屬性字段
    return "this is sql.";
  }
}

在我們將Student對象轉換成JSON對象的時候,希望轉換后的JSON格式應該是:

{
   "id": 1,
   "courseIds": [1, 2, 3]
 }

然而在使用“net.sf.json”包的JSONObject json = JSONObject.fromObject(student); API轉換后的結果卻是:

詳談Java中net.sf.json包關于JSON與對象互轉的坑

也就是說可以猜測到的是,“net.sf.json”獲取Java對象中public修飾符get開頭的方法,并將其后綴定義為JSON對象的“key”,而將get開頭方法的返回值定義為對應key的“value”,注意是public修飾符get開頭的方法,且有返回值。

我認為這是不合理的轉換規(guī)則。如果我在Java對象中定義了一個方法,僅僅因為這個方法是“get”開頭,且有返回值就將其作為轉換后JSON對象的“key-value”,那豈不是暴露出來了?或者在返回給客戶端(瀏覽器)時候就直接暴露給了前端的Console控制臺?作者規(guī)定了這種轉換規(guī)則,我想的大概原因是:既然你定義為了public方法,且命名為get,那就是有意將此方法暴露出來讓調用它的客戶端有權獲取。但我仍然認為這不合理,甚至我定義它是一個bug。我這么定義也許也不合理,因為據(jù)我實測發(fā)現(xiàn),不僅是“net.sf.json”包會按照這個規(guī)則進行轉換,fastjson和jackson同樣也是照此規(guī)則,唯獨谷歌的GSON并沒有按照這個規(guī)則進行對象向JSON轉換。

通過JSONObject json = JSONObject.fromObject(student);將構造好的Student對象轉換為JSON對象,Student如上文所述。 進入此方法后會繼續(xù)調用fromObject(Object, JsonConfig)的重載方法,在此重載方法中會通過instanceOf判斷待轉換的Object對象是否是枚舉、注解等類型,這些特殊類型會有特別的判斷方法。在這里是一個普通的Java POJO對象,所以會進入到_fromObject(Object, JsonConfig),在這個方法中會有一些判斷,而最后則通過調用defaultBeanProcessing創(chuàng)建JSON對象。這個方法是關鍵,在里面還繼續(xù)會通過PropertyUtils.getPropertyDescriptors(bean)方法獲取“屬性描述符”,實際上就是獲取帶get的方法,它在這里封裝成了PropertyDescriptor。這Student這個類中會獲取4個,分別是:getClass、getId、getCourseIds、getSql。

詳談Java中net.sf.json包關于JSON與對象互轉的坑

其實PropertyDescriptor封裝得已經(jīng)很詳細了,什么讀寫方法都已經(jīng)賦值了。

詳談Java中net.sf.json包關于JSON與對象互轉的坑

例如這個getSql方法已經(jīng)被解析成了上圖的PropertyDescriptor。之后的通過這個類將一些方法過濾掉,例如getClass方法不是POJO中的方法,所以并不需要將它轉換成JSON對象。而PropertyDescriptor的獲取是通過BeanInfo#getPropertyDescriptors,而BeanInfo的獲取則又是通過new Introspector(beanClass, null, USE_ALL_BEANINFO).getBeanInfo();不斷深入最后就會到達如下方法。

private BeanInfo getBeanInfo() throws IntrospectionException {
  …
  MethodDescriptor mds[] = getTargetMethodInfo();  //這個方法中會調用getPublicDeclaredMethods,可以看到確實是查找public方法,而且是所有public方法,包括wait等
  PropertyDescriptor pds[] = getTargetPropertyInfo();  //按照一定的規(guī)則進行過濾,過濾規(guī)則全在這個方法里了,就是選擇public修飾符帶有get前綴和返回值的方法
  …

對net.sf.json的源碼簡要分析了一下,發(fā)現(xiàn)確實如猜想的那樣,具體的源碼比較多篇幅有限需自行查看跟蹤。

2. 在JSON對象轉換Java對象時,List<Long>會出現(xiàn)轉換錯誤

標題一句話解釋不清楚,這個問題,我很確定地認為它是一個bug。

現(xiàn)在有{"id": 1, "courseIds": [1,2,3]}的JSON字符串,需要將它轉換為上文中提到的Student對象,在Student對象中有int和List<Long>類型的兩個屬性字段,也就是說這個JSON字符串應該轉換為對應的數(shù)據(jù)類型。

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);

上面的輸出結果應該是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(fā)”則將其處理為Long型,從這里來看最后的結果應該是對的啊。

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ù)字后的標識符準確地判斷了數(shù)據(jù)類型,問題出就出在獲得了這個值以及它的數(shù)據(jù)類型后需要將它存入JSONObject中,而存入的過程中有JSONUtils#transformNumber這個方法的存在,這個方法的存在,至少在目前看來純屬畫蛇添足。

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范圍,那么就最終還是會轉換為Integer。
        return new Integer(input.intValue());
      }
    }

    return input;
  }
}

上面的這段代碼很清晰的顯示了元兇所在,不論是Long型(Integer范圍內的Long型),包括Byte、Short都會轉換為Integer。尚不明白這段代碼的意義在哪里。前面又要根據(jù)數(shù)字后的字母確定準確的數(shù)據(jù)類型,后面又要將準確的數(shù)據(jù)類型轉換一次,這就導致了開頭提到的那個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包關于JSON與對象互轉的坑就是小編分享給大家的全部內容了,希望能給大家一個參考,也希望大家多多支持億速云。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內容。

AI