Summary of Jackson's handling in Spring Boot framework

1. Preface

Generally, when using the Spring Boot framework, if we do not specify the serialization type of the interface, we will use the Jackson framework integrated by default by the Spring Boot framework for processing, and serialize the data responded by the server into JSON format through the Jackson framework.

This paper mainly summarizes the experience of using Jackson in the Spring Boot framework, and also states the problems and solutions encountered in the actual development scenario.

The source code address involved in this article: https://gitee.com/dt_research_institute/code-in-action

PS: at present, there are many frameworks for JSON serialization on the market, including Jackson, Gson and fastjason. If the developer has no special requirements for the serialization framework, I suggest to directly use Jackson, which is integrated by default by the Spring Boot framework, and there is no need to replace it.

2. Unified serialization time format

In our interface, serialization of time type fields is one of the most common requirements. Generally, front and rear developers will uniformly restrict time fields, which helps to unify coding specifications during coding development.

In the Spring Boot framework, if the Jackson processing framework is used and there is no configuration, Jackson will have different serialization formats for different time type fields.

Let's start with a simple example, user The Java entity class code is as follows:

public class User {

    private String name;

    private Integer age;

    private LocalDateTime birthday;

    private Date studyDate;

    private LocalDate workDate;
    
    private Calendar firstWorkDate;
    
    public static User buildOne(){
        User user=new User();
        LocalDateTime now=LocalDateTime.now();
        user.setWorkDate(now.plusYears(25).toLocalDate());
        user.setStudyDate(Date.from(now.plusYears(5).atZone(ZoneId.systemDefault()).toInstant()));
        user.setName("full name-"+RandomUtil.randomString(5));
        user.setAge(RandomUtil.randomInt(0,100));
        user.setBirthday(now);
        user.setFirstWorkDate(Calendar.getInstance());
        return user;
    }
    
    //getter and setter...
}

The interface code layer is also very simple. Just return a User entity object. The code is as follows:

@RestController
public class UserApplication {


    @GetMapping("/queryOne")
    public ResponseEntity<User> queryOne(){
        return ResponseEntity.ok(User.buildOne());
    }
}

If we do not have any configuration for the framework code, we call the interface / queryOne to get the returned result data as shown in the figure below:

The Jackson serialization framework has different serialization operations for four different time type fields. If we have formatting requirements for time fields, what should we do?

2.1 annotation through @ JsonFormat

The most direct and simple way is to mark the time field to be formatted by using the @ JsonFormat annotation provided by Jackson, and write our time formatting character in the @ JsonFormat annotation, user The Java code is as follows:

public class User {

    private String name;

    private Integer age;

    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime birthday;

    private Date studyDate;

    private LocalDate workDate;

    private Calendar firstWorkDate;
    //getter and setter...
}

At this time, we call the interface and get the return result as shown in the figure below:

By annotating @ JsonFormat on the birthday field, the Jackson framework will eventually serialize the field into the format type we annotate.

2.2 configure global application yml

Although @ JsonFormat annotation can solve the problem, there will be a lot of time fields involved in our actual development. If all the time fields in the project are annotated by annotation, the development workload will be large, and there will inevitably be omissions when multiple teams work together to code. Therefore, @The JsonFormat annotation is only applicable to constraining the time field of serialized response for specific interfaces and specific scenarios. From a global perspective, developers should consider Global configuration in YML configuration file

For the global configuration of Jackson in the Spring Boot framework, we When configuring YML, editors such as IDEA will give corresponding prompts, and the included attributes are as follows:

Developers can use org springframework. boot. autoconfigure. jackson. JacksonProperties. Java to view the source code information of all configurations

Configuration propertiesexplain
date-formatDate field formatting, for example: yyyy MM DD HH: mm: SS

For the formatting of date fields, we only need to use the date format attribute for configuration, application The YML configuration is as follows:

spring:
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss

Of course, if necessary, you also need to configure the time zone property, but if this property is not configured, Jackson will use the system default time zone.

We can see the time processing logic for Jackson from the source code of Spring Boot, Jackson autoconfiguration Some codes in Java are as follows:

private void configureDateFormat(Jackson2ObjectMapperBuilder builder) {
    // We support a fully qualified class name extending DateFormat or a date
    // pattern string value
    String dateFormat = this.jacksonProperties.getDateFormat();
    if (dateFormat != null) {
        try {
            Class<?> dateFormatClass = ClassUtils.forName(dateFormat, null);
            builder.dateFormat((DateFormat) BeanUtils.instantiateClass(dateFormatClass));
        }
        catch (ClassNotFoundException ex) {
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat(dateFormat);
            // Since Jackson 2.6.3 we always need to set a TimeZone (see
            // gh-4170). If none in our properties fallback to the Jackson's
            // default
            TimeZone timeZone = this.jacksonProperties.getTimeZone();
            if (timeZone == null) {
                timeZone = new ObjectMapper().getSerializationConfig().getTimeZone();
            }
            simpleDateFormat.setTimeZone(timeZone);
            builder.dateFormat(simpleDateFormat);
        }
    }
}

From the above code, we can see the processing logic:

  • Get the dateFormat attribute field from the yml configuration file
  • First, through classutils The forname method is used to determine whether the developer configures a format class. If the developer configures a format class, the dateFormat property is directly configured
  • If the class cannot be found, a ClassNotFoundException exception is caught. By default, the SimpleDateFormat class provided with the JDK is used for initialization

