Jackson JSON Request and Response Mapping in Spring Boot Jackson JSON Request and Response Mapping in Spring Boot

Page content

In this tutorial, we’ll learn how to map Jackson JSON request and response in Spring Boot Application with various Jackson configurations.

Jackson JSON Mapper

When you create a @RestController in a Spring Boot application to define API endpoints then Jackson JSON ObjectMapper is default HTTP Converter of your REST application which does two things:

  1. Convert the incoming JSON Request Body to Java Object of your method @RequestBody argument. Generally used in POST HTTP methods.
  2. Convert the returned Java Object to JSON Response. Generally used in GET HTTP methods.

Its good to know that the process of converting:

  • Java Object to JSON is known as Marshalling, or Serialization, and
  • JSON to Java Object is called Unmarshalling, or Deserialization

Examples

Let’s define a UserController with GET and POST HTTP methods:

package com.example.api.controller;

@RestController
@RequestMapping("/users")
public class UserController {

	@Autowired
	private UserService userService;

	@GetMapping
	public Users getAllUsers() {
		return userService.getAllUsers();
	}

	@GetMapping("/{id}")
	public User getUserById(@PathVariable Long id) {
		return userService.getUserById(id);
	}

	@PostMapping
	@ResponseStatus(HttpStatus.CREATED)
	public Long createUser(@RequestBody User user) {
		return userService.createUser(user);
	}
}

Here is our User Object Model for JSON request and response mapping:

package com.example.api.model;

public class User {

  private Long id;
	
  private String name;
	
  private LocalDate dateOfBirth;
  
  private LocalDateTime lastLogin;

	/* Getters and Setters */	
}

Let’s look at various important configurations and their impact on API request and response

Prevent Failure on Unknown Property in JSON Request Body

If there are unknown properties in JSON Request Body which cannot be mapped to Java Object then Jackson ObjectMapper throw UnrecognizedPropertyException. This feature is enabled by default.

Let’s add extra field gender in POST request body which is not there in User request mapping Object. It will throw exception.

spring.jackson.deserialization.FAIL_ON_UNKNOWN_PROPERTIES = true (default)

application.yml
spring: jackson: deserialization: FAIL_ON_UNKNOWN_PROPERTIES: true
Request
curl -X POST \ http://localhost:8080/users \ -H 'cache-control: no-cache' \ -H 'content-type: application/json' \ -d '{ "id": 1, "name": "Ashish", "dateOfBirth": "1986-08-22", "gender": "male" }'
Response Error
JSON parse error: Unrecognized field "gender" (class com.example.api.model.User), not marked as ignorable; nested exception is com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "gender" (class com.example.api.model.User), not marked as ignorable (3 known properties: "dateOfBirth", "id", "name"])

We can disable this feature to allow unknown properties (or extra fields) in our JSON Request Body. Please note that if you are using Spring Boot’s default ObjectMapper then you don’t need to do anything as this feature is disabled by default.

We see that POST request did not fail this time and returned id of created user.

spring.jackson.deserialization.FAIL_ON_UNKNOWN_PROPERTIES = false

application.yml
spring: jackson: deserialization: FAIL_ON_UNKNOWN_PROPERTIES: false
Request
curl -X POST \ http://localhost:8080/users \ -H 'cache-control: no-cache' \ -H 'content-type: application/json' \ -d '{ "id": 1, "name": "Ashish", "dateOfBirth": "1986-08-22", "gender": "male" }'
Response
1


Don’t allow certain property in JSON Request Body

Sometime we don’t want certain properties such as id to be sent in request body because you would be generating that id in backend. In such case you can annotate such properties with @JsonIgnore and enable FAIL_ON_IGNORED_PROPERTIES feature.

This feature throw IgnoredPropertyException if ignored properties are passed in JSON Request Body. This feature is disabled by default.

We can annotate the id property in this way:

package com.example.api.model;

public class User {

  @JsonIgnore
  private Long id;
	
  private String name;
	
  private LocalDate dateOfBirth;
  
  private LocalDateTime lastLogin;

	/* Getters and Setters */	
}

Let’s see the default behavior first where it allow ignored properties:

spring.jackson.deserialization.FAIL_ON_IGNORED_PROPERTIES = false (default)

