最初接觸alibaba fastjson是由於其性能上的優勢,對比原來采用codehause.jackson的解析,在hadoop平台上的手動轉換對象有着將近1/3的性能提升,但隨着開發應用越來越多,漸漸地也發現了在其他方面的強大之處,例如直接轉化泛型(jackson對泛型的支持相對來說就比較差)。
Fastjson會自動處理對象中的泛型,將其解析成原來定義好的對象,建議一定要將泛型類型定義精確,以避免出現歧義,例如如果使用Map<String, Object> 就並不是一個定義良好的泛型,我們也並不知道Object對象真正表示的是什么,而fastjson解析Object時,由於不知道其具體類型,只能將其轉化成JSONObject(結構類似一個Map,有着key和value)。
泛型類型解析
如果頂層類型中存在泛型,就會出現不知道確切類型的狀況,因為Class是無法攜帶泛型信息的,Java中不存在一個類型為List<String>.class,如果這樣使用,Fastjson會將對象解析成JSONObject。
alibaba fastjson提供了一套TypeReference相關的方法來解決這個問題,從源代碼來看定義不是很長:
public class TypeReference<T> { private final Type type; protected TypeReference(){ Type superClass = getClass().getGenericSuperclass(); type = ((ParameterizedType) superClass).getActualTypeArguments()[0]; } public Type getType() { return type; } public final static Type LIST_STRING = new TypeReference<List<String>>() {}.getType(); }
構造函數中做了兩件事,先從當前的Class.getGenericSuperclass,返回該Class表示的實體或直接超類的Type,重點在與,如果是參數化類型,返回的Type中會帶上該類型,我們看一下這個方法的定義及其作用:
getGenericSuperclass
public Type getGenericSuperclass()
如果超類是參數化類型,則返回的 Type 對象必須准確反映源代碼中所使用的實際類型參數。如果以前未曾創建表示超類的參數化類型,則創建這個類型。有關參數化類型創建過程的語義,請參閱 ParameterizedType
聲明。如果此 Class 表示 Object 類、接口、基本類型或 void,則返回 null。如果此對象表示一個數組類,則返回表示 Object 類的 Class 對象。
GenericSignatureFormatError
- 如果常規類簽名不符合 Java Virtual Machine Specification, 3rd edition 規定的格式
TypeNotPresentException
- 如果常規超類引用不存在的類型聲明
MalformedParameterizedTypeException
- 如果常規超類引用的參數化類型由於某種原因無法實例化
從文檔中可以看出,如果其超類是參數化類型,返回的Type就為sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl類型,其繼承自java.lang.reflect.ParameterizedType,用來表示參數化類型。
參數化類型中就可以從getActualTypeArguments中獲取,因為TypeReference只有一個參數化類型定義,因此可以確定返回的數組肯定有一個元素(如果是Map就存在兩個元素),這樣type就為其參數化類型了,我們通過這個定義將對象的泛型傳入至后續的json處理邏輯中。
getActualTypeArguments
Type[] getActualTypeArguments()
返回表示此類型實際類型參數的
Type 對象的數組。
注意,在某些情況下,返回的數組為空。如果此類型表示嵌套在參數化類型中的非參數化類型,則會發生這種情況。
返回:
表示此類型的實際類型參數的
Type 對象的數組
拋出:
TypeNotPresentException
- 如果任何實際類型參數引用不存在的類型聲明
MalformedParameterizedTypeException
- 如果任何實際類型參數引用參數化類型,該類型出於某種原因無法被實例化
從以下版本開始:
1.5
所以使用該類的方法只有一種,就是繼承,因此其構造方法為protected,只能允許子類來調用,在構造該對象時,就需要在聲明最后加上{}來表示繼承關系。
java.util.Map<java.lang.String, com.xxx.commons.json.test.JsonTest$InnerObj>
關於類型轉換
如果是數組,由於某種對象的數組是存在對應的Class類型信息的,也就是說可以直接直接以Class的方式轉換成功。
當時如果沒有指定類型,就會轉換成JSONObject,在其中以key/value的方式(類似Map)存儲。
如果是數組,轉成JSONArray,在其中包裝JSONObject

