Automatic detection of Jackson serialization

1. Background

There was a problem online today. I used the springMVC RestController interface to return json data to the client, and found that all the properties in one of the models were serialized twice, and one time was uppercase, and the other was lowercase. Part of the structure is as follows:

                "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
                    }
                ]

 The model structure is as follows:

 

 

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 ...
}

 A simple configuration of springMVC serialization is made as follows:

public class HotelMappingJacksonHttpMessageConverter extends MappingJackson2HttpMessageConverter {

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

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

    private void setSelfConfiguration() {
        // any properties are visible
        super.getObjectMapper().setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
        // filter for null
        super.getObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_NULL);
    }
}

2. Verification

The problem is relatively easy to restore, a simple demo is as follows:

 

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 serialization
        ObjectMapper objectMapper = new ObjectMapper();
        //Set any field to be visible
        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}

Trace the source code, found in the class POJOPropertiesCollector

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

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

 Attribute serialization will determine the access rights of attributes, so I won't post a lot of source code here.

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

 Serialize according to get/set method

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

 The following get method, gets the method name through reflection, intercepts the name toLowerCase after the get, and uses it as the serialized 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 obtains model attributes and get/set methods based on reflection. The sequence of serialization is fields and methods (get/set). By default, only public-modified fields and public-modified get/set methods are serialized.

Therefore, the conclusion that the above output results are output twice is that the first output is the serialized content of the attribute, and the second output is the serialized content of the get method.

Once the problem is located, the solution is relatively simple. There are many solutions, such as: replacing the serialization of the get/set method, switching to Google's Gson serialization, etc. In order to reduce the risk, I chose the first solution.

The demo is modified as follows:

 

public class JacksonTest {

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

        //jackson serialization
        ObjectMapper objectMapper = new ObjectMapper();
        //Shield the serialization of the get method
        objectMapper.setVisibility(PropertyAccessor.GETTER, JsonAutoDetect.Visibility.NONE);
        // set any property visible
        objectMapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
        System.out.println(objectMapper.writeValueAsString(userBean));
    }
}
输出结果:{"Name":"Li Lei","Content":"I am Li Lei","Age":20}

 

 

3. Solutions

Modify the configuration information of springMVC in the project as follows:

 

public class HotelMappingJacksonHttpMessageConverter extends MappingJackson2HttpMessageConverter {

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

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

    private void setSelfConfiguration() {
        // any properties are visible
        super.getObjectMapper().setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
        // block get method
        super.getObjectMapper().setVisibility(PropertyAccessor.GETTER, Visibility.NONE);
        // mask null
        super.getObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_NULL);
    }
}

 The pre-release environment has passed the test and is online.

 

4. Summary

The default detection mechanism of jackson is as follows: public modified fields -> public modified getters/setters. When using jackson as a serialization tool, pay attention to the modification permissions of properties and methods.

The selection of jackson is a balance between stability and performance. Fastjson has many bugs, and gson is very powerful but inefficient. If there is no special requirement for serialization, try to use gson or a combination of gson & jackson.

 

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326272883&siteId=291194637