Finally, we are in application The global Jackson formatting information for date processing is configured in the YML configuration file. At this time, let's look at the content of the response of the / queryOne interface? As shown below:

From the figure, we can find that except for the fields of LocalDate type, the Date types including hour, minute and second types: LocalDateTime, Date and Calendar all serialize the Date into yyyy MM DD HH: mm: SS format according to our requirements, which meets our requirements.

3.Jackson's configuration options in the Spring Boot framework

In the above time field serialization processing, we already know how to configure. What are the main configuration items for Jackson in the Spring Boot framework? We can see from the prompt of IDEA that the configuration is as follows:

In the above 12 attributes, the configuration of each attribute will have different effects on Jackson. Next, we will explain the function of each attribute configuration one by one

3.1 date format

Date format we already know the function of this attribute, mainly for the formatting of date fields

3.2 time zone

The time zone field is also of the same type as the date field. If different time zones are used, the response results of the final date type field will be different

There are two ways to represent time zones:

  • Specify the name of the time zone, for example: Asia/Shanghai,America/Los_Angeles
  • Do + or - custom operations for hours, minutes and seconds through GMT in Greenwich mean time

By specifying the name of the time zone, suppose we specify that the current project is America/Los_Angeles, what is the effect of interface response data?

PS: if the time zone name is not clear, you can generally view it in the / usr/share/zoneinfo directory of the Linux server, as shown in the following figure:

application.yml:

spring:
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: America/Los_Angeles

The renderings are as follows:

We are analyzing in combination with the code:

//User.java
public static User buildOne(){
    User user=new User();
    LocalDateTime now=LocalDateTime.now();
    user.setWorkDate(now.plusYears(25).toLocalDate());
    user.setStudyDate(Date.from(now.plusYears(5).atZone(ZoneId.systemDefault()).toInstant()));
    user.setName("full name-"+RandomUtil.randomString(5));
    user.setAge(RandomUtil.randomInt(0,100));
    user.setBirthday(now);
    user.setFirstWorkDate(Calendar.getInstance());
    return user;
}

Since there is a 16 hour difference between the Los Angeles time zone and the Shanghai time zone, the Jackson framework does different types of processing for date serialization, but we can also see the difference

  • For fields of LocalDateTime and LocalDate types, Jackson's time zone setting will not affect this field (because these two date types have their own time zone properties)
  • Fields of Date and Calendar types are affected by the time zone setting of Jackson serialization framework

The other way is to add and subtract through Greenwich mean time (GMT), which is mainly supported by two formats:

  • GMT+HHMM or GMT-HHMM or GMT+H: where HH represents hours, MM represents minutes, and the value range is 0-9. For example, GMT+8 represents Dongba District, that is, Beijing time
  • GMT+HH:MM or GMT-HH:MM: where HH represents hours, MM represents minutes, and the value range is 0-9, which is similar to the above meaning

You can write your own test code for testing. Examples are as follows:

public class TimeTest {
    public static void main(String[] args) {
        LocalDateTime localDateTime=LocalDateTime.now();
        DateTimeFormatter dateTimeFormatter=DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        System.out.println(localDateTime.format(dateTimeFormatter));
        System.out.println(LocalDateTime.now(ZoneId.of("GMT+0901")).format(dateTimeFormatter));
        System.out.println(LocalDateTime.now(ZoneId.of("GMT+09:01")).format(dateTimeFormatter));
    }
}

3.3 locale localization

Locale variable setting during JSON serialization

3.4 visibility access level

Jackson supports reading values from private fields, but does not do so by default. If there are different serialization and deserialization requirements in our project, we can configure visibility in the configuration file

We'll use the user The get method modifier of the name attribute in Java code is changed from public to private, and other fields remain unchanged

The code is as follows:

public class User {

    private String name;

    private Integer age;
    private Date nowDate;

    private LocalDateTime birthday;

    private Date studyDate;

    private LocalDate workDate;

    private Calendar firstWorkDate;

    //Modify the getter method modifier from public to private
    private String getName() {
        return name;
    }
    //other setter and getter
}

At this time, we call the / queryOne interface, and the response results are as follows:

From the results, we can see that jackson did not get the field when serializing because we set the getter method of the name attribute to private

At this point, we will modify the application The configuration of YML is as follows:

spring:
  jackson:
    visibility:
      getter: any

We set the getter to any level and call the / queryOne interface. The response results are as follows:

As can be seen from the figure, the name attribute appears again in jackson's serialization result, which means that even though the attribute of the name field and the getter method are private, jackson still obtains the value of the member variable and serializes it.

The above effect can be achieved by setting the visibility property. Developers make their own choices according to their own needs.

3.5 property naming strategy

Generally speaking, we usually target the entity class attributes in java code Camel case However, the Jackson serialization framework also provides more serialization strategies, and the property naming strategy configures this property.

Let's first look at how the Spring Boot framework configures jackson's naming policy

JacksonAutoConfiguration.java
private void configurePropertyNamingStrategyField(Jackson2ObjectMapperBuilder builder, String fieldName) {
    // Find the field (this way we automatically support new constants
    // that may be added by Jackson in the future)
    Field field = ReflectionUtils.findField(PropertyNamingStrategy.class, fieldName,
                                            PropertyNamingStrategy.class);
    Assert.notNull(field, () -> "Constant named '" + fieldName + "' not found on "
                   + PropertyNamingStrategy.class.getName());
    try {
        builder.propertyNamingStrategy((PropertyNamingStrategy) field.get(null));
    }
    catch (Exception ex) {
        throw new IllegalStateException(ex);
    }
}