如果我們使用TypeReference,指定其為List<String>,而List中其實是對象,那么直接轉換成String,此時字符串就為json字符串。
如果事先不知道該json串的類型信息(json事先也無法知道其類型信息,而Java的序列化,是帶類型信息的,因此攜帶的內容也要比json串豐富地多,耗費網絡帶寬也大),或者在Map中寫不出統一的泛型,那么就會發生隱性的類型轉換過程。在codehaus jackson中會將無法識別的json對象直接轉換成Map(key,value)格式,如果是fastjson,則轉換成JSONObject(其實也類似於json)。如果需要將該對象仍然轉換成json格式,不會有任何問題,但如果想要以原來的對象方式操作它,就會出現問題了(因為沒有對象信息,除非手動將其轉換,但每部分都需要手寫代碼,比較麻煩)。
Map中字段為null不被持久化的問題
如果Map中的某個屬性為null,並不會被持久化,可以參考
如果想讓Map中的字段能夠正常輸出(這也是我們需要的),就需要在轉換toJSONString時設置特殊的Feature來完成該功能:
JSON.toJSONString(object, SerializerFeature.WriteMapNullValue)
關於Feature
Fastjson中存在多種SerializerFeature,可以按照需要使用,例如
- SerializerFeature.UseSingleQuotes,//支持單引號
- SerializerFeature.WriteDateUseDateFormat,//日期格式化
- SerializerFeature.WriteMapNullValue, // 輸出空置字段
- SerializerFeature.WriteNullListAsEmpty, // list字段如果為null,輸出為[],而不是null
- SerializerFeature.WriteNullNumberAsZero, // 數值字段如果為null,輸出為0,而不是null
- SerializerFeature.WriteNullBooleanAsFalse, // Boolean字段如果為null,輸出為false,而不是null
- SerializerFeature.WriteNullStringAsEmpty // 字符類型字段如果為null,輸出為"",而不是null
很多場景中,我們需要序列化的對象中存在循環引用,在許多的json庫中,這會導致stackoverflow。在功能強大的fastjson中,你不需要擔心這個問題。
使用@JSONField Annotation來定制化序列化字段的名稱,還可以使用@JSONCreator來指定構造函數來創建對象,配合@JSONField來確定字段的名稱。
如果使用新特性SerializerFeature.WriteClassName,可以在序列化的文本中加入@type信息,這樣就可以進行合理的反序列化,因為知道了類型信息,可以通過默認的構造以及屬性名稱自動設置,此時反序列化的結果類似下面:
{"@type":"java.awt.Color","r":255,"g":0,"b":0,"alpha":255}
此時就可以進行自動識別,但此時輸出的文本就會多出一倍的空間,因此如果有着明確的類型,不建議使用這種方式。
String text = ...; // {"@type":"java.awt.Color","r":255,"g":0,"b":0,"alpha":255} Color color = (Color) JSON.parse(text);
這樣可以解決一些序列化泛型中接口的問題,
還舉例說明了可以使用組合類型,fastjson對這種結構的反序列化有專門支持:
String text = ...; // [{/* header */}, {/* body */}] Type[] types = new Type[] {Header.class, Body.class}; List<Object> list = JSON.parseArray(text, types); Header header = (Header) list.get(0); Body body = (Body) list.get(1);
在使用WriteClassName這個Feature來進行寫Map等操作時,有一個問題,其中的List以及數組在被轉化回來時無法識別,會被自動轉換成JSONArray而不是原始的List,數組對象。如果用戶原來使用的List,這個問題可以得到完美的解決,看一下JSONArray的定義,
/** * @author wenshao[szujobs@hotmail.com] */ public class JSONArray extends JSON implements List<Object>, Cloneable, RandomAccess, Serializable {
可以對其進行強制轉換List即可。
但是如果原始類型為數組類型,則只能夠通過toArray方法,至於網友提出的能否直接使用Object[],其開發者給出了預期(只不過在1.2.9上也並未見其實現)。

但在1.2.9版本並沒有看到這個Feature,所以還是拭目以待吧。
<!--?xml version="1.0" encoding="UTF-8" standalone="no"?-->
此外,我們在使用SpringMVC時,默認使用的json格式RequestMapper轉換實現應該是jackson實現的,當然可以將其替換成fastjson的實現:
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping" /> <!-- HandlerAdapter --> <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"> <property name="messageConverters"> <list> <bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter"> <property name="features"> <array value-type="com.alibaba.fastjson.serializer.SerializerFeature"> <value>WriteDateUseDateFormat</value> <value>WriteMapNullValue</value> </array> </property> </bean> </list> </property> </bean>