application.yml
spring: jackson: deserialization: FAIL_ON_IGNORED_PROPERTIES: false
Request
curl -X POST \ http://localhost:8080/users \ -H 'cache-control: no-cache' \ -H 'content-type: application/json' \ -d '{ "id": 1, "name": "Ashish", "dateOfBirth": "1986-08-22" }'
Response Error
1

We see that POST request throw exception when we pass id in JSON Request Body once we enable this feature

spring.jackson.deserialization.FAIL_ON_IGNORED_PROPERTIES = true

application.yml
spring: jackson: deserialization: FAIL_ON_IGNORED_PROPERTIES: true
Request
curl -X POST \ http://localhost:8080/users \ -H 'cache-control: no-cache' \ -H 'content-type: application/json' \ -d '{ "id": 1, "name": "Ashish", "dateOfBirth": "1986-08-22" }'
Response
JSON parse error: Ignored field "id" (class com.example.api.model.User) encountered; mapper configured not to allow this; nested exception is com.fasterxml.jackson.databind.exc.IgnoredPropertyException: Ignored field "id" (class com.example.api.model.User) encountered; mapper configured not to allow this (3 known properties: "dateOfBirth", "name", "lastLogin"])


Pretty Print JSON Response

API JSON responses are not pretty print (formatted) by default.

spring.jackson.serialization.INDENT_OUTPUT = false (default)

application.yml
spring: jackson: serialization: INDENT_OUTPUT: false
Request
curl -X GET http://localhost:8080/users
Response
{"users":[{"id":1,"name":"Adam","dateOfBirth":"1950-01-01"},{"id":2,"name":"Bob","dateOfBirth":"1990-10-30"},{"id":3,"name":"Charlie","dateOfBirth":"1979-07-26"}]}

We can pretty print JSON Response by turning on the INDENT_OUTPUT property . We see that JSON response is formatted after that.

spring.jackson.serialization.INDENT_OUTPUT = true

application.yml
spring: jackson: serialization: INDENT_OUTPUT: true
Request
curl -X GET http://localhost:8080/users
Response
{ "users" : [ { "id" : 1, "name" : "Adam", "dateOfBirth" : "1950-01-01" }, { "id" : 2, "name" : "Bob", "dateOfBirth" : "1990-10-30" }, { "id" : 3, "name" : "Charlie", "dateOfBirth" : "1979-07-26" } ] }


Format Date and DateTime properties in JSON Response

Date and DateTime fields in Java Object, are converted into numeric timestamp by default during JSON serialization.

spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS = true (default)

application.yml
spring: jackson: serialization: WRITE_DATES_AS_TIMESTAMPS: true
Request
curl -X GET http://localhost:8080/users/1
Response
{ "id" : 1, "name" : "Adam", "dateOfBirth" : [ 1950, 1, 1 ], "lastLogin" : [ 2020, 7, 3, 0, 26, 22, 211000000 ] }

We can disable this feature to allow Jackson to convert Date and DateTime fields to human readable String format. Please note that if you are using Spring Boot’s default ObjectMapper then this feature is disabled by default.

spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS = false

application.yml
spring: jackson: serialization: WRITE_DATES_AS_TIMESTAMPS: false
Request
curl -X GET http://localhost:8080/users/1
Response
{ "id" : 1, "name" : "Adam", "dateOfBirth" : "1950-01-01", "lastLogin" : "2020-07-03T00:28:32.394" }

We see that date and time are in human readable format in JSON response after disabling this feature. We can further customize the Date and DateTime field by annotating them with @JsonFormat annotations in our User Object Model.

package com.example.api.model;

public class User {
	
	private Long id;
	
	private String name;
	
	@JsonFormat(pattern="dd MMM yyyy")
	private LocalDate dateOfBirth;
	
	@JsonFormat(pattern="dd MMM yyyy hh:mm:ss")
	private LocalDateTime lastLogin;
	
	@JsonFormat(pattern = "yyyy-MM-dd@HH:mm:ss.SSSXXX", locale = "en_SG", timezone = "Asia/Singapore")
	private ZonedDateTime zonedDateTime;

	/* Getters and Setters */	
}

Output will be something like this after applying @JsonFormat annotations:

application.yml
spring: jackson: serialization: WRITE_DATES_AS_TIMESTAMPS: false
Request
curl -X GET http://localhost:8080/users/1
Response
{ "id" : 1, "name" : "Adam", "dateOfBirth" : "01 Jan 1950", "lastLogin" : "03 Jul 2020 01:03:34", "zonedDateTime" : "2020-07-03@01:03:34.467+08:00" }

