Spring Boot's auto-configuration of ObjectMapper
Spring Boot’s auto-configured ObjectMapper
As you know, Spring Boot configures a lot of things for us. They register lots of Beans on behalf of us. Sometimes, we might think that there is no configuration at all in the first place.
I think many developers would not pay much attention to serializer and deserializer, because Spring Boot’s default configuration works great for most of cases. When we return object (e.g. Person
object) from @RestController
controller, ObjectMapper
serialize the object into Json format behind the scene. ObjectMapper
also deserialize a Json request body into an object.
When we use Spring Boot, our ObjectMapper
is little different from Jackson’s default one, which is created by new ObjectMapper()
. Spring Boot’s auto-configured ObjectMapper
has following differences from new ObjectMapper()
.
Spring Boot’s auto-configured ObjectMapper
SerializationFeature.WRITE_DATES_AS_TIMESTAMPS
is set tofalse
SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS
is set tofalse
DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES
is set tofalse
MapperFeature.DEFAULT_VIEW_INCLUSION
is set tofalse
Let’s look into what these features do in simple test code. If you want to get test code, you can find it on my github. https://github.com/SPICYJO/blog-demo-1
Test Class
@Slf4j
@SpringBootTest
class ObjectMapperTest {
@Autowired
ObjectMapper springObjectMapper; // Spring Boot auto-configured ObjectMapper
ObjectMapper jacksonObjectMapper = new ObjectMapper(); // Jackson default ObjectMapper
@BeforeEach
void setup() {
jacksonObjectMapper = new ObjectMapper();
}
/* ... */
}
Our test code looks like above. ObjectMapper springObjectMapper
is autowired from Spring Boot auto-configured Bean and jacksonObjectMapper
is contructed with new ObjectMapper()
constructor.
WRITE_DATES_AS_TIMESTAMPS and WRITE_DURATIONS_AS_TIMESTAMPS
@Test
void test_WRITE_DATES_AS_TIMESTAMPS_WRITE_DURATIONS_AS_TIMESTAMPS() throws JsonProcessingException {
String springSerialized = springObjectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(
new TimeClass(1, new Date(), Instant.now(), Duration.ofMinutes(10L))
);
log.info(springSerialized);
//{
// "id" : 1,
// "date" : "2022-01-03T12:52:11.802+00:00",
// "instant" : "2022-01-03T12:52:11.802831Z",
// "duration" : "PT10M"
//}
jacksonObjectMapper.registerModule(new JavaTimeModule()); // For serialization of Instant, Duration
String jacksonSerialized = jacksonObjectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(
new TimeClass(1, new Date(), Instant.now(), Duration.ofMinutes(10L))
);
log.info(jacksonSerialized);
//{
// "id" : 1,
// "date" : 1641214331840,
// "instant" : 1641214331.840827200,
// "duration" : 600.000000000
//}
}
You can see that Spring Boot’s auto-configured ObjectMapper returned yyyy-MM-dd'T'HH:mm:ss.SSS
format for Date
and Instant
class while Jackson’s default mapper returned unix time. You can also notice that Duration
class is formatted in ISO8601 duration format.
FAIL_ON_UNKNOWN_PROPERTIES
@AllArgsConstructor
@NoArgsConstructor
@Data
public static class Person {
private Integer id;
private String name;
}
@Test
void test_FAIL_ON_UNKNOWN_PROPERTIES() throws JsonProcessingException {
String jsonString = "{\"id\": 42, \"name\": \"John Doe\", \"salary\": 123456}";
Person springDeserialized = springObjectMapper.readValue(jsonString, Person.class);
log.info(springDeserialized.toString());
// ObjectMapperTest.Person(id=42, name=John Doe)
Person jacksonDeserialized = jacksonObjectMapper.readValue(jsonString, Person.class);
// above statement throws UnrecognizedPropertyException
//com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "salary" (class com.example.demo.ObjectMapperTest$Person), not marked as ignorable (2 known properties: "id", "name"])
// at [Source: (String)"{"id": 42, "name": "John Doe", "salary": 123456}"; line: 1, column: 48] (through reference chain: com.example.demo.ObjectMapperTest$Person["salary"])
log.info(jacksonDeserialized.toString());
}
You can see that Jackson’s default mapper throws exception because there was unknown property salary
in the string.
DEFAULT_VIEW_INCLUSION
public static class Views {
public static class Public {
}
public static class Internal {
}
}
@AllArgsConstructor
@NoArgsConstructor
@Data
public static class Person2 {
@JsonView({Views.Internal.class})
private Integer id;
@JsonView({Views.Public.class, Views.Internal.class})
private String name;
private String middleName;
}
@Test
void test_DEFAULT_VIEW_INCLUSION() throws JsonProcessingException, IOException {
Person2 p = new Person2(42, "John Doe", "Middle");
String springSerialized = springObjectMapper.writerWithView(Views.Public.class).writeValueAsString(p);
String jacksonSerialized = jacksonObjectMapper.writerWithView(Views.Public.class).writeValueAsString(p);
log.info(springSerialized); // {"name":"John Doe"}
log.info(jacksonSerialized); // {"name":"John Doe","middleName":"Middle"}
Person2 sp = springObjectMapper.readerWithView(Views.Public.class).readValue("{\"name\":\"John Doe\",\"middleName\":\"Middle\"}", Person2.class);
Person2 jp = jacksonObjectMapper.readerWithView(Views.Public.class).readValue("{\"name\":\"John Doe\",\"middleName\":\"Middle\"}", Person2.class);
log.info(sp.toString()); // ObjectMapperTest.Person2(id=null, name=John Doe, middleName=null)
log.info(jp.toString()); // ObjectMapperTest.Person2(id=null, name=John Doe, middleName=Middle)
}
You can see that middleName
property which has no @JsonView
annotation is excluded in Spring Boot’s auto-configured mapper becuase MapperFeature.DEFAULT_VIEW_INCLUSION
is set to false
.
Wrap up
In this article, we have seen configuration difference between two ObjectMapper
from Spring Boot auto-configuration and Jackson default constructor. If you find out more differece, please feel free to leave a comment below. Have a good day!