Jackson序列化之自动检测

标签: jackson 序列化 | 发表时间:2018-01-04 21:40 | 作者:
出处:http://www.iteye.com

一、背景

今天线上出现了一个问题,使用springMVC RestController接口返回json数据给客户端,发现其中某一个model中的所有属性,被序列化了两遍,并且一次是大写开头,一次是小写,部分结构如下:

                "promotionTags": [
                    {
                        "CornerRadius": 1,
                        "TitleFontSize": 10,
                        "Title": "返券",
                        "TitleColor": "#FF9900",
                        "Transparent": true,
                        "BackgroundColor": "#FF9900",
                        "Border": true,
                        "Transparent": true,
                        "Border": true,
                        "cornerRadius": 1,
                        "titleFontSize": 10,
                        "title": "返券",
                        "titleColor": "#FF9900",
                        "transparent": true,
                        "backgroundColor": "#FF9900",
                        "border": true,
                        "transparent": true,
                        "border": true
                    }
                ]

 model结构如下:

 

 

public class HotelLabelModel implements Serializable {

    private Double CornerRadius;

    private String BorderColor;

    private Integer TitleFontSize;

    private String Title;

    private String TitleColor;

    private Boolean Transparent;

    private String BackgroundColor;

    private Boolean Border;

    getter and setter ...
}

 对springMVC序列化做了简单的配置,如下:

public class HotelMappingJacksonHttpMessageConverter extends MappingJackson2HttpMessageConverter {

    public HotelMappingJacksonHttpMessageConverter() {
        super();
        this.setSelfConfiguration();
    }

    public HotelMappingJacksonHttpMessageConverter(ObjectMapper objectMapper) {
        super(objectMapper);
        this.setSelfConfiguration();
    }

    private void setSelfConfiguration() {
        // 任何属性可见
        super.getObjectMapper().setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
        // 过滤null
        super.getObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_NULL);
    }
}

二、验证

问题比较容易还原,简单demo如下:

 

public class JacksonTest {

    public static void main(String[] args) throws JsonProcessingException {
        //name content age
        UserBean userBean = new UserBean("Li Lei", "I am Li Lei", 20);

        //jackson序列化
        ObjectMapper objectMapper = new ObjectMapper();
        //设置任何字段可见
        objectMapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
        System.out.println(objectMapper.writeValueAsString(userBean));
    }
}
public class UserBean {

    private String Name;

    private String Content;

    private Integer Age;

    public UserBean(String name, String content, Integer age) {
        Name = name;
        Content = content;
        Age = age;
    }

    getter and setter ...
} 
输出结果:{"Name":"Li Lei","Content":"I am Li Lei","Age":20,"name":"Li Lei","content":"I am Li Lei","age":20}

跟踪源码,在类POJOPropertiesCollector中发现

protected void collectAll(){
        LinkedHashMap<String, POJOPropertyBuilder> props = new LinkedHashMap<String, POJOPropertyBuilder>();

        // First: gather basic data
        _addFields(props);
        _addMethods(props);
       ...
}

 属性序列化会判断属性的访问权限,在此就不大量的贴源码了

protected void _addFields(Map<String, POJOPropertyBuilder> props)
    {
            ...
            // having explicit name means that field is visible; otherwise need to check the rules
            boolean visible = (pn != null);
            if (!visible) {
                visible = _visibilityChecker.isFieldVisible(f);
            }
            ...
    }

 根据get/set方法来序列化

protected void _addMethods(Map<String, POJOPropertyBuilder> props)
    {
        final AnnotationIntrospector ai = _annotationIntrospector;
        
        for (AnnotatedMethod m : _classDef.memberMethods()) {
            
            int argCount = m.getParameterCount();
            if (argCount == 0) { // getters (including 'any getter')
            	_addGetterMethod(props, m, ai);
            } else if (argCount == 1) { // setters
            	_addSetterMethod(props, m, ai);
            } else if (argCount == 2) { // any getter?
                if (ai != null  && ai.hasAnySetterAnnotation(m)) {
                    if (_anySetters == null) {
                        _anySetters = new LinkedList<AnnotatedMethod>();
                    }
                    _anySetters.add(m);
                }
            }
        }
    }

 如下get方法,通过反射拿到方法名,截取get后面的名称toLowerCase后,作为序列化的name

 

