让Jackson JSON生成的数据包含的中文以unicode方式编码
在年初的时候,我曾经写过一篇文章介绍非关系型数据库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方式编码
我们都知道,Jackson JSON以高速.方便和灵活著称.之前的文章中介绍过使用注解的形式来规定如何将一个对象序列化成JSON的方法,以及如何将一个JSON数据反序列化到一个对象上.但是美中不足的一 ...
- Visual Studio 2017 - Windows应用程序打包成exe文件(2)- Advanced Installer 关于Newtonsoft.Json,LINQ to JSON的一个小demo mysql循环插入数据、生成随机数及CONCAT函数 .NET记录-获取外网IP以及判断该IP是属于网通还是电信 Guid的生成和数据修整(去除空格和小写字符)
Visual Studio 2017 - Windows应用程序打包成exe文件(2)- Advanced Installer Advanced Installer :Free for 30 da ...
- php生成json或者xml数据
, ,'数据返回成功',$arr);echo $xml;?>
- 使用JSONObject类来生成json格式的数据
JSONObject类不支持javabean转json 生成json格式数据的方式有: 1.使用JSONObject原生的来生成 2.使用map构建json格式的数据 3.使用javabean来构建j ...
- 大数据技术之_25_手机APP信息统计系统项目_01_APP 数据生成模块 + 数据收集模块 + 数据处理模块框架搭建 + 业务需求处理 + 数据展示模块 +项目总结 + 问题总结
一 项目概述1.1 角色1.2 业务术语1.3 项目效果展示二 项目需求三 项目概要3.1 项目技术架构3.2 项目目录结构3.3 项目技术选型3.4 项目整体集群规划3.5 创建项目工程四 APP ...
- Android中解析JSON形式的数据
1.JSON(JavaScript Object Notation) 定义: 一种轻量级的数据交换格式,具有良好的可读和便于快速编写的特性.业内主流技术为其提供了完整的解决方案(有点类似于正则表达式, ...
- Spring3 MVC 使用JSON进行前后台数据交互
http://wbj0110.iteye.com/blog/2007918 在 Spring3 中,响应.接受 JSON都十分方便.向前台返回 JSON 格式的数据: 1 2 3 4 5 6 7 8 ...
- Android Json生成及解析实例
JSON的定义: 一种轻量级的数据交换格式,具有良好的可读和便于快速编写的特性.业内主流技术为其提供了完整的解决方案(有点类似于正则表达式 ,获得了当今大部分语言的支持),从而可以在不同平台间进行数据 ...
- Spring MVC 处理JSON | JSONP类型数据
SpringMVC返回JSON格式的数据: 1 添加jar包(gson-2.8.0.jar): <dependency> <groupId>com.google.code.gs ...
随机推荐
- Oracle记录学习
--基本函数--select name,count(id) from work_test group by name having count(id)>1--select upper(name) ...
- 安装Node.js以及Hexo
安装前提 安装 Hexo 相当简单.然而在安装前,您必须检查电脑中是否已安装下列应用程序: Node.js 如何在Ubuntu上安装最新版本的Node.js https://hexo.io/zh-cn ...
- LigerTree的使用
效果图: 页面: <div id="divs" style="width: 310px; overflow-x: hidden; overflow-y: hidde ...
- 用n(0)次求一个数组里面最大子数组的和(数组可以输入负数)
今天老师布置了题目上的任务,可谓是杀死人脑细胞不偿命呐... 在课上叽叽咕咕的讨论了一节课也没有答案,只得出几个备选方案,一个是通过枚举法将数组里面的子数组和一个个列出来然后在进行比较,可想而知(n2 ...
- WPF实用知识点
1.一个基本的WPF程序, 需要引入的程序集WindowsBase, PresentationCore, PresentationFramework using System; using Syste ...
- Python_selenium之窗口切换(二)
Python_selenium之窗口切换(二)一.思路拆分1. 之前有介绍窗口切换,这里加上断言部分2. 这里还是以百度新闻为例,获取百度新闻网址http://news.baidu.com/3. 同样 ...
- Github初试~从安装到连接到版本库
这里我就只说说从安装到连接到github库时出现的错误,以及如何解决 1. 下载github,安装后你就可以在系统中右键看到 Gui 和 Bush按钮 2. 此时你可能会去尝试着去连接你在github ...
- Trifo-VIO:Roubst and Efficient Stero Visual Inertial Odometry using Points and Lines论文笔记
这是2018-IROS上的一篇文章,亮点是作者提出了Lines特征的VIO方案,还有就是提出一个新颖的回环检测,不是用传统的基于优化的方法或者BA,另外作者还发布了一个新的用于VIO的数据集.亮点主要 ...
- c# + Sql server 事务处理
事务(Transaction)是并发控制的单位,是用户定义的一个操作序列.这些操作要么都做,要么都不做,是一个不可分割的工作单位. 通过事务,SQL Server能将逻辑相关的一组操作绑定在一起,以便 ...
- 推荐10 个短小却超实用的 JavaScript 代码段
1. 判断日期是否有效 JavaScript中自带的日期函数还是太过简单,很难满足真实项目中对不同日期格式进行解析和判断的需要.jQuery也有一些第三方库来使日期相关的处理变得简单,但有时你可能只需 ...