Get the value of the member variable in the PropertyNamingStrategy class directly through reflection

Propertynaming strategy defines named policy constant member variables in Jackson(2.11.4) framework

package com.fasterxml.jackson.databind;

//other import

public class PropertyNamingStrategy // NOTE: was abstract until 2.7
    implements java.io.Serializable
{
    /**
     * Naming convention used in languages like C, where words are in lower-case
     * letters, separated by underscores.
     * See {@link SnakeCaseStrategy} for details.
     *
     * @since 2.7 (was formerly called {@link #CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES})
     */
    public static final PropertyNamingStrategy SNAKE_CASE = new SnakeCaseStrategy();

    /**
     * Naming convention used in languages like Pascal, where words are capitalized
     * and no separator is used between words.
     * See {@link PascalCaseStrategy} for details.
     *
     * @since 2.7 (was formerly called {@link #PASCAL_CASE_TO_CAMEL_CASE})
     */
    public static final PropertyNamingStrategy UPPER_CAMEL_CASE = new UpperCamelCaseStrategy();

    /**
     * Naming convention used in Java, where words other than first are capitalized
     * and no separator is used between words. Since this is the native Java naming convention,
     * naming strategy will not do any transformation between names in data (JSON) and
     * POJOS.
     *
     * @since 2.7 (was formerly called {@link #PASCAL_CASE_TO_CAMEL_CASE})
     */
    public static final PropertyNamingStrategy LOWER_CAMEL_CASE = new PropertyNamingStrategy();
    
    /**
     * Naming convention in which all words of the logical name are in lower case, and
     * no separator is used between words.
     * See {@link LowerCaseStrategy} for details.
     * 
     * @since 2.4
     */
    public static final PropertyNamingStrategy LOWER_CASE = new LowerCaseStrategy();

    /**
     * Naming convention used in languages like Lisp, where words are in lower-case
     * letters, separated by hyphens.
     * See {@link KebabCaseStrategy} for details.
     * 
     * @since 2.7
     */
    public static final PropertyNamingStrategy KEBAB_CASE = new KebabCaseStrategy();

    /**
     * Naming convention widely used as configuration properties name, where words are in
     * lower-case letters, separated by dots.
     * See {@link LowerDotCaseStrategy} for details.
     *
     * @since 2.10
     */
    public static final PropertyNamingStrategy LOWER_DOT_CASE = new LowerDotCaseStrategy();
    
    //others...
}

From the source code, we can see that there are six strategies for us to configure. The configuration examples are as follows:

spring:
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    locale: zh_CN
    time-zone: GMT+8
    visibility:
      getter: any
    property-naming-strategy: LOWER_CAMEL_CASE

SNAKE_CASE

SNAKE_ The main rules contained in case are shown in SnakeCaseStrategy:

  • All uppercase characters in the java attribute name will be converted to two characters, the underscore and the lowercase form of the character, for example, userName will be converted to user_name, for continuous uppercase characters, the next uppercase character will be underlined, and the following uppercase characters will be lowercase. For example, the WWW will be converted to the_www
  • When the initial letter is capitalized, it is converted to lowercase. For example, results will be converted to results, not to_ results
  • If the attribute already contains underscores, only lower case conversion is performed
  • When the underscore appears in the first place, it will be removed, such as attribute name:_ User will be converted to user

The real effect is as follows:

UPPER_CAMEL_CASE

UPPER_CAMEL_CASE as the name suggests, the rules of hump nomenclature, but the initial letter will be converted to uppercase. See UpperCamelCaseStrategy

The real effect picture is as follows:

LOWER_CAMEL_CASE

LOWER_CAMEL_CASE effect and UPPER_CAMEL_CASE is just the opposite. Its initial will become lowercase. See LowerCamelCaseStrategy

The renderings are as follows:

LOWER_CASE

LOWER_ From the naming of case, it is obvious to change all attribute names to lowercase. See LowerCaseStrategy

KEBAB_CASE

KEBAB_CASE strategy and snake_ The case rule is similar, except that the underline becomes a horizontal line - see KebabCaseStrategy

The renderings are as follows:

LOWER_DOT_CASE

LOWER_DOT_CASE strategy and kebab_ The case rules are similar, except that the horizontal line becomes a point, See details LowerDotCaseStrategy

The renderings are as follows:

Summary: after looking at the above policies with so many attribute names, in fact, each type is only needed in different scenarios. If the default policy name given by jackson above cannot be met, we can also see from the source code that the customized implementation class can also meet the personalized needs of enterprises, which is very convenient.

3.6 configuration of mapper general function switch

mapper attribute is a Map type. It mainly defines switch attributes for MapperFeature. Do you want to enable these attributes

/**
* Jackson general purpose on/off features.
*/
private final Map<MapperFeature, Boolean> mapper = new EnumMap<>(MapperFeature.class);

In mapperfeature In Java, we can trace the source code to see:

/**
 * Enumeration that defines simple on/off features to set
 * for {@link ObjectMapper}, and accessible (but not changeable)
 * via {@link ObjectReader} and {@link ObjectWriter} (as well as
 * through various convenience methods through context objects).
 *<p>
 * Note that in addition to being only mutable via {@link ObjectMapper},
 * changes only take effect when done <b>before any serialization or
 * deserialization</b> calls -- that is, caller must follow
 * "configure-then-use" pattern.
 */
public enum MapperFeature implements ConfigFeature
{
    //.......
}

MapperFeature is an enumeration type. For some features of the current jackson, the switch attributes are defined by enumerating variables, which is also convenient for users.

It mainly includes the following enumeration variables:

  • USE_ANNOTATIONS:
  • USE_GETTERS_AS_SETTERS
  • PROPAGATE_TRANSIENT_MARKER
  • AUTO_DETECT_CREATORS
  • AUTO_DETECT_FIELDS
  • AUTO_DETECT_GETTERS
  • AUTO_DETECT_IS_GETTERS
  • AUTO_DETECT_SETTERS
  • REQUIRE_SETTERS_FOR_GETTERS
  • ALLOW_FINAL_FIELDS_AS_MUTATORS
  • INFER_PROPERTY_MUTATORS
  • INFER_CREATOR_FROM_CONSTRUCTOR_PROPERTIES
  • CAN_OVERRIDE_ACCESS_MODIFIERS
  • OVERRIDE_PUBLIC_ACCESS_MODIFIERS
  • USE_STATIC_TYPING
  • USE_BASE_TYPE_AS_DEFAULT_IMPL
  • DEFAULT_VIEW_INCLUSION
  • SORT_PROPERTIES_ALPHABETICALLY
  • ACCEPT_CASE_INSENSITIVE_PROPERTIES
  • ACCEPT_CASE_INSENSITIVE_ENUMS
  • ACCEPT_CASE_INSENSITIVE_VALUES
  • USE_WRAPPER_NAME_AS_PROPERTY_NAME
  • USE_STD_BEAN_NAMING
  • ALLOW_EXPLICIT_PROPERTY_RENAMING
  • ALLOW_COERCION_OF_SCALARS
  • IGNORE_DUPLICATE_MODULE_REGISTRATIONS
  • IGNORE_MERGE_FOR_UNMERGEABLE
  • BLOCK_UNSAFE_POLYMORPHIC_BASE_TYPES

3.7 serialization feature switch configuration

Similar to mapper, the serialization attribute is also a Map type attribute

/**
* Jackson on/off features that affect the way Java objects are serialized.
*/
private final Map<SerializationFeature, Boolean> serialization = new EnumMap<>(SerializationFeature.class);

3.8 deserialization switch configuration

Deserialization deserialization configuration

/**
* Jackson on/off features that affect the way Java objects are deserialized.
 */
private final Map<DeserializationFeature, Boolean> deserialization = new EnumMap<>(DeserializationFeature.class);

3.9 parser configuration

3.10 generator configuration

3.11 defaultPropertyInclusion serialization contains the property configuration

This property is an enumeration configuration, mainly including:

  • ALWAYS: as the name suggests, ALWAYS contains, regardless of the value of the attribute
  • NON_NULL: only properties with non empty values will contain properties
  • NON_ Exception: attribute with non empty value or attribute of Optional type
  • NON_EMPTY: null value attribute does not contain
  • NON_DEFAULT: this field is serialized without jackson's default rules. See Example
  • CUSTOM: CUSTOM rules
  • USE_DEFAULTS: configure the attribute fields that use this rule. The annotation rules on the class will take precedence. Otherwise, the global serialization rules will be used. See Example

CUSTOM custom rule requires the developer to use @ JsonInclude annotation on the attribute field and specify valueFilter attribute, which needs to pass a Class. An example is as follows:

//User.java
//The specified value level is CUSTOM
@JsonInclude(value = JsonInclude.Include.CUSTOM, valueFilter = StringFilter.class)
private String name;

StringFilter is the basis for judging whether it is not empty. The basis is defined by the developer. If it returns true, it will be excluded, and if it returns false, it will not be excluded. An example is as follows:

//User defined non empty judgment rule
public class StringFilter {
    @Override
    public boolean equals(Object other) {
        if (other == null) {
            // Filter null's.
            return true;
        }

        // Filter "custom_string".
        return "custom_string".equals(other);
    }
}

4. What spring boot does for Jackson's agreed configuration

In the previous article, we have learned about Jackson's configuration items in the Spring Boot framework in detail. What will Spring Boot do when agreeing to configure the Jackson framework?

In the spring-boot-autoconfigure-x.x.jar package of Spring Boot, we can see the processing source code of the Spring Boot framework for jackson, as shown in the following figure:

It mainly includes three classes:

  • jackson properties: the spring boot framework provides jackson's configuration property class, that is, the developer in application Configuration item properties in YML configuration file
  • Jackson autoconfiguration: Jackson's default injection configuration class
  • Jackson2ObjectMapperBuilderCustomizer: customize the configuration auxiliary interface used to inject jackson

The core class is Jackson autoconfiguration Java, which is the key configuration class for the Spring Boot framework to inject Jackson related entity beans into the Spring container. Its main functions:

  • Inject Jackson's ObjectMapper entity Bean into the Spring container
  • Inject the ParameterNamesModule entity Bean into the Spring container
  • Inject Jackson2ObjectMapperBuilder entity Bean
  • Inject JsonComponentModule entity Bean
  • Inject the StandardJackson2ObjectMapperBuilderCustomizer entity Bean, which is the implementation class of the above Jackson2ObjectMapperBuilderCustomizer. It is mainly used to receive Jackson properties, receive Jackson's external configuration properties, and finally execute the customize method to construct the Jackson2ObjectMapperBuilder property required by ObjectMapper, Finally, prepare for the assignment of the ObjectMapper property

