In this new entry of Refactorizando, we will see an introduction to serialization and deserialization with Jackson, which is very important for serializing and deserializing attributes.
Jackson makes our work much easier when we work with an API that receives JSON and we want to convert it into internal objects of our application.
Jackson is a powerful Java library that provides functionalities for both serialization and deserialization of Java objects to and from JSON format. It simplifies the process of working with APIs that deal with JSON data, allowing seamless conversion between JSON and internal application objects.
Below, we show examples for serialization and deserialization using Jackson annotations and lombok to reduce boilerplate.
Annotations for Serialization with Jackson
@JsonGetter Annotation
The @JsonGetter annotation is very useful for marking a property of a class as a getter. It works similarly to @JsonProperty, with the only difference that it marks only the attribute as a getter. Let’s see an example:
@Slf4j public class JacksonCar { public static void main(String args[]){ ObjectMapper mapper = new ObjectMapper(); try { Car car = new Car("Ford", "Mustang"); String jsonCar = mapper .writerWithDefaultPrettyPrinter() .writeValueAsString(car); log.debug(jsonCar); } catch (IOException e) { log.error("Error parsing object car"); } } } public class Car { private String brand; private String model; public Car(String brand, String model){ this.brand = brand; this.model = model; } @JsonGetter("brandName") public String getBrand(){ return brand; } public String getModel(){ return model; } }
Output: { "brandName" : "Ford", "model" : "Mustang" }
@JsonAnyGetter Annotation
The @JsonAnyGetter annotation allows us to mark a Map field so that it is serialized in a key-value format similar to other JSON properties. Let’s see an example:
public class Car { private Map<String, String> properties; public Car(){ properties = new HashMap<>(); } @JsonAnyGetter public Map<String, String> getProperties(){ return properties; } public void add(String brand, String model){ properties.put(brand, model); } } @Slf4j public class JacksonCar { public static void main(String args[]){ ObjectMapper mapper = new ObjectMapper(); try{ Car car = new Car(); car.add("Ford", "Mustang"); car.add("Reanult", "Megane"); String carToString = mapper .writerWithDefaultPrettyPrinter() .writeValueAsString(car); log.debug(carToString); } catch (IOException e) { log.error("Error parsing object car"); } } }
Output: { "Ford" : "Mustang", "Reanult" : "Megane" }
@JsonPropertyOrder Annotation
The @JsonPropertyOrder annotation allows us to set an order for the fields of a class when we deserialize an object.
If we use the previous Car class and specify that the “model” should appear before “brand,” it would be like this:
@Slf4j public class JacksonCar { public static void main(String args[]){ ObjectMapper mapper = new ObjectMapper(); try { Car car = new Car("Ford", "Mustang"); String jsonCar = mapper .writerWithDefaultPrettyPrinter() .writeValueAsString(car); log.debug(jsonCar); } catch (IOException e) { log.error("Error parsing object car"); } } } @JsonPropertyOrder({ "model", "brand" }) @Getter @Setter @AllArgsConstructor public class Car { private String brand; private String model; }
{ "model" : "Mustang", "brand" : "Ford" }
@JsonRawValue Annotation
The @JsonRawValue annotation allows us to serialize a text without escaping it, meaning that the text will be serialized exactly as it is. Let’s see an example:
@Slf4j public class JacksonCar { public static void main(String args[]){ ObjectMapper mapper = new ObjectMapper(); try { Car car = new Car("Ford", "Mustang", "{\"attributes\":\"color:blue\"}"); String jsonCar = mapper .writerWithDefaultPrettyPrinter() .writeValueAsString(car); log.debug(jsonCar); } catch (IOException e) { log.error("Error parsing object car"); } } } @Getter @Setter @AllArgsConstructor public class Car { private String brand; private String model; @JsonRawValue private String attributes; }
{ "model" : "Mustang", "brand" : "Ford", "attributes": "{"attributes":"color:blue"}" }
This annotation is very useful when we have incoming JSON and want to maintain the format.
@JsonSerialize Annotation
The @JsonSerialize annotation is very useful when we need to create a custom serializer. For example, if we have a date that we want to be in a specific format, let’s see it with an example:
@Slf4j public class JacksonCar { public static void main(String args[]){ ObjectMapper mapper = new ObjectMapper(); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("d/MM/yyyy"); try { Car car = new Car("Ford", "Mustang", LocalDate.parse("05/10/1983", formatter)); String jsonCar = mapper .writerWithDefaultPrettyPrinter() .writeValueAsString(car); log.debug(jsonCar); } catch (IOException e) { log.error("Error parsing object car"); } } } @Getter @Setter @AllArgsConstructor public class Car { private String brand; private String model; private LocalDate enrollmentDay; } class LocalDateSerializer extends StdSerializer<LocalDate> { DateTimeFormatter formatter = DateTimeFormatter.ofPattern("d/MM/yyyy"); public LocalDateSerializer(Class<LocalDate> t) { super(t); } @Override public void serialize(LocalDate value, JsonGenerator generator, SerializerProvider arg2) throws IOException { generator.writeString(formatter.format(value)); } }
@JsonRootName Annotation
This annotation is used to wrap a field. That is, instead of directly mapping the fields, we pass the name of the entity we want to wrap. It is better to see it with an example:
The @JsonRootName annotation will work only when SerializationFeature.WRAP_ROOT_VALUE is active.
@Slf4j public class JacksonCar { public static void main(String args[]){ ObjectMapper mapper = new ObjectMapper(); try { Car car = new Car("Ford", "Mustang", "{\"attributes\":\"color:blue\"}"); mapper.enable(SerializationFeature.WRAP_ROOT_VALUE); String jsonCar = mapper .writerWithDefaultPrettyPrinter() .writeValueAsString(car); log.debug(jsonCar); } catch (IOException e) { log.error("Error parsing object car"); } } } @JsonRootName(value = "car") @Getter @Setter @AllArgsConstructor public class Car { private String brand; private String model; }
Output: { "car" : { "brand" : "Ford", "model" : "Mustang" } }
Annotations for Deserialization with Jackson
Below, we show the annotations for deserialization with Jackson.
@JsonCreator Annotation
The @JsonCreator annotation is used to customize the constructor or factory used during deserialization. It’s worth noting that we can achieve the same with @JsonProperty.
This annotation can be useful when deserializing a JSON that is not exactly the same as the attribute we want to parse. Let’s see an example:
@Slf4j public class JacksonCar { public static void main(String args[]) { String jsonCar = "{\"marca\":\"Ford\",\"modelo\":\"Mustang\"}"; ObjectMapper mapper = new ObjectMapper(); try { Car car = mapper.readerFor(Car.class).readValue(jsonCar); log.debug(car.getBrand() + ", " + car.getModel()); } catch (IOException e) { log.error("Error parsing object car"); } } } @Getter @Setter @AllArgsConstructor public class Car { public String brand; public String model; @JsonCreator public Car(@JsonProperty("marca") String brand, @JsonProperty("modelo") String model){ this.brand = brand; this.model = model; } }
Output: 1, Mark
@JsonInject Annotation
The @JacksonInject annotation added to a field indicates that its value will be obtained from injection instead of from the JSON information.
@Slf4j public class JacksonCar { public static void main(String args[]) throws ParseException{ String jsonCar = "{\"brand\":\"Ford\"}"; InjectableValues valuesToInject = new InjectableValues.Std() .addValue(String.class, "Mustang"); ObjectMapper mapper = new ObjectMapper(); try { Car car = mapper .reader(valuesToInject) .forType(Car.class) .readValue(jsonCar); log.debug("The model has been injected " + car.getBrand() +", " + car.getModel()); } catch (IOException e) { log.error("Error parsing object car"); } } } @Getter @Setter @AllArgsConstructor class Car { public String brand; @JacksonInject public String model; }
@JsonAnySetter Annotation
With the @JsonAnySetter annotation, we can use a Map as properties, and during deserialization, these properties will be added to the map. It’s easier to understand with an example:
@Slf4j public class JacksonCar { public static void main(String args[]){ ObjectMapper mapper = new ObjectMapper(); String jsonCar = "{\"Brand\" : \"Ford\",\"Model\" : \"Mustang\"}"; try { Car car = mapper.readerFor(Car.class).readValue(jsonCar); log.debug(car.getProperties().get("Brand")); log.debug(car.getProperties().get("Model")); } catch (IOException e) { log.error("Error parsing object car"); } } } @Getter @Setter @AllArgsConstructor class Car { private Map<String, String> properties; public Car(){ properties = new HashMap<>(); } public Map<String, String> getProperties(){ return properties; } @JsonAnySetter public void add(String property, String value){ properties.put(property, value); } }
@JsonSetter Annotation
The @JsonSetter annotation provides an alternative way to the @JsonProperty annotation. This annotation will be very helpful when we read a JSON that does not exactly match the class to which we want to map it.
Similar to what we did with @JsonGetter, which marked a getter, with @JsonSetter, we mark a setter method.
@Slf4j public class JacksonCar { public static void main(String args[]){ ObjectMapper mapper = new ObjectMapper(); String jsonCar = "{\"brand\":"Ford",\"model\":\"Mustang\"}"; try { Car car = mapper.readerFor(Car.class).readValue(jsonCar); log.debug(car.getModel()); } catch (IOException e) { log.error("Error getting mapper car"); } } } @Getter @Setter @AllArgsConstructor class Car { public String brand; public String model; @JsonSetter("name") public void setModel(String model) { this.model = model; } }
.
@JsonDeserialize Annotation
With the @JsonDeserialize annotation, just like with @JsonSerialize, we can perform custom deserialization of a field of a class. Let’s see an example:
@Slf4j public class JacksonCar { public static void main(String args[]) throws ParseException { ObjectMapper mapper = new ObjectMapper(); String jsonCar = "{\"brand\":\"Ford\",\"enrollmentDay\":\"01-01-2021\"}"; try { Car car = mapper .readerFor(Car.class) .readValue(jsonCar); log.debug(car.getEnrollmentDay().toString()); } catch (IOException e) { log.error("Error getting mapper car"); } } } @Getter @Setter public class Car { public String brand; @JsonDeserialize(using = LocalDateDeserializer.class) public LocalDate enrollmentDay; } class LocalDateDeserializer extends StdDeserializer<LocalDate> { DateTimeFormatter formatter = DateTimeFormatter.ofPattern("d/MM/yyyy"); protected LocalDateDeserializer(Class<?> vc) { super(vc); } @Override public LocalDate deserialize(JsonParser parser, DeserializationContext context) throws IOException{ String date = parser.getText(); return LocalDate.parse(date, formatter); } }
@JsonAlias Annotation
The @JsonAlias annotation can be used during deserialization to rename an attribute of a class. Let’s see it with an example:
@Slf4j public class JacksonCar { public static void main(String args[]){ ObjectMapper mapper = new ObjectMapper(); try { Car car = new Car("Ford", "Mustang"); String jsonWriter = mapper .writerWithDefaultPrettyPrinter() .writeValueAsString(car); } catch (IOException e) { e.printStackTrace(); } } @Getter @Setter @AllArgsConstructor public class Car { @JsonProperty("marca") private String brand; @JsonProperty("modelo") private String model; }
Output: { "marca" : "Ford", "modelo" : "Mustang" }
Conclusion
In this article, we have seen an introduction to Serialization and Deserialization with Jackson, which will be very useful for converting or transforming JSON information into our objects.