【私人定製jackson】定製jackson的自定義序列化(null值的處理)

      最近用springMVC作服務端的http+json的接口,出現一個不是特別容易解決的問題:java

在對List類型的值進行處理時,有一部分服務是有作一些邏輯判斷的,在邏輯判斷不經過的時候會返回一個null值,git

而有一些值是直接經過jpa查詢到的List類型的值則會進行實例化,即一樣是List類型,一個是null,一個"[]"。github

  最簡單的辦法是在null值的地方所有實例化一個new ArrayList<?>(0);可是這樣會修改不少地方,並且對於這些狀況都要進行實例化分配內存不是那麼的理想。spring

  因此就在springMvc轉json的地方作手腳。json

  咱們都知道springMvc是使用jackson作的json序列化工具。  spring-mvc

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

     能夠配置其一個MappingJackson2HttpMessageConverter類,這個類同時能夠作另外一個事情,防止ie對json數據當作文件進行下載。mvc

  MappingJackson2HttpMessageConverter類中能夠取到一個ObjectMapper,即jackson序列化的主類。app

     查看代碼看到:ide

    

    @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); } }

     看到使用了spring-boot

    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" />
     
       <!--其餘配置 -->
相關文章
相關標籤/搜索