The source code is as follows:

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ObjectMapper.class)
public class JacksonAutoConfiguration {

	private static final Map<?, Boolean> FEATURE_DEFAULTS;

	static {
		Map<Object, Boolean> featureDefaults = new HashMap<>();
		featureDefaults.put(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
		featureDefaults.put(SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS, false);
		FEATURE_DEFAULTS = Collections.unmodifiableMap(featureDefaults);
	}

	@Bean
	public JsonComponentModule jsonComponentModule() {
		return new JsonComponentModule();
	}

	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass(Jackson2ObjectMapperBuilder.class)
	static class JacksonObjectMapperConfiguration {

		@Bean
		@Primary
		@ConditionalOnMissingBean
		ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
			return builder.createXmlMapper(false).build();
		}

	}

	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass(ParameterNamesModule.class)
	static class ParameterNamesModuleConfiguration {

		@Bean
		@ConditionalOnMissingBean
		ParameterNamesModule parameterNamesModule() {
			return new ParameterNamesModule(JsonCreator.Mode.DEFAULT);
		}

	}

	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass(Jackson2ObjectMapperBuilder.class)
	static class JacksonObjectMapperBuilderConfiguration {

		@Bean
		@Scope("prototype")
		@ConditionalOnMissingBean
		Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder(ApplicationContext applicationContext,
				List<Jackson2ObjectMapperBuilderCustomizer> customizers) {
			Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
			builder.applicationContext(applicationContext);
			customize(builder, customizers);
			return builder;
		}

		private void customize(Jackson2ObjectMapperBuilder builder,
				List<Jackson2ObjectMapperBuilderCustomizer> customizers) {
			for (Jackson2ObjectMapperBuilderCustomizer customizer : customizers) {
				customizer.customize(builder);
			}
		}

	}
    @Configuration(proxyBeanMethods = false)
	@ConditionalOnClass(Jackson2ObjectMapperBuilder.class)
	@EnableConfigurationProperties(JacksonProperties.class)
	static class Jackson2ObjectMapperBuilderCustomizerConfiguration {

		@Bean
		StandardJackson2ObjectMapperBuilderCustomizer standardJacksonObjectMapperBuilderCustomizer(
				ApplicationContext applicationContext, JacksonProperties jacksonProperties) {
			return new StandardJackson2ObjectMapperBuilderCustomizer(applicationContext, jacksonProperties);
		}

		static final class StandardJackson2ObjectMapperBuilderCustomizer
				implements Jackson2ObjectMapperBuilderCustomizer, Ordered {

			private final ApplicationContext applicationContext;

			private final JacksonProperties jacksonProperties;

			StandardJackson2ObjectMapperBuilderCustomizer(ApplicationContext applicationContext,
					JacksonProperties jacksonProperties) {
				this.applicationContext = applicationContext;
				this.jacksonProperties = jacksonProperties;
			}

			@Override
			public int getOrder() {
				return 0;
			}

			@Override
			public void customize(Jackson2ObjectMapperBuilder builder) {

				if (this.jacksonProperties.getDefaultPropertyInclusion() != null) {
					builder.serializationInclusion(this.jacksonProperties.getDefaultPropertyInclusion());
				}
				if (this.jacksonProperties.getTimeZone() != null) {
					builder.timeZone(this.jacksonProperties.getTimeZone());
				}
				configureFeatures(builder, FEATURE_DEFAULTS);
				configureVisibility(builder, this.jacksonProperties.getVisibility());
				configureFeatures(builder, this.jacksonProperties.getDeserialization());
				configureFeatures(builder, this.jacksonProperties.getSerialization());
				configureFeatures(builder, this.jacksonProperties.getMapper());
				configureFeatures(builder, this.jacksonProperties.getParser());
				configureFeatures(builder, this.jacksonProperties.getGenerator());
				configureDateFormat(builder);
				configurePropertyNamingStrategy(builder);
				configureModules(builder);
				configureLocale(builder);
			}

			//more configure methods...
	}
}

Summary: through a series of methods, finally construct an ObjectMapper object available at the production level for serialization and deserialization of Java objects in the Spring Boot framework.

5. Examples of Jackson's common annotations

Note: content source of this summary https://www.baeldung.com/jackson-annotations , if you don't use Jackson's annotation in your work, you can take a look at this article, which is a very good supplement.

5.1 serialization

5.1.1 @JsonAnyGetter

@The JsonAnyGetter annotation can flexibly use the Map type as the attribute field

Entity classes are as follows:

public class ExtendableBean {

    public String name;
    private Map<String, String> properties;

    @JsonAnyGetter
    public Map<String, String> getProperties() {
        return properties;
    }

    public ExtendableBean(String name) {
        this.name = name;
        this.properties=new HashMap<String, String>();
    }


