让Jackson JSON生成的数据包含的中文以unicode方式编码
2012-12-04 20:32
671 查看
本文出处: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框架允许我们自定义序列化方法。那么我们就来写一个序列化类:
这个序列化类将要对应用中所有使用Jackson JSON的地方全都用一种方法来处理字符串类型。光有了方法还不行,还要对它进行注册。让Jackson JSON在序列化对象的时候使用刚刚定义好的方法:
2014年5月13日补充:最近被问到很多次关于单例模式的实现。上面的写法真的很不安全,没有加锁,也没有对objectMapper进行volatile修饰(即所谓的“双检索”货“双重检查”),因此最简单的可靠的方法应该使用“枚举单例法”。
2014年11月21日补充:由于Jackson 2的版本变化,CustomSerializerFactory已经被去掉了,经过实验,可以使用这种方式代替:
接下来我们来做一个测试用的对象,验证我们的代码:
从代码上可以看出,我们并没有对String类型的属性强制指定用何种序列与反序列方法。然后我们来构造测试用例:
看一下输出:
我们看到,已经成功将中文字符显示成为了Unicode编码的数据。同样,我们之前构造的Unicode编码的数据,在不经过任何修改的情况下成功显示出来了。
细心的朋友也许观察到了,在测试用的对象定义代码中,针对同样Date类型的属性“birthday”和“logTime”,我们指定了不同的序列化与反序列化方法。让我们来看烂这两个有什么不同:
从代码我们可以看出,DateTimeSerializer和DateTimeDeserializer比DateSerializer和DateDeserializer细粒度更加高,加入了具体时间的属性。这在应用开发中是很常见的,生日信息我们往往知道年月日就可以了,而登陆时间往往需要得比较详细。从实例中我们可以知道,即便是同一类型,通过制定不同的序列与反序列方法,可以灵活地得到我们想要的数据形态。以上测试用例已经打包,上传到了我的资源。欢迎大家下载,共同学习。下载地址:http://download.csdn.net/detail/chaijunkun/4846394
2012年12月17日补充:
最近有一个需求,需要在序列化与反序列化对象的时候对数据进行修改,当发现数据源值为空时需要让生成的JSON显示改字段为“游客”。可是我无论如何指定序列化器与反序列化器都无效。程序根本走不到指定的代码中去。后来我得出结论,Jackson JSON在反序列化对象的时候,若JSON数据中对应属性为null,则不会走自定义的反序列化器;同样地,当你设置对象的某个属性值为null时,在将其序列化成JSON时,也不会走自定义的序列化器。因此若有类似的需求,请在序列化与反序列化之前通过硬代码形式判断和修改,千万不要什么事都指望着序列化器与反序列化器。
参考资料:来源于国外网站的一篇介绍如何转码的文章,原文有点错误。我将其改正了,并加入了一些中文注释:http://wiki.fasterxml.com/JacksonSampleQuoteChars
在年初的时候,我曾经写过一篇文章介绍非关系型数据库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框架允许我们自定义序列化方法。那么我们就来写一个序列化类:
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('"');//写入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); //支持结束 }
2014年5月13日补充:最近被问到很多次关于单例模式的实现。上面的写法真的很不安全,没有加锁,也没有对objectMapper进行volatile修饰(即所谓的“双检索”货“双重检查”),因此最简单的可靠的方法应该使用“枚举单例法”。
2014年11月21日补充:由于Jackson 2的版本变化,CustomSerializerFactory已经被去掉了,经过实验,可以使用这种方式代替:
if (objectMapper== null){ objectMapper= new ObjectMapper(); //当找不到对应的序列化器时 忽略此字段 objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); //使Jackson JSON支持Unicode编码非ASCII字符 SimpleModule module = new SimpleModule(); module.addSerializer(String.class, new StringUnicodeSerializer()); objectMapper.registerModule(module); //设置null值不参与序列化(字段不被显示) objectMapper.setSerializationInclusion(Include.NON_NULL); //支持结束 }
接下来我们来做一个测试用的对象,验证我们的代码:
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; //Getters and Setters }
从代码上可以看出,我们并没有对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”,我们指定了不同的序列化与反序列化方法。让我们来看烂这两个有什么不同:
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时,也不会走自定义的序列化器。因此若有类似的需求,请在序列化与反序列化之前通过硬代码形式判断和修改,千万不要什么事都指望着序列化器与反序列化器。
参考资料:来源于国外网站的一篇介绍如何转码的文章,原文有点错误。我将其改正了,并加入了一些中文注释:http://wiki.fasterxml.com/JacksonSampleQuoteChars
相关文章推荐
- 让Jackson JSON生成的数据包含的中文以unicode方式编码
- 如何让Jackson JSON生成的数据包含的中文以unicode方式编码
- Java使用百度API 返回JSON数据为unicode编码,怎么转化为中文?
- http 请求数据返回 json 中中文字符为 unicode 编码转汉字转码
- php查询到的数据乱码和转json时中文变成了Unicode的编码。
- python中json格式数据输出实现方式(无unicode编码输出形式)
- 如何使得中文不被 json_encode 不编码成 unicode PHP 浏览:9722 2014年08月14日 使用 php 自带的 json_encode 函数对数据进行编码时,中文都会变成
- 生成二维码 加密解密类 TABLE转换成实体、TABLE转换成实体集合(可转换成对象和值类型) COOKIE帮助类 数据类型转换 截取字符串 根据IP获取地点 生成随机字符 UNIX时间转换为DATETIME\DATETIME转换为UNIXTIME 是否包含中文 生成秘钥方式之一 计算某一年 某一周 的起始时间和结束时间
- 如果JSON数据中有中文,输出Unicode编码,无法识别解决办法
- 关于爬取数据保存到json文件,中文是unicode解决方式
- Foundation+Log.m,让网络请求回来的中文被unicode编码的数据汉字显示
- python编码转换,列表编码转换,编码检测,pycharm编写python乱码,文件中以unicode编码方式转成中文
- 使用Gson解析复杂、变态的Json数据(包含中文key)
- java 处理get请求得到json数据的中文编码
- JsonCpp中文unicode编码问题
- Android解析JSON方式(一)服务器端生成JSON数据
- 【原创】将数据生成json文件下载以及json中文乱码和优化json文件格式的方法
- java对象和json数据转换实现方式3-使用jackson实现
- UTF-8编码)将字符串写入文件,当字符串中包含中文时,出现写入文件的数据残缺,而当不包含中文时,写入文件的数据正常
- 解决PHP json_encode() 中文自动Unicode编码的函数