Serialización y Deserialización con Jackson

serialización y deserialización con jackson

serialización y deserialización con jackson


En esta nueva entrada de refactorizando vamos a ver una introducción a la serialización deserialización con Jackson, muy importantes para la serialización y deserialización de atributos.

Jackson nos facilita mucho el trabajo cuando trabajamos con un API en la que vamos a recibir JSON y los queremos convertir a objetos internos de nuestra aplicación.

A continuación mostramos ejemplos para la serialización y deserialización mediante Jackson a través de sus anotaciones.

En los siguientes ejemplos se ha usado lombok por simplicidad y eliminar algo de boilerplate en los ejemplos.

Anotaciones para la serialización con Jackson

Anotación @JsonGetter

La anotación @JsonGetter va a ser muy útil para para marcar una propiedad de una clase para que sea de tipo getter. Funciona de manera similar a @JsonProperty, con la única diferencia que solo se marca como get ese atributo. Vamos a verlo con un ejemplo:

@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"
} 

Anotación @JsonAnyGetter

La anotación @JsonAnyGetter anotación nos va a permitir marcada en un campo Map, que ese atributo sea serializado de manera similar a otras propiedades JSON, de manera key value. Veamos un ejemplo:

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"
}

Anotación @JsonPropertyOrder

Esta anotación nos va a permitir establecer un orden en los campos de una clase, cuando realizamos la deserialización de un objeto.

Si partimos de la clase anterior y especificamos que primero aparezca el model y después el bran sería:

@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"
} 

Anotación @JsonRawValue

La anotación @JsonRawValue, nos va a permitir serializar un texto sin escaparlo, es decir, serializar un texto exactamente tal y como es. Veamos un ejemplo:

@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"}"
} 

Esta anotación viene muy bien cuando se tienen json de entrada y se quiere mantener el formato.

Anotación @JsonSerialize

La anotación @JsonSerialize es muy útil cuando necesitamos crear un serializador custom. Por ejemplo, tenemos una fecha que queremos un formato especifíco, vamos a verlo con un ejemplo:

@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));
   }
}

Anotación @JsonRootName

Esta anotación es usada para hacer un envoltorio de un campo. Es decir, en lugar de mapear los campos directamente, lo que vamos a hacer es pasar el nombre de la entidad que vamos a envolver. Es mejor verlo con un ejemplo

La anotación JsonRootName funcionará siempre y cuando wrapper este activo, SerializationFeature.WRAP_ROOT_VALUE.

@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" 
   } 
}

Anotaciones para la Deserialización con Jackson

A continuación mostramos las anotaciones para la deserialización con Jackson.

@JsonCreator

Esta anotación, @JsonCreator, es usada para ajustar el constructor o la factoría usada durante la deserialización. Hay que tener en cuenta que con @JsonProperty podemos obtener lo mismo.

Puede sernos útil al deserializar un JSON que no es exactamente igual al atributo al que vamos a parsear. Vamos a verlo con un ejemplo:

@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 

Anotación @JacksonInject

Esta anotación añadida en un atributo nos indica que obtendrá su valor desde la injección realizada en lugar de la información del JSON.

@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;  
}

Anotación @JsonAnySetter

Con la anotación @JsonAnySetter vamos a poder un Map como propiedades y en la deserialización se añadirán estas propiedades al map. Mejor que explicarlo es verlo con un ejemplo:

@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); 
   }   
}

Anotación @JsonSetter

La anotación @JsonSetter nos ofrece una manera alternativa a la anotación @JsonProperty. Esta anotación nos será de gran ayuda cuando leemos un Json pero no coincide exactamente con la clase a la que se mapea.

Al igual que ocurría con @JsonGetter, que servía para marcar un getter, con @JsonSetter, marcaremos un campo getter.

@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; 
   }  
}

.

Anotación @JsonDeserialize

Con la anotación @JsonDeserialize, al igual que hicimos con la anotación @JsonSerialize, vamos a poder realizar la deserialización de un campo de una clase de una manera custom. Mejor ver un ejemplo:

@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);

   }
}

Anotación @JsonAlias

La anotación @JsonAlias puede ser utilizada durante la deserialización para renombrar un atributo de una clase. Vamos a verlo con un ejemplo:

@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"
} 

Conclusión

En este artículo hemos visto una introducción a la Serialización y Deserialización con Jackson, que nos será de gran utilidad para convertir o transformar información JSON a nuestros objetos.

Si tienes alguna duda no dudes en contactar a través de un comentario un e-mail, o en nuestras redes sociales.

No te olvides de seguirnos en nuestras redes sociales Facebook o Twitter para estar actualizado.

Si necesitas más información puedes escribirnos un comentario o un correo electrónico a refactorizando.web@gmail.com y te ayudaremos encantados!


1 pensamiento sobre “Serialización y Deserialización con Jackson

Deja una respuesta

Tu dirección de correo electrónico no será publicada.