fastjson轉換json時,碰到的那些首字母大小寫轉換的坑!

agamem發表於2019-01-19

某年某月的某一天,本汪在某個奇葩的公司,接手了某個奇葩的專案,遇到了一些奇葩的事情,就掉進關於fastjson做bean to json轉換時,那些關於首字元大小寫的坑。

這個奇葩專案裡面,api介面定義的是天馬行空、雲山霧繞,api裡面的欄位定義更是五花八門、千奇百怪,完全沒有規則可言,都可以開個不符合規範的案例博物館了。下面3個坑裡面舉的例子,那些奇葩的名稱定義,都是在專案裡面真實存在的,如有雷同,純屬不幸。

在api的處理過程裡面,bean轉換成json時,我們總是希望欄位名是什麼樣的,轉換成json就應該是什麼樣的,然而現實總是殘酷的。

坑0x01 全大寫的鍵名

這種情況在api定義裡面很常見,如下:

private String TEST = “1”;
public String getTEST() { return this.TEST}

在fastjson轉換後,變成:json{ “tEST”: ”1”},與預期不符,首字母變成小寫了。

原因:
fastjson在將bean轉換為json時,先取出對應的methodName: getTEST,從methodName的第4個字元開始,取出propertyName:TEST,它預設認為這個propertyName的首字母是在定義getter的時候被寫成大寫的,現在要轉成小寫:
propertyName = Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4);
結果,正確的TEST就被轉成錯誤的tEST了。

解決:
解決這個問題比較簡單,在轉換前,多加一行程式碼:
TypeUtils.compatibleWithJavaBean =true;
此時,fastjson會先判斷propertyName長度大於1、且頭兩個字元都是大寫時,不做轉換:

if(name.length() > 1 && Character.isUpperCase(name.charAt(1)) && Character.isUpperCase(name.charAt(0))){
    return name;
}

也就是說,連續大寫開頭的propertyName,在轉換時,會保持原樣。

坑0x02 第一個字元大寫,第二個字元不是大寫的鍵名

按坑0x01裡面講的設定TypeUtils.compatibleWithJavaBean=true後,如果有如下的鍵名,依然會出問題:

private String T_EST = “1”;
private String Test = “2”;
public String getT_EST() { return this.T_EST}
public String getTest() { return this.Test}

在fastjson轉換後,變成:{ “t_EST”: ”1”, “test”:”2”},又是一個坑。

原因:
從坑0x01的最後一段程式碼可以看出,要想fastjson不改首字元,除了需要設定compatibleWithJavaBean外,propertyName的第二個字元也必須是大寫。在坑0x02這裡,兩個propertyName都無法通過判斷,首字母就要被轉換成小寫了。

解決
要解決這個問題,在轉換前,必須再加一行程式碼:
TypeUtils.compatibleWithFieldName = true;
Fastjson裡面對應的處理程式碼如下:

if(compatibleWithFieldName){
    if(!fieldCacheMap.containsKey(propertyName)){
        String tempPropertyName = methodName.substring(fromIdx);
        return fieldCacheMap.containsKey(tempPropertyName) ? tempPropertyName : propertyName;
    }
}

fastjson在按坑0x01所述的過程處理完propertyName後,propertyName的首字元被轉換為小寫,然後會在bean的的field列表裡面找一遍轉換後的名稱。如果找不到,就從methodName裡面重新取get後面的字串,然後再到field列表裡面找一遍,如果找到,就用原propertyName,如果找不到就用首字元被轉換的名稱。

坑0x03 首字元小寫,第二個字元大寫的鍵名

這個坑與lombok相關,嚴格來說,應該是lombok挖的坑。
如上所述,就算你按坑0x01、0x02設定了,如果在工程裡面用lombok時,有如下的鍵名定義,依然要被坑:

@Getter
private String iPhone = “1”;

在fastjson轉換後,變成:{ “IPhone”: ”1” },首字元變大寫了,WTF!

原因:
Lombok在自動生成getter的時候,會把propertyName的第一的字母改成大寫,等同如下程式碼:

public String getIPhone(){return this.iPhone;}

問題就出在這個轉換上,get後面緊跟的字元變成大寫了。在eclipse、idea裡面,如果自動生成程式碼,get後的字元是小寫。

如前面坑0x01最後一段程式碼,fastjson在處理getter時,會判斷前兩個字元是不是大寫,如果是的話,就保持原樣,取到的propertyName就成了:IPhone。但是在field列表裡面,對應的propertyName是iPhone,就算開啟了compatibleWithFieldName,fastjson用從getter中解析出來的IPhone,在field列表裡面也找不到對應值,也只能保持IPhone這個名稱了。

另外,就算在屬性前用了@JSONField(name = “iPhone”)註解,因為fastjson用從getter解析出來的propertyName找不到對應的field,也無法讀出該field對應的註解,這個註解也是無效的。

解決

  1. 碰到這種propertyName,就不要用lombok生成的getter/setter了,自己寫,保持首字元小寫,如:getiPhone/setiPhone,只要在程式碼裡面有這兩個方法,lombok就不會自動生成,lombok是按忽略大小寫後的propertyName判斷的。
  2. 把propertyName第二個字元改成小寫,或者重新取個名字,並用@JSONField註明正確的名稱,如:

    @Getter
    @ JSONField(name = “iPhone”)
    private String iphone = “1”;

    關於lombok的這個坑,可以參考:http://xxxx.ooo/2017/using-lo…,lombok的作者也是死鴨子嘴硬,被提了n多的issue,就是不改。

以上,就是本汪在專案裡面填坑的過程,不要問我為什麼api裡面的欄位名稱會定義成這副模樣,本汪也不知道,汪!汪!!汪!!!

關於fastjson的程式碼,都在TypeUtils.computeGetters裡面。
@JSONField是個好東西,不嫌麻煩的話,就都加上吧。

相關文章