AdBlock Detected

It looks like you're using an ad-blocker!

Our team work realy hard to produce quality content on this website and we noticed you have ad-blocking enabled. Advertisements and advertising enable us to continue working and provide high-quality content.

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.

Leave a Reply

Your email address will not be published. Required fields are marked *