    public void add(String key,String value){
        this.properties.put(key,value);
    }
}

By serializing the entity Bean, we will get all keys in the Map attribute as attribute values. The test serialization code is as follows:

@Test
public void whenSerializingUsingJsonAnyGetter_thenCorrect()
  throws JsonProcessingException {
 
    ExtendableBean bean = new ExtendableBean("My bean");
    bean.add("attr1", "val1");
    bean.add("attr2", "val2");

    String result = new ObjectMapper().writeValueAsString(bean);
 
    assertThat(result, containsString("attr1"));
    assertThat(result, containsString("val1"));
}

The final output results are as follows:

{
    "name":"My bean",
    "attr2":"val2",
    "attr1":"val1"
}

If the @ JsonAnyGetter annotation is not used, the final serialization result will be under the properties property, and the result is as follows:

{
    "name": "My bean",
    "properties": {
        "attr2": "val2",
        "attr1": "val1"
    }
}

5.1.2 @JsonGetter

@The JsonGetter annotation is an annotation that replaces @ JsonProperty and can label a method as a getter method

For example, in the following example, we use the annotation @ JsonGetter to take the method getTheName() as the getter method of the property name

public class MyBean {
    public int id;
    private String name;

    @JsonGetter("name")
    public String getTheName() {
        return name;
    }
}

5.1.3 @JsonPropertyOrder

You can specify the serialization order of properties by using the @ JsonPropertyOrder annotation

Entity bean s are defined as follows:

@JsonPropertyOrder({ "name", "id" })
public class MyBean {
    public int id;
    public String name;
}

The final serialization result is:

{
    "name":"My bean",
    "id":1
}

You can also specify alphabetical sorting through @ jsonpropertyorder (alpha = true), and the response result will be:

{
    "id":1,
    "name":"My bean"
}

5.1.4 @JsonRawValue

@The JsonRawValue annotation can specify that the string attribute class is json, as shown in the following code:

public class RawBean {
    public String name;

    @JsonRawValue
    public String json;
}

Create an example of RawBean and assign a value to the attribute json. The code is as follows:

 RawBean bean = new RawBean("My bean", "{\"attr\":false}");

String result = new ObjectMapper().writeValueAsString(bean);

The final serialization result is as follows:

{
    "name":"My bean",
    "json":{
        "attr":false
    }
}

5.1.5 @JsonValue

@The JsonValue annotation is mainly used to serialize a single method of the entire instance object. For example, in an enumeration class, the @ JsonValue annotation is annotated. The code is as follows:

public enum  TypeEnumWithValue {
    TYPE1(1, "Type A"), TYPE2(2, "Type 2");

    private Integer id;
    private String name;

    TypeEnumWithValue(Integer id, String name) {
        this.id = id;
        this.name = name;
    }

    @JsonValue
    public String getName() {
        return name;
    }
}

The test code is as follows:

String enumAsString = new ObjectMapper()
                .writeValueAsString(TypeEnumWithValue.TYPE1);
System.out.println(enumAsString);

The final result of serializing the code will be:

"Type A"

5.1.6 @JsonRootName

@The JsonRootName annotation is intended to add a wrapper object to the currently serialized entity object.

Examples are as follows:

//RootUser.java
public class RootUser {

    private String name;
    private String title;

    public RootUser(String name, String title) {
        this.name = name;
        this.title = title;
    }
    
 	//getter and setters  
}

In the above entity class, under normal circumstances, if you want the serial number RootUser object, the result format is:

{
    "name": "name1",
    "title": "title1"
}

After adding @ JsonRootName annotation to RootUser, the class changes as follows:

//RootUser.java
@JsonRootName(value = "root")
public class RootUser {

    private String name;
    private String title;

    public RootUser(String name, String title) {
        this.name = name;
        this.title = title;
    }
    
 	//getter and setters  
}

Enable wrap for ObjectMapper objects_ ROOT_ Value characteristic, test code is as follows:

ObjectMapper objectMapper=new ObjectMapper();
objectMapper.enable(SerializationFeature.WRAP_ROOT_VALUE);
String result=objectMapper.writeValueAsString(new RootUser("name1","title1"));

The final result of serializing JSON is as follows:

{
    "root": {
        "name": "name1",
        "title": "title1"
    }
}

5.1.7 @JsonSerialize

@The JsonSerialize annotation allows developers to customize the serialization implementation to see the code implementation

public class EventWithSerializer {
    public String name;

    @JsonSerialize(using = CustomDateSerializer.class)
    public Date eventDate;
    
    public Date publishDate;
    
    //getter and setter...
}

In the above code, for the eventDate field, we customized a serialization implementation class CustomDateSerializer by using the @ JsonSerialize annotation. The implementation of this class is as follows:

//CustomDateSerializer.java
public class CustomDateSerializer extends StdSerializer<Date> {

    private static SimpleDateFormat formatter 
      = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss");

    public CustomDateSerializer() { 
        this(null); 
    } 

    public CustomDateSerializer(Class<Date> t) {
        super(t); 
    }

    @Override
    public void serialize(
      Date value, JsonGenerator gen, SerializerProvider arg2) 
      throws IOException, JsonProcessingException {
        gen.writeString(formatter.format(value));
    }
}

The final serialized result format is as follows:

{
    "name": "name",
    "eventDate": "24-03-2021 06:14:32",
    "publishDate": 1616580872574
}

From the results, we can know that we can completely customize the serialization method for a specific field, which is very convenient.

5.2 deserialization

5.2.1 @JsonCreator

@JsonCreator and @ JsonProperty annotation can achieve the effect of specifying that the property name will not be changed when deserializing entity objects

For example, JSON is as follows:

{
    "id":1,
    "theName":"My bean"
}

In the entity class, we do not have the attribute name theName, but we want to assign the theName attribute to name during deserialization. At this time, the object structure of the entity class is as follows:

public class BeanWithCreator {
    public int id;
    public String name;

