Jackson and JSON in Java, finally learn with a coding-first approach:
>> Download the eBookMocking is an essential part of unit testing, and the Mockito library makes it easy to write clean and intuitive unit tests for your Java code.
Get started with mocking and improve your application tests using our Mockito guide:
Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.
Get started with understanding multi-threaded applications with our Java Concurrency guide:
Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:
Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.
But these can also be overused and fall into some common pitfalls.
To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:
Get started with Spring and Spring Boot, through the Learn Spring course:
>> LEARN SPRINGExplore Spring Boot 3 and Spring 6 in-depth through building a full REST API with the framework:
Yes, Spring Security can be complex, from the more advanced functionality within the Core to the deep OAuth support in the framework.
I built the security material as two full courses - Core and OAuth, to get practical with these more complex scenarios. We explore when and how to use each feature and code through it on the backing project.
You can explore the course here:
Spring Data JPA is a great way to handle the complexity of JPA with the powerful simplicity of Spring Boot.
Get started with Spring Data JPA through the guided reference course:
Refactor Java code safely β and automatically β with OpenRewrite.
Refactoring big codebases by hand is slow, risky, and easy to put off. Thatβs where OpenRewrite comes in. The open-source framework for large-scale, automated code transformations helps teams modernize safely and consistently.
Each month, the creators and maintainers of OpenRewrite at Moderne run live, hands-on training sessions β one for newcomers and one for experienced users. Youβll see how recipes work, how to apply them across projects, and how to modernize code with confidence.
Join the next session, bring your questions, and learn how to automate the kind of work that usually eats your sprint time.
1. Overview
In this quick tutorial, weβll learn how to control the way Java Enums are serialized and deserialized with Jackson 2.
To dig a little deeper and learn other cool things we can do with Jackson 2, head on over to the main Jackson tutorial.
2. Controlling the Enum Representation
Letβs define the following Enum:
public enum Distance {
KILOMETER("km", 1000),
MILE("miles", 1609.34),
METER("meters", 1),
INCH("inches", 0.0254),
CENTIMETER("cm", 0.01),
MILLIMETER("mm", 0.001);
private String unit;
private final double meters;
private Distance(String unit, double meters) {
this.unit = unit;
this.meters = meters;
}
// standard getters and setters
}
3. Serializing Enums to JSON
3.1. Default Enum Representation
By default, Jackson will represent Java Enums as a simple String. For instance:
new ObjectMapper().writeValueAsString(Distance.MILE);
Will result in:
"MILE"
However, when marshaling this Enum to a JSON Object, we would like to get something like:
{"unit":"miles","meters":1609.34}
3.2. Enum as a JSON Object
Starting with Jackson 2.1.2, thereβs now a configuration option that can handle this kind of representation. This can be done via the @JsonFormat annotation at the class level:
@JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum Distance { ... }
This will lead to the desired result when serializing this enum for Distance.MILE:
{"unit":"miles","meters":1609.34}
3.3. Enums and @JsonValue
Another simple way of controlling the marshaling output for an enum is using the @JsonValue annotation on a getter:
public enum Distance {
...
@JsonValue
public String getMeters() {
return meters;
}
}
What weβre expressing here is that getMeters() is the actual representation of this enum. So the result of serializing will be:
1609.34
3.4. Custom Serializer for Enum
If weβre using a version of Jackson earlier than 2.1.2, or if even more customization is required for the enum, we can use a custom Jackson serializer. First, weβll need to define it:
public class DistanceSerializer extends StdSerializer {
public DistanceSerializer() {
super(Distance.class);
}
public DistanceSerializer(Class t) {
super(t);
}
public void serialize(
Distance distance, JsonGenerator generator, SerializerProvider provider)
throws IOException, JsonProcessingException {
generator.writeStartObject();
generator.writeFieldName("name");
generator.writeString(distance.name());
generator.writeFieldName("unit");
generator.writeString(distance.getUnit());
generator.writeFieldName("meters");
generator.writeNumber(distance.getMeters());
generator.writeEndObject();
}
}
Then we can apply the serializer to the class thatβll be serialized:
@JsonSerialize(using = DistanceSerializer.class)
public enum TypeEnum { ... }
This results in:
{"name":"MILE","unit":"miles","meters":1609.34}
4. Deserializing JSON to Enum
First, letβs define a City class that has a Distance member:
public class City {
private Distance distance;
...
}
Then weβll discuss the different ways of deserializing a JSON string to an Enum.
4.1. Default Behavior
By default, Jackson will use the Enum name to deserialize from JSON.
For example, itβll deserialize the JSON:
{"distance":"KILOMETER"}
To a Distance.KILOMETER object:
City city = new ObjectMapper().readValue(json, City.class);
assertEquals(Distance.KILOMETER, city.getDistance());
If we want Jackson to case-insensitively deserialize from JSON by the Enum name, we need to customize the ObjectMapper to enable the ACCEPT_CASE_INSENSITIVE_ENUMS feature.
Letβs say we have another JSON:
{"distance":"KiLoMeTeR"}
Now, letβs do a case-insensitive deserialization:
ObjectMapper objectMapper = JsonMapper.builder()
.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS)
.build();
City city = objectMapper.readValue(json, City.class);
assertEquals(Distance.KILOMETER, city.getDistance());
As the test above shows, we enable the ACCEPT_CASE_INSENSITIVE_ENUMS feature with the JsonMapper builder.
4.2. Using @JsonValue
Weβve learned how to use @JsonValue to serialize Enums. We can use the same annotation for deserialization as well. This is possible because Enum values are constants.
First, letβs use @JsonValue with one of the getter methods, getMeters():
public enum Distance {
...
@JsonValue
public double getMeters() {
return meters;
}
}
The return value of the getMeters() method represents the Enum objects. Therefore, when deserializing the sample JSON:
{"distance":"0.0254"}
Jackson will look for the Enum object that has a getMeters() return value of 0.0254. In this case, the object is Distance.INCH:
assertEquals(Distance.INCH, city.getDistance());
4.3. Using @JsonProperty
The @JsonProperty annotation is used on enumeration instances:
public enum Distance {
@JsonProperty("distance-in-km")
KILOMETER("km", 1000),
@JsonProperty("distance-in-miles")
MILE("miles", 1609.34);
...
}
By using this annotation, weβre simply telling Jackson to map the value of the @JsonProperty to the object annotated with this value.
As a result of the above declaration, the example JSON string:
{"distance": "distance-in-km"}
Will be mapped to the Distance.KILOMETER object:
assertEquals(Distance.KILOMETER, city.getDistance());
4.4. Using @JsonCreator
Jackson invokes methods annotated with @JsonCreator to get an instance of the enclosing class.
Consider the JSON representation:
{
"distance": {
"unit":"miles",
"meters":1609.34
}
}
Then weβll define the forValues() factory method with the @JsonCreator annotation:
public enum Distance {
@JsonCreator
public static Distance forValues(@JsonProperty("unit") String unit,
@JsonProperty("meters") double meters) {
for (Distance distance : Distance.values()) {
if (
distance.unit.equals(unit) && Double.compare(distance.meters, meters) == 0) {
return distance;
}
}
return null;
}
...
}
Note the use of the @JsonProperty annotation to bind the JSON fields with the method arguments.
Then, when we deserialize the JSON sample, weβll get the result:
assertEquals(Distance.MILE, city.getDistance());
4.5. Using a Custom Deserializer
We can use a custom deserializer if none of the described techniques are available. For example, we might not have access to the Enum source code, or we might be using an older Jackson version that doesnβt support one or more of the annotations covered so far.
According to our custom deserialization article, in order to deserialize the JSON provided in the previous section, weβll start by creating the deserialization class:
public class CustomEnumDeserializer extends StdDeserializer<Distance> {
@Override
public Distance deserialize(JsonParser jsonParser, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
JsonNode node = jsonParser.getCodec().readTree(jsonParser);
String unit = node.get("unit").asText();
double meters = node.get("meters").asDouble();
for (Distance distance : Distance.values()) {
if (distance.getUnit().equals(unit) && Double.compare(
distance.getMeters(), meters) == 0) {
return distance;
}
}
return null;
}
}
Then weβll use the @JsonDeserialize annotation on the Enum to specify our custom deserializer:
@JsonDeserialize(using = CustomEnumDeserializer.class)
public enum Distance {
...
}
And our result is:
assertEquals(Distance.MILE, city.getDistance());
5. Conclusion
This article illustrated how to gain better control over the serialization and deserialization processes and formats of Java Enums.