public static String okNameForRegularGetter(AnnotatedMethod am, String name,
            boolean stdNaming)
    {
        if (name.startsWith("get")) {
            
            if ("getCallbacks".equals(name)) {
                if (isCglibGetCallbacks(am)) {
                    return null;
                }
            } else if ("getMetaClass".equals(name)) {
                // 30-Apr-2009, tatu: Need to suppress serialization of a cyclic reference
                if (isGroovyMetaClassGetter(am)) {
                    return null;
                }
            }
            return stdNaming
                    ? stdManglePropertyName(name, 3)
                    : legacyManglePropertyName(name, 3);
        }
        return null;
    } 
protected static String legacyManglePropertyName(final String basename, final int offset)
    {
        final int end = basename.length();
        if (end == offset) { // empty name, nope
            return null;
        }
        // next check: is the first character upper case? If not, return as is
        char c = basename.charAt(offset);
        char d = Character.toLowerCase(c);
        
        if (c == d) {
            return basename.substring(offset);
        }
        // otherwise, lower case initial chars. Common case first, just one char
        StringBuilder sb = new StringBuilder(end - offset);
        sb.append(d);
        int i = offset+1;
        for (; i < end; ++i) {
            c = basename.charAt(i);
            d = Character.toLowerCase(c);
            if (c == d) {
                sb.append(basename, i, end);
                break;
            }
            sb.append(d);
        }
        return sb.toString();
    }

 

jackson是根据反射获取model的属性和get/set方法,序列化的顺序为字段、方法(get/set),默认只序列化public修饰的字段和public修饰的get/set方法。

所以上面输出结果输出了两遍的结论是第一遍输出的是属性的序列化内容,第二遍输出的是get方法的序列化内容。

定位到了问题,解决方案就比较简单了,方案有很多,如:换屏蔽掉get/set方法的序列化、换用Google的Gson序列化等等,为了减少风险,我选用了第一种方案。

demo修改如下:

 

public class JacksonTest {

    public static void main(String[] args) throws JsonProcessingException {
        UserBean userBean = new UserBean("Li Lei", "I am Li Lei", 20);

        //jackson序列化
        ObjectMapper objectMapper = new ObjectMapper();
        //屏蔽get方法的序列化
        objectMapper.setVisibility(PropertyAccessor.GETTER, JsonAutoDetect.Visibility.NONE);
        //设置任何属性可见
        objectMapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
        System.out.println(objectMapper.writeValueAsString(userBean));
    }
}
输出结果:{"Name":"Li Lei","Content":"I am Li Lei","Age":20}

 

 

三、解决方案

项目中修改springMVC的配置信息,如下:

 

public class HotelMappingJacksonHttpMessageConverter extends MappingJackson2HttpMessageConverter {

    public HotelMappingJacksonHttpMessageConverter() {
        super();
        this.setSelfConfiguration();
    }

    public HotelMappingJacksonHttpMessageConverter(ObjectMapper objectMapper) {
        super(objectMapper);
        this.setSelfConfiguration();
    }

    private void setSelfConfiguration() {
        // 任何属性可见
        super.getObjectMapper().setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
        // 屏蔽get方法
        super.getObjectMapper().setVisibility(PropertyAccessor.GETTER, Visibility.NONE);
        // 屏蔽null
        super.getObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_NULL);
    }
}

 预发布环境测试通过,上线。

 

四、总结

jackson默认的检测机制如下:public修饰的字段->public修饰的getter/的setter,在使用jackson作为序列化工具的时候要注意属性和方法的修饰权限。

选型jackson是考虑稳定性和性能的平衡点,Fastjson bug比较多,gson非常强大但效率比较低,如对序列化没有特殊要求尽量选用gson或gson & jackson结合使用。

 



已有 0 人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



相关 [jackson 序列化] 推荐:

Jackson序列化之自动检测

- - ITeye博客
今天线上出现了一个问题,使用springMVC RestController接口返回json数据给客户端,发现其中某一个model中的所有属性,被序列化了两遍,并且一次是大写开头,一次是小写,部分结构如下:. "Title": "返券",. "title": "返券",.  对springMVC序列化做了简单的配置,如下:.

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