    @JsonCreator
    public BeanWithCreator(
      @JsonProperty("id") int id, 
      @JsonProperty("theName") String name) {
        this.id = id;
        this.name = name;
    }
}

Add @ JsonCreator annotation to the constructor of BeanWithCreator, and point the property with @ JsonProperty annotation. The final deserialization code is as follows:

@Test
public void whenDeserializingUsingJsonCreator_thenCorrect()
  throws IOException {
 
    String json = "{\"id\":1,\"theName\":\"My bean\"}";

    BeanWithCreator bean = new ObjectMapper()
      .readerFor(BeanWithCreator.class)
      .readValue(json);
    assertEquals("My bean", bean.name);
}

5.2.2 @JacksonInject

@The JacksonInject annotation can specify that when deserializing an object, the property value is not obtained from the source JSON, but from the injection

Entity classes are as follows:

public class BeanWithInject {
    @JacksonInject
    public int id;
    
    public String name;
}

Deserialization code

@Test
public void whenDeserializingUsingJsonInject_thenCorrect()
  throws IOException {
 
    String json = "{\"name\":\"My bean\"}";
    
    InjectableValues inject = new InjectableValues.Std()
      .addValue(int.class, 1);
    BeanWithInject bean = new ObjectMapper().reader(inject)
      .forType(BeanWithInject.class)
      .readValue(json);
    
    assertEquals("My bean", bean.name);
    assertEquals(1, bean.id);
}

5.2.3 @JsonAnySetter

@The JsonAnySetter and @ JsonAnyGetter annotations have the same meaning, but for serialization and deserialization, the @ JsonAnySetter annotation can finally convert the source JSON into a Map type attribute structure

The entity code is as follows:

public class ExtendableBean {
    public String name;
    private Map<String, String> properties;

    @JsonAnySetter
    public void add(String key, String value) {
        properties.put(key, value);
    }
}

JSON source is as follows:

{
    "name":"My bean",
    "attr2":"val2",
    "attr1":"val1"
}

Through the annotation of @ JsonAnySetter, the final values of attr1 and attr2 will be added to the Map object of properties

The example code is as follows:

@Test
public void whenDeserializingUsingJsonAnySetter_thenCorrect()
  throws IOException {
    String json
      = "{\"name\":\"My bean\",\"attr2\":\"val2\",\"attr1\":\"val1\"}";

    ExtendableBean bean = new ObjectMapper()
      .readerFor(ExtendableBean.class)
      .readValue(json);
    
    assertEquals("My bean", bean.name);
    assertEquals("val2", bean.getProperties().get("attr2"));
}

5.2.4 @JsonSetter

@The JsonSetter annotation is an alternative annotation to @ JsonProperty, which is used to mark that the method is a setter method

This annotation is very useful when we need to read some JSON data, but the target entity class does not exactly match the data.

The example code is as follows:

public class MyBean {
    public int id;
    private String name;

    @JsonSetter("name")
    public void setTheName(String name) {
        this.name = name;
    }
}

By specifying setTheName as the setter method of the attribute name, the final effect can be achieved during deserialization

Examples are as follows:

@Test
public void whenDeserializingUsingJsonSetter_thenCorrect()
  throws IOException {
 
    String json = "{\"id\":1,\"name\":\"My bean\"}";

    MyBean bean = new ObjectMapper()
      .readerFor(MyBean.class)
      .readValue(json);
    assertEquals("My bean", bean.getTheName());
}

5.2.5 @JsonDeserialize

@The effect of the JsonDeserialize annotation and the serialization annotation @ JsonSerialize are consistent. The effect is different from that of deserialization for specific fields

public class EventWithSerializer {
    public String name;

    @JsonDeserialize(using = CustomDateDeserializer.class)
    public Date eventDate;
}

The CustomDateDeserializer code is as follows:

public class CustomDateDeserializer
  extends StdDeserializer<Date> {

    private static SimpleDateFormat formatter
      = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss");

    public CustomDateDeserializer() { 
        this(null); 
    } 

    public CustomDateDeserializer(Class<?> vc) { 
        super(vc); 
    }

    @Override
    public Date deserialize(
      JsonParser jsonparser, DeserializationContext context) 
      throws IOException {
        
        String date = jsonparser.getText();
        try {
            return formatter.parse(date);
        } catch (ParseException e) {
            throw new RuntimeException(e);
        }
    }
}

Finally, when deserializing JSON, the eventDate field is obtained. The test code is as follows:

@Test
public void whenDeserializingUsingJsonDeserialize_thenCorrect()
  throws IOException {
 
    String json
      = "{"name":"party","eventDate":"20-12-2014 02:30:00"}";

    SimpleDateFormat df
      = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss");
    EventWithSerializer event = new ObjectMapper()
      .readerFor(EventWithSerializer.class)
      .readValue(json);
    
    assertEquals(
      "20-12-2014 02:30:00", df.format(event.eventDate));
}

5.2.6 @JsonAlias

@The JsonAlias annotation is used to specify an alias to be associated with a field in JSON data. For final deserialization, the value can be assigned to the object during final deserialization

The entities are as follows:

public class AliasBean {
    @JsonAlias({ "fName", "f_name" })
    private String firstName;   
    private String lastName;
}

In the above code, the firstName field specifies two alias fields through the @ JsonAlias annotation, which means that fName or F can be read from JSON during deserialization_ The value of name is assigned to firstName

The test code is as follows:

@Test
public void whenDeserializingUsingJsonAlias_thenCorrect() throws IOException {
    String json = "{\"fName\": \"John\", \"lastName\": \"Green\"}";
    AliasBean aliasBean = new ObjectMapper().readerFor(AliasBean.class).readValue(json);
    assertEquals("John", aliasBean.getFirstName());
}

5.3 attribute annotation

5.3.1 @JsonIgnoreProperties

Using the @ JsonIgnoreProperties annotation to act at the class level can achieve the effect of ignoring one or more fields during serialization

The entity code is as follows:

@JsonIgnoreProperties({ "id" })
public class BeanWithIgnore {
    public int id;
    public String name;
}

Finally, when serializing the BeanWithIgnore entity object, the field id will be ignored

5.3.2 @JsonIgnore

@The JsonIgnore annotation functions at the attribute level, and this field can be ignored during serialization

The entity code is as follows:

public class BeanWithIgnore {
    @JsonIgnore
    public int id;

