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”