本文出處:http://blog.csdn.net/chaijunkun/article/details/8257209,轉載請註明。因爲本人不按期會整理相關博文,會對相應內容做出完善。所以強烈建議在原始出處查看此文。
在年初的時候,我曾經寫過一篇文章介紹非關係型數據庫MongoDB和Jackson JSON框架相結合實現接口敏捷開發的文章(http://blog.csdn.net/chaijunkun/article/details/7263804),被可愛的CSDN小編推到了首頁。在此本人對小編表示感謝。事隔10個月,隨着手頭一些項目的進行,對Jackson JSON框架用得愈來愈多。以爲有必要再寫點什麼補充出來。做爲和廣大同仁的一個經驗的分享。
咱們都知道,Jackson JSON以高速、方便和靈活著稱。以前的文章中介紹過使用註解的形式來規定如何將一個對象序列化成JSON的方法,以及如何將一個JSON數據反序列化到一個對象上。可是美中不足的一點就是對於中文的處理。固然我說的美中不足是在默認狀況下,Jackson JSON不會將中文等非ASCII字符轉換爲\uFFFF這樣的形式來顯示。也就是說默認狀況下會顯示爲{"name":"張三"}而不是{"name":"\u5F20\u4E09"}。那麼爲何有這樣的需求呢?在HTTP協議中,咱們能夠指定數據頭部分的內容編碼。如:「GBK」、「UTF-8」等等。若是你設置正確了,那麼OK,前者所表示的數據您能夠正確處理。然而若是設置錯誤,對於中文字符將會產生亂碼。兩套應用系統對接,有可能兩邊使用的默認編碼不一樣,若是一方修改默認編碼將會對應用形成不可預知的後果。所以若能以長遠的眼光開發,那麼不管您設置成什麼編碼方式,都不會使數據產生亂碼。由於,這裏用到了萬國編碼——Unicode。
好的,問題出來了,咱們如何解決呢?使其經過實驗,Jackson JSON其實在默認設置下已經具有了對Unicode編碼的JSON數據進行解析。所欠缺的就是在序列化對象時缺乏相應的步驟。好在Jackson JSON框架容許咱們自定義序列化方法。那麼咱們就來寫一個序列化類:java
- package net.csdn.blog.chaijunkun.util;
-
- import java.io.IOException;
-
- import org.codehaus.jackson.JsonGenerationException;
- import org.codehaus.jackson.JsonGenerator;
- import org.codehaus.jackson.JsonProcessingException;
- import org.codehaus.jackson.impl.JsonWriteContext;
- import org.codehaus.jackson.map.JsonSerializer;
- import org.codehaus.jackson.map.SerializerProvider;
- import org.codehaus.jackson.util.CharTypes;
-
- public class StringUnicodeSerializer extends JsonSerializer<String> {
-
- private final char[] HEX_CHARS = "0123456789ABCDEF".toCharArray();
- private final int[] ESCAPE_CODES = CharTypes.get7BitOutputEscapes();
-
- private void writeUnicodeEscape(JsonGenerator gen, char c) throws IOException {
- gen.writeRaw('\\');
- gen.writeRaw('u');
- gen.writeRaw(HEX_CHARS[(c >> 12) & 0xF]);
- gen.writeRaw(HEX_CHARS[(c >> 8) & 0xF]);
- gen.writeRaw(HEX_CHARS[(c >> 4) & 0xF]);
- gen.writeRaw(HEX_CHARS[c & 0xF]);
- }
-
- private void writeShortEscape(JsonGenerator gen, char c) throws IOException {
- gen.writeRaw('\\');
- gen.writeRaw(c);
- }
-
- @Override
- public void serialize(String str, JsonGenerator gen,
- SerializerProvider provider) throws IOException,
- JsonProcessingException {
- int status = ((JsonWriteContext) gen.getOutputContext()).writeValue();
- switch (status) {
- case JsonWriteContext.STATUS_OK_AFTER_COLON:
- gen.writeRaw(':');
- break;
- case JsonWriteContext.STATUS_OK_AFTER_COMMA:
- gen.writeRaw(',');
- break;
- case JsonWriteContext.STATUS_EXPECT_NAME:
- throw new JsonGenerationException("Can not write string value here");
- }
- gen.writeRaw('"');
- for (char c : str.toCharArray()) {
- if (c >= 0x80){
- writeUnicodeEscape(gen, c);
- }else {
-
- int code = (c < ESCAPE_CODES.length ? ESCAPE_CODES[c] : 0);
- if (code == 0){
- gen.writeRaw(c);
- }else if (code < 0){
- writeUnicodeEscape(gen, (char) (-code - 1));
- }else {
- writeShortEscape(gen, (char) code);
- }
- }
- }
- gen.writeRaw('"');
- }
-
- }
這個序列化類將要對應用中全部使用Jackson JSON的地方全都用一種方法來處理字符串類型。光有了方法還不行,還要對它進行註冊。讓Jackson JSON在序列化對象的時候使用剛剛定義好的方法:
- if (objectMapper== null){
- objectMapper= new ObjectMapper();
-
- objectMapper.configure(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS, false);
-
- CustomSerializerFactory serializerFactory= new CustomSerializerFactory();
- serializerFactory.addSpecificMapping(String.class, new StringUnicodeSerializer());
- objectMapper.setSerializerFactory(serializerFactory);
-
- }
2014年5月13日補充:最近被問到不少次關於單例模式的實現。上面的寫法真的很不安全,沒有加鎖,也沒有對objectMapper進行volatile修飾(即所謂的「雙檢索」貨「雙重檢查」),所以最簡單的可靠的方法應該使用「枚舉單例法」。數據庫
2014年11月21日補充:因爲Jackson 2的版本變化,CustomSerializerFactory已經被去掉了,通過實驗,可使用這種方式代替:apache
- if (objectMapper== null){
- objectMapper= new ObjectMapper();
-
- objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
-
- SimpleModule module = new SimpleModule();
- module.addSerializer(String.class, new StringUnicodeSerializer());
- objectMapper.registerModule(module);
-
- objectMapper.setSerializationInclusion(Include.NON_NULL);
-
- }
接下來咱們來作一個測試用的對象,驗證咱們的代碼:json
- package net.csdn.blog.chaijunkun.json;
-
- import java.util.Date;
-
- import net.csdn.blog.chaijunkun.util.DateDeserializer;
- import net.csdn.blog.chaijunkun.util.DateSerializer;
- import net.csdn.blog.chaijunkun.util.DateTimeDeserializer;
- import net.csdn.blog.chaijunkun.util.DateTimeSerializer;
-
- import org.codehaus.jackson.annotate.JsonPropertyOrder;
- import org.codehaus.jackson.map.annotate.JsonDeserialize;
- import org.codehaus.jackson.map.annotate.JsonSerialize;
-
- @JsonPropertyOrder(alphabetic= false)
- public class DemoObj {
-
- private Integer sid;
-
- private String stuName;
-
- private Boolean sex;
-
- @JsonSerialize(using= DateSerializer.class)
- @JsonDeserialize(using= DateDeserializer.class)
- private Date birthday;
-
- @JsonSerialize(using= DateTimeSerializer.class)
- @JsonDeserialize(using= DateTimeDeserializer.class)
- private Date logTime;
-
-
-
- }
從代碼上能夠看出,咱們並無對String類型的屬性強制指定用何種序列與反序列方法。而後咱們來構造測試用例:
- package net.csdn.blog.chaijunkun.test;
-
- import java.text.SimpleDateFormat;
- import java.util.Calendar;
- import java.util.Date;
-
- import net.csdn.blog.chaijunkun.json.DemoObj;
- import net.csdn.blog.chaijunkun.util.JSONUtil;
-
- import org.apache.log4j.Logger;
-
- public class JSONTest {
-
- private static Logger logger= Logger.getLogger(JSONTest.class);
-
- private static String json= "{\"sid\":2,\"stuName\":\"\u6C5F\u5357Style\",\"sex\":true,\"birthday\":\"2012-07-15\",\"logTime\":\"2012-12-04 19:22:36\"}";
-
- public static void main(String[] args) {
- DemoObj objSrc= new DemoObj();
- objSrc.setSid(1);
- objSrc.setStuName("鳥叔");
- objSrc.setSex(true);
- Calendar calendar= Calendar.getInstance();
- calendar.set(1977, Calendar.DECEMBER, 31, 0, 0, 0);
- objSrc.setBirthday(calendar.getTime());
- objSrc.setLogTime(new Date());
- logger.info(String.format("轉換爲JSON後的數據:%s", JSONUtil.toJSON(objSrc)));
- DemoObj objDes= JSONUtil.fromJSON(json, DemoObj.class);
- if(objDes==null){
- logger.info("反序列化失敗");
- }else{
- logger.info("反序列化成功");
- SimpleDateFormat sdf= new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
- logger.info(String.format("標識:%d", objDes.getSid()));
- logger.info(String.format("姓名:%s", objDes.getStuName()));
- logger.info(String.format("性別:%s", objDes.getSex()==true?"男":"女"));
- logger.info(String.format("生日:%s", sdf.format(objDes.getBirthday())));
- logger.info(String.format("登陸日期:%s", sdf.format(objDes.getLogTime())));
- }
- }
-
- }
看一下輸出:安全
- 轉換爲JSON後的數據:{"sid":1,"stuName":"\u9E1F\u53D4","sex":true,"birthday":"1977-12-31","logTime":"2012-12-04 19:31:57"}
- 反序列化成功
- 標識:2
- 姓名:江南Style
- 性別:男
- 生日:2012-07-15 00:00:00
- 登陸日期:2012-12-04 19:22:36
咱們看到,已經成功將中文字符顯示成爲了Unicode編碼的數據。一樣,咱們以前構造的Unicode編碼的數據,在不通過任何修改的狀況下成功顯示出來了。
細心的朋友也許觀察到了,在測試用的對象定義代碼中,針對一樣Date類型的屬性「birthday」和「logTime」,咱們指定了不一樣的序列化與反序列化方法。讓咱們來看爛這兩個有什麼不一樣:app
- package net.csdn.blog.chaijunkun.util;
-
- import java.io.IOException;
- import java.text.SimpleDateFormat;
- import java.util.Date;
-
- import org.codehaus.jackson.JsonGenerator;
- import org.codehaus.jackson.JsonProcessingException;
- import org.codehaus.jackson.map.JsonSerializer;
- import org.codehaus.jackson.map.SerializerProvider;
-
- public class DateTimeSerializer extends JsonSerializer<Date> {
-
- @Override
- public void serialize(Date date, JsonGenerator gen, SerializerProvider provider)
- throws IOException, JsonProcessingException {
- SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
- String formattedDate= sdf.format(date);
- gen.writeString(formattedDate);
- }
-
- }
- package net.csdn.blog.chaijunkun.util;
-
- import java.io.IOException;
- import java.text.SimpleDateFormat;
- import java.util.Calendar;
- import java.util.Date;
-
- import org.codehaus.jackson.JsonParser;
- import org.codehaus.jackson.JsonProcessingException;
- import org.codehaus.jackson.map.DeserializationContext;
- import org.codehaus.jackson.map.JsonDeserializer;
-
- public class DateTimeDeserializer extends JsonDeserializer<Date> {
-
- @Override
- public Date deserialize(JsonParser parser, DeserializationContext context)
- throws IOException, JsonProcessingException {
- String dateFormat= "yyyy-MM-dd HH:mm:ss";
- SimpleDateFormat sdf= new SimpleDateFormat(dateFormat);
- try{
- String fieldData= parser.getText();
- return sdf.parse(fieldData);
- }catch (Exception e) {
- Calendar ca= Calendar.getInstance();
- ca.set(1970, Calendar.JANUARY, 1, 0, 0, 0);
- return ca.getTime();
- }
- }
- }
- package net.csdn.blog.chaijunkun.util;
-
- import java.io.IOException;
- import java.text.SimpleDateFormat;
- import java.util.Date;
-
- import org.codehaus.jackson.JsonGenerator;
- import org.codehaus.jackson.JsonProcessingException;
- import org.codehaus.jackson.map.JsonSerializer;
- import org.codehaus.jackson.map.SerializerProvider;
-
- public class DateSerializer extends JsonSerializer<Date> {
-
- @Override
- public void serialize(Date date, JsonGenerator gen, SerializerProvider provider)
- throws IOException, JsonProcessingException {
- SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
- String formattedDate= sdf.format(date);
- gen.writeString(formattedDate);
- }
-
- }
- package net.csdn.blog.chaijunkun.util;
-
- import java.io.IOException;
- import java.text.SimpleDateFormat;
- import java.util.Calendar;
- import java.util.Date;
-
- import org.codehaus.jackson.JsonParser;
- import org.codehaus.jackson.JsonProcessingException;
- import org.codehaus.jackson.map.DeserializationContext;
- import org.codehaus.jackson.map.JsonDeserializer;
-
- public class DateDeserializer extends JsonDeserializer<Date> {
-
- @Override
- public Date deserialize(JsonParser parser, DeserializationContext context)
- throws IOException, JsonProcessingException {
- String dateFormat= "yyyy-MM-dd";
- SimpleDateFormat sdf= new SimpleDateFormat(dateFormat);
- try{
- String fieldData= parser.getText();
- return sdf.parse(fieldData);
- }catch (Exception e) {
- Calendar ca= Calendar.getInstance();
- ca.set(1970, Calendar.JANUARY, 1, 0, 0, 0);
- return ca.getTime();
- }
- }
- }
從代碼咱們能夠看出,DateTimeSerializer和DateTimeDeserializer比DateSerializer和DateDeserializer細粒度更加高,加入了具體時間的屬性。這在應用開發中是很常見的,生日信息咱們每每知道年月日就能夠了,而登錄時間每每須要得比較詳細。從實例中咱們能夠知道,即使是同一類型,經過制定不一樣的序列與反序列方法,能夠靈活地獲得咱們想要的數據形態。以上測試用例已經打包,上傳到了個人資源。歡迎你們下載,共同窗習。下載地址:http://download.csdn.net/detail/chaijunkun/4846394
2012年12月17日補充:框架
最近有一個需求,須要在序列化與反序列化對象的時候對數據進行修改,當發現數據源值爲空時須要讓生成的JSON顯示改字段爲「遊客」。但是我不管如何指定序列化器與反序列化器都無效。程序根本走不到指定的代碼中去。後來我得出結論,Jackson JSON在反序列化對象的時候,若JSON數據中對應屬性爲null,則不會走自定義的反序列化器;一樣地,當你設置對象的某個屬性值爲null時,在將其序列化成JSON時,也不會走自定義的序列化器。所以如有相似的需求,請在序列化與反序列化以前經過硬代碼形式判斷和修改,千萬不要什麼事都期望着序列化器與反序列化器。ide
參考資料:來源於國外網站的一篇介紹如何轉碼的文章,原文有點錯誤。我將其改正了,並加入了一些中文註釋:http://wiki.fasterxml.com/JacksonSampleQuoteChars學習