    public String name;
}

Finally, when serializing the BeanWithIgnore entity object, the field id will be ignored

5.3.3 @JsonIgnoreType

@JsonIgnoreType specifies that the type attribute is ignored

public class User {
    public int id;
    public Name name;

    @JsonIgnoreType
    public static class Name {
        public String firstName;
        public String lastName;
    }
}

In the above example, the type Name will be ignored

5.3.4 @JsonInclude

Use the @ JsonInclude annotation to exclude attributes with empty/null/default in the attribute value

@JsonInclude(Include.NON_NULL)
public class MyBean {
    public int id;
    public String name;
}

Include. Is used in MyBean NON_ Null means that the entity object will not contain null values when serialized

5.3.5 @JsonAutoDetect

@JsonAutoDetect can override the default visibility level in entity object properties, such as visible and invisible private properties

The entity objects are as follows:

public class PrivateBean {
    private int id;
    private String name;

    public PrivateBean(int id, String name) {
        this.id = id;
        this.name = name;
    }
}

In the PrivateBean, no public getter method is set for the attribute field id and name. At this time, jackson will prompt an error if we serialize the entity object directly

Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class com.xiaoymin.boot.action.jackson.model.PrivateBean and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS)

We modified the code in PrivateBean and added @ JsonAutoDetect annotation. The code is as follows:

@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
public class PrivateBean {
    private int id;
    private String name;

    public PrivateBean(int id, String name) {
        this.id = id;
        this.name = name;
    }
}

At this point, when serializing the entity object, you will get the response result

PrivateBean bean = new PrivateBean(1, "My bean");
String result = new ObjectMapper().writeValueAsString(bean);
System.out.println(result);

5.4 general notes

5.4.1 @JsonProperty

We can add the @ JsonProperty annotation to indicate the property name in JSON.

When there is no standard getter/setter method in the entity object, we can use this annotation to specify the attribute name, which is convenient for the jackson framework to serialize / deserialize

public class MyBean {
    public int id;
    private String name;

    @JsonProperty("name")
    public void setTheName(String name) {
        this.name = name;
    }

    @JsonProperty("name")
    public String getTheName() {
        return name;
    }
}

5.4.2 @JsonFormat

For the date field, you can format the output by using the @ JsonFormat annotation

public class EventWithFormat {
    public String name;

    @JsonFormat(
      shape = JsonFormat.Shape.STRING,
      pattern = "dd-MM-yyyy hh:mm:ss")
    public Date eventDate;
}

5.4.3 @JsonUnwrapped

@The jsonun wrapped annotation can specify whether the jackson framework needs to wrap the field during serialization / deserialization

Example code:

public class UnwrappedUser {
    public int id;

    @JsonUnwrapped
    public Name name;
    
    //getter and setter...

    public static class Name {
        public String firstName;
        public String lastName;
        //getter and setter
    }
}

The name attribute is annotated with @ JsonUnwrapped annotation. When the object is finally serialized, it will be different from the normal case

UnwrappedUser.Name name = new UnwrappedUser.Name("John", "Doe");
UnwrappedUser user = new UnwrappedUser(1, name);

String result = new ObjectMapper().writeValueAsString(user);

Our results are as follows:

{
    "id": 1,
    "firstName": "John",
    "lastName": "Doe"
}

5.4.4 @JsonView

Specify whether attributes are included in serialization / deserialization through View

The example code is as follows:

View definition

public class Views {
    public static class Public {}
    public static class Internal extends Public {}
}

Entity code:

public class Item {
    @JsonView(Views.Public.class)
    public int id;

    @JsonView(Views.Public.class)
    public String itemName;

    @JsonView(Views.Internal.class)
    public String ownerName;
    //getter and setter..

}

Final serialization code example:

Item item = new Item(2, "book", "John");

String result = new ObjectMapper().writerWithView(Views.Public.class).writeValueAsString(item);
System.out.println(result);

Final serialization result output:

{"id":2,"itemName":"book"}

Keywords: Java Spring Spring Boot

Added by Formula on Fri, 14 Jan 2022 22:49:41 +0200