如何讓Jackson JSON生成的資料包含的中文以unicode方式編碼

NO IMAGE

我們都知道,Jackson JSON以高速、方便和靈活著稱。之前的文章中介紹過使用註解的形式來規定如何將一個物件序列化成JSON的方法,以及如何將一個JSON資料反序列化到一個物件上。但是美中不足的一點就是對於中文的處理。當然我說的美中不足是在預設情況下,Jackson JSON不會將中文等非ASCII字元轉換為\uFFFF這樣的形式來顯示。也就是說預設情況下會顯示為{“name”:”張三”}而不是{“name”:”\u5F20\u4E09″}。那麼為什麼有這樣的需求呢?在HTTP協議中,我們可以指定資料頭部分的內容編碼。如:“GBK”、“UTF-8”等等。如果你設定正確了,那麼OK,前者所表示的資料您可以正確處理。然而如果設定錯誤,對於中文字元將會產生亂碼。兩套應用系統對接,有可能兩邊使用的預設編碼不同,如果一方修改預設編碼將會對應用造成不可預知的後果。因此若能以長遠的眼光開發,那麼無論您設定成什麼編碼方式,都不會使資料產生亂碼。因為,這裡用到了萬國編碼——Unicode。

好的,問題出來了,我們如何解決呢?使其通過實驗,Jackson JSON其實在預設設定下已經具備了對Unicode編碼的JSON資料進行解析。所欠缺的就是在序列化物件時缺少相應的步驟。好在Jackson JSON框架允許我們自定義序列化方法。那麼我們就來寫一個序列化類:
複製程式碼 程式碼如下:
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(‘”‘);//寫入JSON中字串的開頭引號
     for (char c : str.toCharArray()) {
       if (c >= 0x80){
        writeUnicodeEscape(gen, c); // 為所有非ASCII字元生成轉義的unicode字元
       }else {
         // 為ASCII字元中前128個字元使用轉義的unicode字元
         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); // 短轉義字元 (\n \t …)
         }
       }
     }
     gen.writeRaw(‘”‘);//寫入JSON中字串的結束引號
 }

}

這個序列化類將要對應用中所有使用Jackson JSON的地方全都用一種方法來處理字串型別。光有了方法還不行,還要對它進行註冊。讓Jackson JSON在序列化物件的時候使用剛剛定義好的方法:
複製程式碼 程式碼如下:
if (objectMapper== null){
 objectMapper= new ObjectMapper();
 //當找不到對應的序列化器時 忽略此欄位
 objectMapper.configure(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS, false);
 //使Jackson JSON支援Unicode編碼非ASCII字元
 CustomSerializerFactory serializerFactory= new CustomSerializerFactory();
 serializerFactory.addSpecificMapping(String.class, new StringUnicodeSerializer());
 objectMapper.setSerializerFactory(serializerFactory);
 //支援結束
}

接下來我們來做一個測試用的物件,驗證我們的程式碼:
複製程式碼 程式碼如下:
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;

 //Getters and Setters

}

從程式碼上可以看出,我們並沒有對String型別的屬性強制指定用何種序列與反序列方法。然後我們來構造測試用例:
複製程式碼 程式碼如下:
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”,我們指定了不同的序列化與反序列化方法。讓我們來看爛這兩個有什麼不同:
複製程式碼 程式碼如下:
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);
 }

}

複製程式碼 程式碼如下:
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();
  }
 }
}

複製程式碼 程式碼如下:
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);
 }

}

複製程式碼 程式碼如下:
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細粒度更加高,加入了具體時間的屬性。這在應用開發中是很常見的,生日資訊我們往往知道年月日就可以了,而登陸時間往往需要得比較詳細。從例項中我們可以知道,即便是同一型別,通過制定不同的序列與反序列方法,可以靈活地得到我們想要的資料形態。以上測試用例已經打包。點選下載

補充:

最近有一個需求,需要在序列化與反序列化物件的時候對資料進行修改,當發現資料來源值為空時需要讓生成的JSON顯示改欄位為“遊客”。可是我無論如何指定序列化器與反序列化器都無效。程式根本走不到指定的程式碼中去。後來我得出結論,Jackson JSON在反序列化物件的時候,若JSON資料中對應屬性為null,則不會走自定義的反序列化器;同樣地,當你設定物件的某個屬性值為null時,在將其序列化成JSON時,也不會走自定義的序列化器。因此若有類似的需求,請在序列化與反序列化之前通過硬程式碼形式判斷和修改,千萬不要什麼事都指望著序列化器與反序列化器。

您可能感興趣的文章:

JS實現漢字與Unicode碼相互轉換的方法詳解JS將unicode碼轉中文方法js 中文漢字轉Unicode、Unicode轉中文漢字、ASCII轉換Unicode、Unicode轉換ASCII、中文轉換&#XXX函式程式碼Json_encode防止漢字轉義成unicode的方法js unicode 編碼解析關於資料轉換為中文的兩種方法javascript unicode與GBK2312(中文)編碼轉換方法無語,javascript居然支援中文(unicode)程式設計!解決JSON.stringify()自動將中文轉譯成unicode的問題JS實現的漢字與Unicode碼相互轉化功能分析