Please note that once you apply @JsonFormat annotation on Date and DateTime fields, same format would be used for JSON deserialization. That means you need to pass date or datetime parameters in JSON request body of an HTTP request in same format.

Conclusion

We looked at some of the useful configurations. Here is the full list of Jackson serialization and deserialization properties configurable in Spring Boot Application using application.yml, or application.properties file.

application.yml
spring:
  jackson:
    serialization:
      CLOSE_CLOSEABLE: true/false
      EAGER_SERIALIZER_FETCH: true/false
      FAIL_ON_EMPTY_BEANS: true/false
      FAIL_ON_SELF_REFERENCES: true/false
      FAIL_ON_UNWRAPPED_TYPE_IDENTIFIERS: true/false
      FLUSH_AFTER_WRITE_VALUE: true/false
      INDENT_OUTPUT: true/false
      ORDER_MAP_ENTRIES_BY_KEYS: true/false
      USE_EQUALITY_FOR_OBJECT_ID: true/false
      WRAP_EXCEPTIONS: true/false
      WRAP_ROOT_VALUE: true/false
      WRITE_BIGDECIMAL_AS_PLAIN: true/false
      WRITE_CHAR_ARRAYS_AS_JSON_ARRAYS: true/false
      WRITE_DATES_AS_TIMESTAMPS: true/false
      WRITE_DATES_WITH_ZONE_ID: true/false
      WRITE_DATE_KEYS_AS_TIMESTAMPS: true/false
      WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS: true/false
      WRITE_DURATIONS_AS_TIMESTAMPS: true/false
      WRITE_EMPTY_JSON_ARRAYS: true/false
      WRITE_ENUMS_USING_INDEX: true/false
      WRITE_ENUMS_USING_TO_STRING: true/false
      WRITE_ENUM_KEYS_USING_INDEX: true/false
      WRITE_NULL_MAP_VALUES: true/false
      WRITE_SELF_REFERENCES_AS_NULL: true/false
      WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED: true/false
    deserialization:
      ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT: true/false
      ACCEPT_EMPTY_STRING_AS_NULL_OBJECT: true/false
      ACCEPT_FLOAT_AS_INT: true/false
      ACCEPT_SINGLE_VALUE_AS_ARRAY: true/false
      ADJUST_DATES_TO_CONTEXT_TIME_ZONE: true/false
      EAGER_DESERIALIZER_FETCH: true/false
      FAIL_ON_IGNORED_PROPERTIES: true/false
      FAIL_ON_INVALID_SUBTYPE: true/false
      FAIL_ON_MISSING_CREATOR_PROPERTIES: true/false
      FAIL_ON_MISSING_EXTERNAL_TYPE_ID_PROPERTY: true/false
      FAIL_ON_NULL_CREATOR_PROPERTIES: true/false
      FAIL_ON_NULL_FOR_PRIMITIVES: true/false
      FAIL_ON_NUMBERS_FOR_ENUMS: true/false
      FAIL_ON_READING_DUP_TREE_KEY: true/false
      FAIL_ON_TRAILING_TOKENS: true/false
      FAIL_ON_UNKNOWN_PROPERTIES: true/false
      FAIL_ON_UNRESOLVED_OBJECT_IDS: true/false
      READ_DATE_TIMESTAMPS_AS_NANOSECONDS: true/false
      READ_ENUMS_USING_TO_STRING: true/false
      READ_UNKNOWN_ENUM_VALUES_AS_NULL:true/false
      READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE: true/false
      UNWRAP_ROOT_VALUE: true/false
      UNWRAP_SINGLE_VALUE_ARRAYS: true/false
      USE_BIG_DECIMAL_FOR_FLOATS: true/false
      USE_BIG_INTEGER_FOR_INTS: true/false
      USE_JAVA_ARRAY_FOR_JSON_ARRAY: true/false
      USE_LONG_FOR_INTS: true/false
      WRAP_EXCEPTIONS: true/false

Please note that spring boot configuration support Relaxed Binding that means properties can be in uppercase or lowercase, both are valid.

spring:
  jackson:
    serialization:
      INDENT_OUTPUT: true

is same as

spring:
  jackson:
    serialization:
      indent_output: true

That’s it for now. I’ll keep updating this post with more practical use cases as I come across.

Download the source code for these examples from github/springboot-api