Location:Action,新的JSON序列化的思路
序列化Json,是當前火爆的互聯網世界的一種最基礎的技術之一,最常用的是采用 Annotation&SerializerFeature 方式,國內流行的fastjson,還有國外流行的jackson都是這樣。然而,這種方法有一定的局限性。會遇到各種奇葩序列化要求而很難做到。或者 json工具作者不得不按照奇葩需求開發更多的序列化實現。本文介紹了常規思路的瓶頸,以及新思路,附帶一個beetl-json實現(用了2周)來說明 這個新的思路
通常來說,序列化json,實際上有2總方式
- 通過當前流行的JSON工具。
- 編寫代碼,手工序列化 </ul>
這倆種方式各有優劣。第一種方式毫無疑問,不需要開發者做什么工作,直接調用序列化接口,輸出就是json。但是,如果需要特殊需求,比如需要將日期格式化按照yyyy-mm-dd 輸出,這些JSON工具可以指定日期格式化輸出,比如FastJSON里:
SerializeConfig mapping = new SerializeConfig(); String dateFormat = "yyyy-MM-dd"; mapping.put(Date.class, new SimpleDateFormatSerializer(dateFormat)); String json = JSON.toJSONString(obj,mapping);
在Jackon里,代碼也是類似,如:
ObjectMapper objectMapper = new ObjectMapper(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); objectMapper.setDateFormat(sdf); objectMapper.writeValue(writer, obj);
- 對象里有個字段是Calendar,我想格式化yyyy-MM-dd 輸出。
- 對象里另外有個字段也是Calendar,我想僅僅輸出time(Long類型)
- 對象及其關聯對象的id我都不想輸出。
- 如果某個對象的集合屬性為null,則僅僅輸出[], 但另外一個集合屬性未null,則輸出null。
當前流行工具總是疲于應付這種“變態”序列化需求,不停的升級版本增加處理類來解決這些需求。有沒有終極解決辦法嗎?
在回答這個問題前,讓我們回歸到我說的第二種序列化解決方法,就是手工編寫代碼。通過硬編碼貌似是是終極奧義。各種變態序列化需求都能通過硬編碼來解決。然而,這種終極奧義的問題是序列化太麻煩了。面對企業應用,互聯網應用中成百上千的模型,手工來序列化是不現實的,那么,有沒有第三個辦法,既能方便序列化對象,有具有很強的序列化能力,應付各種變態需求呢?
以我看來,的確有第三個辦法,正如正則表達式那樣,能分析各種復雜文本,依靠的是(位置&指令)*。序列化JSON,實際上也需要類似正則表達式的思路,我抽象為(locatoin:action)*. location 可能是指一個對象的屬性名,也可能泛指某個類型的對象。action 是指序列化操作,比如忽略此location,或者一個排序動作,一個格式化動作,或者,是一個調用此對象的某個方法的動作等等。對于以下User對象,我們可以指定一系列的(Location:Action)*
public class User{ String name="joel"; int age =12; double salary=12.32266; Date bir = new Date(); Customer customer = new Customer(); List<Customer> list = new ArrayList<Customer>(); List<Customer> deleteList = null; //getter and setter 方法必須有,在此忽略 }
Location:Action | 描述 |
name:i |
name 指的是User對象的name屬性,i代表一個操作,意思ignore。表示此字段不需要序列化 |
~d:f/yyyy-MM-dd/ |
~d 表示所有日期類型(包含其子類),動作是調用一個格式化函數f,參數是yyyy-MM-dd |
bir:$.getTime |
bir是User對象bir屬性,輸出時調用其getTime方法輸出毫秒時間 |
~*::O/name, age/ |
將User類的name,age放到前面優先顯示 |
~*:Ci/name,id/ |
如果User實例被引用過(循環引用),則僅僅輸出id,和name。即避免了循環引用問題,也同時有明確的輸出 |
deleteList :?null->[] |
deleteList是User的屬性,如果為null,則輸出[] |
如上幾個簡單例子可以看到基于(Location:Action)序列化功能的強大和靈活,甚至可以 組合Action,比如
~L/java.util.Calendar*/:$.getTime->f/yyyy-MM-dd/,可以解釋為對于所有對象類型為java.util.Calendar及其子類,輸出的時候,先調用$.getTime,獲得Date,然后再格式化輸出。
由此可見,如果定義好Location,以及提供一定數量的Action,和內置一些表達式操作(如?empty->dosomething),便具有超過傳統的基于(Annotation&&SerializerFeature)的JSON工具的序列化能力。不僅僅如此,通過指定好policy,policy=(location:action)*,可以實現對同一對象的不同序列化策略。比如代碼:
String json = JsonTool.serialize(User,"id:i"); //不輸出id //or 指定一個序列化策略,age,name先輸出,適合有特殊需求的對象或者無法注解(第三方)對象 String json2 = JsonTool.serialize(User,"~*:O/age,name/"));
由此可見,序列化JSON的第三種道路,即"(Location:Action)*" 非常接近我認為的序列化JSON終極奧義,他具有當前流行"(Annotation&SerializerFeature)*" 操作簡便性,也具有硬編碼序列化的的靈活性。我寫了一個beetl-json 作為驗證,斷斷續續花了2周時間和犧牲了周末:),自我感覺效果不錯的,我貼一些Beetl-JSON 代碼可以看看
JsonTool.addLocationAction("~d","f/yyyy.MM.dd/"); JsonTool.addLocationAction("~L/java.util.Calendar*/","$.getTime->f/yyyy-MM-dd/"); //類json格式的策略,用逗號分開多個locationAction JsonTool.addPolicy("~f:f/#.##/,~c:?null->[]"); // 默認是緊湊輸出,使用true,將換行和縮進 JsonTool.pretty = true; //序列化User String json = JsonTool.serialize(User); //or 指定一個序列化策略,age,name先輸出,適合有特殊需求的對象或者無法注解(第三方)對象 String json2 = JsonTool.serialize(User,"~*:O/age,name/"));
User對象定義如下:
@Json( policys={ @JsonPolicy(location="name", action="nn/newUserName/"), @JsonPolicy(location="deleteList", action="?empty->[]") } ) public class User{ String name="joel"; int age =12; double salary=12.32266; Customer customer = new Customer(); List<Customer> list = new ArrayList<Customer>(); List<Customer> deleteList = null; //getter and setter 方法必須有,在此忽略 }
序列化性能現在還未完全考慮中,因為只做了2周,功能還不全,還屬于驗證階段,此時如果比較性能,比較占便宜,我把我初步的性能測試代碼放到 performance test 里了,包含了FastJSON,Jackson.在我老式筆記本里,單線程序列化一個普通對象1百萬次,性能如下:
初步性能測試還是不錯的。beetl-json略快一些,只需要1.238秒,不過考慮到beetl-json現在功能還未全,且對日期輸出做了優化,而FastJson沒有做。所以這三個之間,實際沒有太大的性能差距。
在當今web應用,互聯網應用火爆的年代,Json序列化是這些應用需要的一種基礎技術能力,基于(Location:Action)* 的JSON工具,相比于傳統(Annotation&SerializerFeature )* 的工具會更加靈活和功能強大,能高度滿足程序員的序列化需求。beetl-json會進一步實踐這種思想。讓天下沒有難以序列化的對象。
來自:http://my.oschina.net/u/567839/blog/413072