最近用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. 升级python2.7至python3

    首先下载源tar包 可利用linux自带下载工具wget下载,如下所示: 这里我用下载的是Python-3.5.1.tar.bz2版本,下载完成后到下载目录下,解压 进入解压缩后的文件夹 cd  Py ...

  2. neutron floatingip-delete

  3. stimulsoft Report报表使用笔记

    1.使用设计器设计mrt报表模板,或者从其他文件复制修改 2.删除business object 数据源 3.使用代码添加数据源 ParcelChangeItem change = new Parce ...

  4. javaWeb学习-----session

    一.Session简单介绍 在WEB开发中,服务器可以为每个用户浏览器创建一个会话对象(session对象),注意:一个浏览器独占一个session对象(默认情况下).因此,在需要保存用户数据时,服务 ...

  5. 这几天帮一个朋友解决了一点小问题(RF的有些小问题及解决过程)

    最近涉猎自动化太少了,以至于都不经常更新了.最近一个朋友在做移动端自动化的时候遇到了一些小问题来找我解决.本人也不是很精通,只是接触的时间长了一点了.下面就是一些问题和解决过程: 1.她刚过来的时候, ...

  6. python几个重要的模块备忘

    一:模块使用方法 二:时间模块time 三:系统接口模块os和sys 四:数据保存的几个模块json,pickle,xml,configparse 五:数据复制移动模块shutil 六:日志模块log ...

  7. 针对bootstrap内联单选框input与文字不能居中对齐的解决办法

    1.html代码 <div > <label class="checkbox-inline first-label"> <input type=&qu ...

  8. Sql Server中不常用的表运算符之APPLY(2)

    在Sql Server中不常用的表运算符之APPLY(1)中提到,SQL2005中新支持的APPLY的特性:1.可以直接将表表达式(表值函数或者子查询)作为APPLY语句的右表连接左表.2.由于使用A ...

  9. [转] --- Error: “A field or property with the name was not found on the selected data source” get only on server

    Error: “A field or property with the name was not found on the selected data source” get only on ser ...

  10. unity3d 知识点随记

    1.transform.translate是增加transform面板相应的数值x,y,z是以本地坐标系为方向:transform.transformdirection是以世界坐标系为方向,可以去测试 ...