最近用springMVC做服务端的http+json的接口,出现一个不是特别容易解决的问题:

在对List类型的值进行处理时,有一部分服务是有做一些逻辑判断的,在逻辑判断不通过的时候会返回一个null值,

而有一些值是直接通过jpa查询到的List类型的值则会进行实例化,即同样是List类型,一个是null,一个"[]"。

  最简单的办法是在null值的地方全部实例化一个new ArrayList<?>(0);但是这样会修改很多地方,而且对于这些情况都要进行实例化分配内存不是那么的理想。

  所以就在springMvc转json的地方做手脚。

  我们都知道springMvc是使用jackson做的json序列化工具。  

   @Bean
public MappingJackson2HttpMessageConverter mappingJacksonHttpMessageConverter() {
final MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setSupportedMediaTypes(ImmutableList.of(MediaType.TEXT_HTML, MediaType.APPLICATION_JSON));
return converter;
}

可以配置其一个MappingJackson2HttpMessageConverter类,这个类同时可以做另一个事情,防止ie对json数据当做文件进行下载。

  MappingJackson2HttpMessageConverter类中可以取到一个ObjectMapper,即jackson序列化的主类。

查看代码看到:

    @Override
protected void writeInternal(Object object, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException { JsonEncoding encoding = getJsonEncoding(outputMessage.getHeaders().getContentType());
// The following has been deprecated as late as Jackson 2.2 (April 2013);
// preserved for the time being, for Jackson 2.0/2.1 compatibility.
@SuppressWarnings("deprecation")
JsonGenerator jsonGenerator =
this.objectMapper.getJsonFactory().createJsonGenerator(outputMessage.getBody(), encoding); // A workaround for JsonGenerators not applying serialization features
// https://github.com/FasterXML/jackson-databind/issues/12
if (this.objectMapper.isEnabled(SerializationFeature.INDENT_OUTPUT)) {
jsonGenerator.useDefaultPrettyPrinter();
} try {
if (this.jsonPrefix != null) {
jsonGenerator.writeRaw(this.jsonPrefix);
}
//此处进行序列化
this.objectMapper.writeValue(jsonGenerator, object);
}
catch (JsonProcessingException ex) {
throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getMessage(), ex);
}
}

看到使用了

   this.objectMapper.writeValue(jsonGenerator, object);

  进行序列化,跟进去,看到一句话:

_serializerProvider(config).serializeValue(jgen, value);

看来这个就是具体的序列化的方法了。

  

 public void serializeValue(JsonGenerator jgen, Object value)
throws IOException, JsonGenerationException
{
if (value == null) {
_serializeNull(jgen);
return;
}
Class<?> cls = value.getClass();
// true, since we do want to cache root-level typed serializers (ditto for null property)
final JsonSerializer<Object> ser = findTypedValueSerializer(cls, true, null); try {
ser.serialize(value, jgen, this);
} catch (IOException ioe) { // As per [JACKSON-99], pass IOException and subtypes as-is
throw ioe;
} catch (Exception e) { // but wrap RuntimeExceptions, to get path information
String msg = e.getMessage();
if (msg == null) {
msg = "[no message for "+e.getClass().getName()+"]";
}
throw new JsonMappingException(msg, e);
}
}

ok,我们本来的目的是 对null值的处理,那么在这个地方我们看到了一个对null的处理,

  /**
* @since 2.0
*/
public JsonSerializer<Object> getDefaultNullValueSerializer() {
return _nullValueSerializer;
}
  @Override
public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonGenerationException
{
jgen.writeNull();
}

那么,我们是不是只要替换掉这个_nullValueSerializer  就可以了呢,是的,这个一个比较常规的对于null值处理的方法。

  具体参考:http://blog.csdn.net/zshake/article/details/17582691

但是这个jsonSerializer有一个比较严重的问题,就是这个nullValueSerializer是全局的,即所有的null都会应用这个JsonSerializer,在这个类中无法判断类型。

 我无法判断当我是List类型时怎样,普通类型时怎样。

所以继续向下跟代码:

跟入 ser.serialize(value, jgen, this);  这个方法,发现其有许多的实现,通过调试模式,进入了一个叫做BeanSerializer的类,其实现为:

  

 /**
* Main serialization method that will delegate actual output to
* configured
* {@link BeanPropertyWriter} instances.
*/
@Override
public final void serialize(Object bean, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonGenerationException
{
if (_objectIdWriter != null) {
_serializeWithObjectId(bean, jgen, provider, true);
return;
}
jgen.writeStartObject();
if (_propertyFilterId != null) {
serializeFieldsFiltered(bean, jgen, provider);
} else {
//调试模式下最终走了这个方法
serializeFields(bean, jgen, provider);
}
jgen.writeEndObject();
}
protected void serializeFields(Object bean, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonGenerationException
{
final BeanPropertyWriter[] props;
if (_filteredProps != null && provider.getActiveView() != null) {
props = _filteredProps;
} else {
props = _props;
}
int i = 0;
try {
for (final int len = props.length; i < len; ++i) {
BeanPropertyWriter prop = props[i];
if (prop != null) { // can have nulls in filtered list
prop.serializeAsField(bean, jgen, provider);
}
}
if (_anyGetterWriter != null) {
_anyGetterWriter.getAndSerialize(bean, jgen, provider);
}
} catch (Exception e) {
String name = (i == props.length) ? "[anySetter]" : props[i].getName();
wrapAndThrow(provider, e, bean, name);
} catch (StackOverflowError e) {
/* 04-Sep-2009, tatu: Dealing with this is tricky, since we do not
* have many stack frames to spare... just one or two; can't
* make many calls.
*/
JsonMappingException mapE = new JsonMappingException("Infinite recursion (StackOverflowError)", e);
String name = (i == props.length) ? "[anySetter]" : props[i].getName();
mapE.prependPath(new JsonMappingException.Reference(bean, name));
throw mapE;
}
}

这个方法中最重要的一个东西就是BeanPropertyWriter 这个类,这个类是由SerializerFactory 工厂进行实例化的,其作用是对bean中的每个字段进行jackson操作的封装,其中封装了字段的一些元信息,

和对此字段进行jackson序列化的操作,那么问题来了,这么说来,这个BeanPropertyWriter类其实就是jackson真正如何对每个bean进行转json的最终的操作的实现,那么我们是不是只要替换掉这个类就可以了

呢,答案是肯定的。

  那么看看jackson为我们预留的对此类进行自定义的方法。

  jackson通过JsonSerializer来对javabean序列化,此serializer都是通过一个SerializerFactory活的的,在这个工厂类中,找到了一个这个方法:

  

  @SuppressWarnings("unchecked")
protected JsonSerializer<Object> constructBeanSerializer(SerializerProvider prov,
BeanDescription beanDesc)
throws JsonMappingException
{
// 13-Oct-2010, tatu: quick sanity check: never try to create bean serializer for plain Object
// 05-Jul-2012, tatu: ... but we should be able to just return "unknown type" serializer, right?
if (beanDesc.getBeanClass() == Object.class) {
return prov.getUnknownTypeSerializer(Object.class);
// throw new IllegalArgumentException("Can not create bean serializer for Object.class");
}
final SerializationConfig config = prov.getConfig();
BeanSerializerBuilder builder = constructBeanSerializerBuilder(beanDesc);
builder.setConfig(config); // First: any detectable (auto-detect, annotations) properties to serialize?
//注意这里,这里为每个属性实例化了一个BeanPropertyWriter
List<BeanPropertyWriter> props = findBeanProperties(prov, beanDesc, builder);
if (props == null) {
props = new ArrayList<BeanPropertyWriter>();
}
// [JACKSON-440] Need to allow modification bean properties to serialize:
//这里通过_factoryConfig中的配置:BeanSerializerModifier 对这个props做了change(修改),
if (_factoryConfig.hasSerializerModifiers()) {
for (BeanSerializerModifier mod : _factoryConfig.serializerModifiers()) {
props = mod.changeProperties(config, beanDesc, props);
}
} // Any properties to suppress?
props = filterBeanProperties(config, beanDesc, props);
//.....之后的省略

重点注意:

//这里通过_factoryConfig中的配置:   BeanSerializerModifier 对这个props做了change(修改),

       if (_factoryConfig.hasSerializerModifiers()) {

         for (BeanSerializerModifier mod : _factoryConfig.serializerModifiers()){

             props = mod.changeProperties(config, beanDesc, props);

         }

       }

     这里从factoryConfig中拿出来了一个Modifiers集合,并且通过这些Modifiers对List<BeanPropertyWriter>进行了修改,那么这样就简单了,我们只要自己定义一个Modifyer对某个List类型的BeanPropertyWriter进行修改集合了。

首先定义一个Modifyer

    

public class MyBeanSerializerModifier extends BeanSerializerModifier {

    private JsonSerializer<Object> _nullArrayJsonSerializer = new MyNullArrayJsonSerializer();

    @Override
public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc,
List<BeanPropertyWriter> beanProperties) {
// 循环所有的beanPropertyWriter
for (int i = 0; i < beanProperties.size(); i++) {
BeanPropertyWriter writer = beanProperties.get(i);
// 判断字段的类型,如果是array,list,set则注册nullSerializer
if (isArrayType(writer)) {
//给writer注册一个自己的nullSerializer
writer.assignNullSerializer(this.defaultNullArrayJsonSerializer());
}
}
return beanProperties;
} // 判断是什么类型
protected boolean isArrayType(BeanPropertyWriter writer) {
Class<?> clazz = writer.getPropertyType();
return clazz.isArray() || clazz.equals(List.class) || clazz.equals(Set.class); } protected JsonSerializer<Object> defaultNullArrayJsonSerializer() {
return _nullArrayJsonSerializer;
}
}

一个对null值处理的JsonSeralizer:

  

public class MyNullArrayJsonSerializer extends JsonSerializer<Object> {

    @Override
public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
if (value == null) {
jgen.writeStartArray();
jgen.writeEndArray();
} else {
jgen.writeObject(value);
}
}
}

  主要是看看怎么设置到jackson里:

  还是那个MappingJackson2HttpMessageConverter:

  

@Bean
public MappingJackson2HttpMessageConverter mappingJacksonHttpMessageConverter() {
final MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
ObjectMapper mapper = converter.getObjectMapper();
// 为mapper注册一个带有SerializerModifier的Factory,此modifier主要做的事情为:当序列化类型为array,list、set时,当值为空时,序列化成[]
mapper.setSerializerFactory(mapper.getSerializerFactory().withSerializerModifier(new MyBeanSerializerModifier())); converter.setSupportedMediaTypes(ImmutableList.of(MediaType.TEXT_HTML, MediaType.APPLICATION_JSON));
return converter;
}

  看看效果:

  在设置mapper.setSerializerFactory(mapper.getSerializerFactory().withSerializerModifier(new MyBeanSerializerModifier()));

我们转换一个类:

  

public class NullTest {

    private String key;

    private List<String> list;

    private Map<String, String> map;

    private int[] array = new int[0];

    private String[] array2;

    private java.util.Date now = new java.util.Date();

//getter.....setter.....

}
@RequestMapping("test/aaa")
@ResponseBody
public ResponseResult test() {
System.err.println("=====");
return this.successResult().data(new NullTest());
}

之前:

{"success":true,"code":0,"message":"","data":{"key":null,"list":null,"map":null,"array":[],"array2":null,"now":1450167151924}}

之后:

    {"success":true,"code":0,"message":"","data":{"key":null,"list":[],"map":null,"array":[],"array2":[],"now":1450167205726}}

成功!

参考资料:

  http://www.baeldung.com/jackson-json-view-annotation

  http://blog.csdn.net/zshake/article/details/17582691

========================华丽的分割线=======================================================

基于最近好几个人问我如果不用spring-boot,普通的spring-mvc怎么进行xml配置,特此总结一下

思路是使用spring的定义bean的方式,通过工厂的方式进行定义

1.首先创建一个工厂:

public class MappingJackson2HttpMessageConverterFactory {

    public MappingJackson2HttpMessageConverter init() {
final MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
ObjectMapper mapper = converter.getObjectMapper();
// 为mapper注册一个带有SerializerModifier的Factory,此modifier主要做的事情为:当序列化类型为array,list、set时,当值为空时,序列化成[]
mapper.getSerializerFactory().withSerializerModifier(new MyBeanSerializerModifier()); converter.setSupportedMediaTypes(ImmutableList.of(MediaType.TEXT_HTML, MediaType.APPLICATION_JSON));
return converter;
}
}

2.进行spring-mvc.xml的配置

<context:annotation-config />
<!-- 激活@Controller模式 -->
<mvc:annotation-driven >
<!-- 消息转换器 -->
<mvc:message-converters register-defaults="true" >
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes" value="text/plain;charset=UTF-8" />
</bean>
<bean factory-bean="mappingJackson2HttpMessageConverterFactory" factory-method="init"
class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter" >
</bean>
</mvc:message-converters>
</mvc:annotation-driven> <bean id="mappingJackson2HttpMessageConverterFactory" class = "com.lclc.core.MappingJackson2HttpMessageConverterFactory" />
<!-- 对包中的所有类进行扫描,以完成Bean创建和自动依赖注入的功能 需要更改 -->
<context:component-scan base-package="com.lclc" /> <!--其他配置 -->

【私人定制jackson】定制jackson的自定义序列化(null值的处理)的更多相关文章

  1. 定制jackson的自定义序列化(null值的处理)

    http://www.cnblogs.com/lic309/p/5048631.html

  2. jackSon注解– @JsonInclude 注解不返回null值字段

    @Data @JsonInclude(JsonInclude.Include.NON_NULL) public class OrderDTO { private String orderId; @Js ...

  3. c# 通过json.net中的JsonConverter进行自定义序列化与反序列化

    https://www.cnblogs.com/yijiayi/p/10051284.html 相信大家在工作中会经常遇见对json进行序列化与反序列化吧,但通常的序列化与反序列化中的json结构与c ...

  4. 源码分析springboot自定义jackson序列化,默认null值个性化处理返回值

    最近项目要实现一种需求,对于后端返回给前端的json格式的一种规范,不允许缺少字段和字段值都为null,所以琢磨了一下如何进行将springboot的Jackson序列化自定义一下,先看看如何实现,再 ...

  5. Java下利用Jackson进行JSON解析和序列化

    Java下利用Jackson进行JSON解析和序列化   Java下常见的Json类库有Gson.JSON-lib和Jackson等,Jackson相对来说比较高效,在项目中主要使用Jackson进行 ...

  6. 47.Odoo产品分析 (五) – 定制板块(2) – 为业务自定义odoo(2)

    查看Odoo产品分析系列--目录 Odoo产品分析 (五) – 定制板块(2) – 为业务自定义odoo(1) 4 添加自定义字段 定制odoo的最普通的原因就是指定到公司的附加信息.如果您正在运行一 ...

  7. JSON数据处理框架Jackson精解第一篇-序列化与反序列化核心用法

    Jackson是Spring Boot默认的JSON数据处理框架,但是其并不依赖于任何的Spring 库.有的小伙伴以为Jackson只能在Spring框架内使用,其实不是的,没有这种限制.它提供了很 ...

  8. java 自定义序列化

    pom.xml 导包 创建自己的序列化类,继承 com.fasterxml.jackson.databind.JsonSerializer<T> 抽象类 重写 serialize() 方法 ...

  9. Jackson中处理map中的null key 或者null value 及实体字段中的null value

    1.map中有null key时的序列化  当有null key时,jackson序列化会报 Null key for a Map not allowed in JSON (use a convert ...

随机推荐

  1. 搭建fedora开发环境 common lisp, c++, go

    第三方软件库: http://download1.rpmfusion.org/free/fedora/releases/25/Everything/x86_64/os/repoview/index.h ...

  2. MaxScript重启3dsMax的重新思考

    前天看到一位大神写用MaxScript实现重启3dsMax的方法,用的是.net临时编译一个exe出来,然后用这个新的进程来关闭并开启新的max.感觉这种思路不错,或许可以用在别的地方.不过谈及max ...

  3. “不是有效WIN32程序”

    写了个控制台,放到服务器上面运行,运行失败,出现"不是有效win32程序" 不知道原因. 猜测: 1.程序只支持64位的处理器,修改了项目的属性,发现支持any cpu.排除 2. ...

  4. 解析json

    String json = "{\"elements\":[{\"distance\":{\"text\":\"1364 ...

  5. python基础知识---正则

    一.python正则简介 python的re模块,让python能够支持perl正则 perl正则的字符集("."  "[abc]"   "(abc) ...

  6. ASP.NET Misconfiguration: Debug Information

    Abstract: Debugging messages help attackers learn about the system and plan a form of attack. Explan ...

  7. scrum.4

    1.准备看板. 形式参考图4. 2.任务认领,并把认领人标注在看板上的任务标签上. 先由个人主动领任务,PM根据具体情况进行任务的平衡. 然后每个人都着手实现自己的任务. 3.为了团队合作愉快进展顺利 ...

  8. 锋利的js前端分页之jQuery

    大家在作分页时,多数是在后台返回一个导航条的html字符串,其实在前端用js也很好实现. 调用pager方法,输入参数,会返回一个导航条的html字符串.方法的内部比较简单. /** * pageSi ...

  9. shell编程学习

    1.项目中用到Linux的crontrab Linux下的定时执行主要是使用crontab文件中加入定制计划来执行,但是也不是非常复杂,基本上用过一遍就能记住了,关键是要记住/var/spool/cr ...

  10. .net 实现上传文件分割,断点续传上传文件

    一 介绍 断点续传搜索大部分都是下载的断点续传,涉及到HTTP协议1.1的Range和Content-Range头. 来个简单的介绍 所谓断点续传,也就是要从文件已经下载的地方开始继续下载.在以前版本 ...