- - 企业架构 - ITeye博客
Java下常见的Json类库有Gson、JSON-lib和Jackson等,Jackson相对来说比较高效,在项目中主要使用Jackson进行JSON和Java对象转换,下面给出一些Jackson的JSON操作方法. 首先去官网下载Jackson工具包,下载地址http://wiki.fasterxml.com/JacksonDownload.

Jackson简单小结

- - jackyrong
   本文简单小结下 jackson的常用操作 ,使用的是1.9.13的版本;. 1) 简单pojo转换json:.    如果要转换为良好的格式,则:. 2  接下来看如何把hashmap转换为json:. 再把hashmap反转为json. 已有 0 人发表留言,猛击->> 这里<<-参与讨论.

Json解析工具Jackson(使用注解)

- - 行业应用 - ITeye博客
       接上一篇文章 Json解析工具Jackson(简单应用),jackson在实际应用中给我们提供了一系列注解,提高了开发的灵活性,下面介绍一下最常用的一些注解.          此注解是类注解,作用是json序列化时将java bean中的一些属性忽略掉,序列化和反序列化都受影响.

極簡風格童話海報創作 – Christian Jackson

- 逸川 - ㄇㄞˋ點子靈感創意誌
極簡、簡約的平面創作一直都是我們最喜歡的藝術風格,例如這個或這個. 所以這個結合了我們童年時所聽到的童話故事或是寓言,再以極簡風格呈現的海報創作真的是讓人愛不釋手. 這些由設計師 Christian Jackson 所創作的海報運用了恰到好處的創意,以最簡單的梗來呈現出最原始的故事. 這些藝術創作帶領觀賞者回到童年時閱讀這些故事的心情,甚至讓人不由自主的露出兒時的憨笑.

JSON库之性能比较:JSON.simple VS GSON VS Jackson VS JSONP

- - Java译站
Java中哪个JSON库的解析速度是最快的. JSON已经成为当前服务器与WEB应用之间数据传输的公认标准,不过正如许多我们所习以为常的事情一样,你会觉得这是理所当然的便不再深入思考了. 我们很少会去想用到的这些JSON库到底有什么不同,但事实上它们的确是不太一样的. 因此,我们运行了一个基准测试来对常用的几个JSON库进行了测试,看看在解析不同大小的文件时哪个库的速度是最快的.

(反)序列化

- - Java - 编程语言 - ITeye博客
本章关注对象序列化API,它提供了一个框架,用来将对象编码成字节流,并从字节流中重新构建对象. “将对象编码成字节流”被称作对象序列化,相反的处理过程被称作反序列化. 序列化技术为远程通信提供了标准的线路级对象表示法,也为JavaBeans组件结构提供了标准的持久化数据格式. 第七十四条:谨慎地实现Serializable接口.

【xxx】前端技能汇总(Jackson Tian) 前端工程师们,珍重!

- - 王跸西的生命体验blog-WangBiXi.com
Frontend Knowledge Structure 项目起源. 还记得@jayli 的这幅前端知识结构图么. 缺失源图的我们,无法为此图贡献些什么,随着时间的迁移,或许有些技术点会发生改变,所以有了这个github项目. 我们可以通过协作的方式来共同维护这个项目. Git的历史记录也可以见证前端行业的一些变迁.

Java JSON(一)Jackson区别于竞争对手的7个杀手锏--转

- - 开源软件 - ITeye博客
转自:http://www.blogjava.net/wangxinsh55/archive/2012/09/06/387179.html. Jackson JSON 处理器 为一行之有效的Java JSON处理器,诸如它具有如下特点:.     简但且方便的JSON解析,以及与Java对象的相互转换.

java序列化java.io.Externalizable

- - Java - 编程语言 - ITeye博客
这次我们讲的是控制对象的序列化和反序列化. 控制序列化就是有选择的序列化对象,而不是把对象的所以内容都序列化,前篇我们的例子中介绍了transit变量和类变量(static)不被序列化,现在我们还有一种更为灵活的控制对象序列化和反序列方法,可以在序列化过程中储存其他非this对象包含的数据. 我们现在再来介绍一个接口 java.io.Externalizable.