Java y JSON – Serialización Jackson con ObjectMapper
Un conjunto completo de ejemplos para serialización y deserialización con Java y JSON para los casos de uso más comunes.
Esta guía contiene ejemplos que te muestran cómo serializar y deserializar de Java a JSON. Cubre los conceptos básicos y los casos de uso más comunes: Strings, Objects, Dates, Optionals, Lists, Maps, etc.
Los bloques de código son simplemente casos de prueba para que puedas verificar la salida y jugar con los ejemplos. Ten en cuenta que, en la vida real, no deberías crear el ObjectMapper cada vez que necesites analizar o generar JSON. Es mucho más eficiente reutilizarlo una vez que lo hayas configurado. Esto es, por ejemplo, lo que haces en Spring con inyección de dependencias.
Repositorio GitHub: Todo el código de esta publicación está disponible en GitHub: Jackson Serialization Examples. Si te resulta útil, ¡por favor dale una estrella!
Serialización JSON con Java
Serializar Strings simples
@Test
public void serializeSimpleString() throws JsonProcessingException {
var mapper = new ObjectMapper();
var personName = "Juan Garcia";
var json = mapper.writeValueAsString(personName);
log.info("Serializing a plain String: {}", json);
assertThat(json).isEqualTo("\"Juan Garcia\"");
}
Es un caso muy simple, pero a veces confuso. Un String simple no se mapea mágicamente a un objeto JSON (es decir, entre llaves y con un nombre de campo). Cuando envías esto a través de HTTP, Kafka o AMQP, solo por mencionar algunos ejemplos, puede suceder que el consumidor espere un objeto, por lo que falla en la deserialización. Es un error común al desarrollar Spring Controllers que devuelven un String. En ese caso, puedes crear objetos simples cuya única intención es envolver un String, o puedes codificar tus propios serializadores o deserializadores.
Serializar Strings como objetos JSON
@Test
public void serializeStringAsObject() throws JsonProcessingException {
var mapper = new ObjectMapper();
var stringWrapperSerializer = new StdSerializer<String>(String.class) {
@Override
public void serialize(String s,
JsonGenerator jsonGenerator,
SerializerProvider serializerProvider)
throws IOException {
jsonGenerator.writeStartObject();
jsonGenerator.writeStringField("string", s);
jsonGenerator.writeEndObject();
}
};
mapper.registerModule(new SimpleModule()
.addSerializer(stringWrapperSerializer));
var personName = "Juan Garcia";
var json = mapper.writeValueAsString(personName);
log.info("Using a custom serializer (in this case for a String): {}", json);
assertThat(json).isEqualTo(
"{\"string\":\"Juan Garcia\"}"
);
}
Como se muestra arriba, puedes crear tu propio Serializer para la clase String y configurarlo para crear un objeto JSON básico que envuelva el valor del string. En este caso, el ejemplo usa un campo con una clave llamada string. Así es como funciona:
- Usas la implementación predeterminada
StdSerializery anulas el métodoserialize. - Luego, usas otra implementación predeterminada, en este caso para un módulo:
SimpleModule, y simplemente le agregas el nuevo serializador. - Solo necesitas registrar tu módulo en el
ObjectMappery todos los objetos String serializados por ese mapper se convertirán a objetos JSON.
Recuerda que debes configurar tus objetos ObjectMapper con anticipación y luego reutilizarlos en tu aplicación. Como puedes imaginar, puedes tener diferentes instancias, cada una con su propia configuración.
Como se muestra arriba, puedes agregar comportamiento personalizado registrando módulos en tu ObjectMapper. La mayoría de los ejemplos en esta guía usan esta característica para proporcionar configuración personalizada.
Serializar una lista de objetos String
@Test
public void serializeListOfString() throws JsonProcessingException {
var mapper = new ObjectMapper();
var personNames = List.of("Juan Garcia", "Manuel Perez");
var json = mapper.writeValueAsString(personNames);
log.info("A simple list of String objects looks like this: {}", json);
assertThat(json).isEqualTo(
"[\"Juan Garcia\",\"Manuel Perez\"]"
);
}
Una lista de objetos String se serializa por defecto como un array JSON simple. Podrías usar un wrapper personalizado como el del ejemplo anterior para obtener un array de objetos JSON en su lugar. Como alternativa, también puedes crear tu propia clase con un solo campo para actuar como wrapper (ver el método serializeListOfPersonName) en “Serializar un String con una clase wrapper”.
Serializar un Map de key-values <String,String>
@Test
public void serializeMapOfString() throws JsonProcessingException {
var mapper = new ObjectMapper();
var personNames = new TreeMap<String, String>();
personNames.put("name1", "Juan Garcia");
personNames.put("name2", "Manuel Perez");
var json = mapper.writeValueAsString(personNames);
log.info("A simple map of <String, String>: {}", json);
assertThat(json).isEqualTo(
"{\"name1\":\"Juan Garcia\",\"name2\":\"Manuel Perez\"}"
);
}
Jackson serializa un Map como un objeto JSON cuyas claves son la representación toString() de las claves (en este caso es el valor mismo). Los valores del Map se serializan usando los serializadores predeterminados a menos que los anules. Eso significa que también puedes establecer Maps como valores si quieres objetos JSON anidados, o puedes usar objetos Java que se serialicen con la estrategia por campo como de costumbre.
Serializar un String con una clase Wrapper
public class PersonName {
private final String name;
public PersonName(String name) {
this.name = name;
}
// Alternativamente, puedes usar un campo público para no necesitar un getter
public String getName() {
return name;
}
}
@Test
public void serializeListOfPersonName() throws JsonProcessingException {
var mapper = new ObjectMapper();
var personNames = List.of(
new PersonName("Juan Garcia"),
new PersonName("Manuel Perez")
);
var json = mapper.writeValueAsString(personNames);
log.info("A list of simple PersonName objects converted to JSON: {}", json);
assertThat(json).isEqualTo(
"[{\"name\":\"Juan Garcia\"},{\"name\":\"Manuel Perez\"}]"
);
}
Este es el primer ejemplo serializando un objeto Java. El serializador predeterminado toma todos los campos públicos y todos los campos accesibles a través de getters. Puedes alterar este comportamiento con anotaciones o serializadores personalizados. En este ejemplo, PersonName se usa como wrapper para un String simple. Una lista de objetos Java se serializa a una lista de objetos JSON que contienen los campos y sus valores. Ten en cuenta que no hay referencia en JSON al nombre de la clase, solo a sus campos.
Serializar objetos con LocalDate (predeterminado)
public class Person {
private final String name;
private final LocalDate birthdate;
public Person(String name, LocalDate birthdate) {
this.name = name;
this.birthdate = birthdate;
}
public String getName() {
return name;
}
public LocalDate getBirthdate() {
return birthdate;
}
}
@Test
public void serializeListOfPerson() throws JsonProcessingException {
var mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
var personNames = List.of(
new Person("Juan Garcia",
LocalDate.of(1980, 9, 15)),
new Person("Manuel Perez",
LocalDate.of(1987, 7, 23))
);
var json = mapper.writeValueAsString(personNames);
log.info("A list of simple Person objects converted to JSON: {}", json);
// Por defecto, Jackson serializa LocalDate y LocalDateTime exportando
// todos los campos del objeto como con cualquier otro objeto. Necesitas usar el
// JavaTimeModule, aunque el formateador predeterminado es un poco extraño (array).
assertThat(json).isEqualTo(
"[{\"name\":\"Juan Garcia\",\"birthdate\":[1980,9,15]}," +
"{\"name\":\"Manuel Perez\",\"birthdate\":[1987,7,23]}]"
);
}
Este ejemplo usa una clase Java más realista, en este caso usando un campo LocalDate (introducido en Java 8). Para serializar esos objetos de manera significativa, necesitas registrar el JavaTimeModule, contenido en la biblioteca de dependencias jackson-datatype-jsr310. Serializa los campos como un array por defecto. También podrías evitar incluir esa dependencia extra si usas tus serializadores y deserializadores personalizados, pero eso no tiene mucho sentido cuando hay extensiones disponibles.
Serializar objetos con LocalDate en formato ISO
@Test
public void serializeListOfPersonFormatted() throws JsonProcessingException {
var mapper = new ObjectMapper();
// Puedes usar un módulo personalizado para cambiar el formateador
mapper.registerModule(new SimpleModule().addSerializer(
new LocalDateSerializer(DateTimeFormatter.ISO_LOCAL_DATE)));
var personNames = List.of(
new Person("Juan Garcia",
LocalDate.of(1980, 9, 15)),
new Person("Manuel Perez",
LocalDate.of(1987, 7, 23))
);
var json = mapper.writeValueAsString(personNames);
log.info("A list of simple Person objects converted to JSON: {}", json);
// En este caso obtienes el formato ISO YYYY-MM-DD
assertThat(json).isEqualTo(
"[{\"name\":\"Juan Garcia\",\"birthdate\":\"1980-09-15\"}," +
"{\"name\":\"Manuel Perez\",\"birthdate\":\"1987-07-23\"}]"
);
}
Configurar el formato del LocalDate analizado es posible de múltiples maneras. En este caso, puedes usar tu propio módulo en lugar del JavaTimeModule, y agregar el LocalDateSerializer manualmente. De esta manera puedes establecer el formato a, por ejemplo, un estándar ISO. El resultado es una fecha en formato YYYY-MM-DD.
Deserialización JSON con Java
Deserializar lista de Strings
@Test
public void deserializeListOfString() throws IOException {
var mapper = new ObjectMapper();
var json = "[\"Juan Garcia\",\"Manuel Perez\"]";
var list = mapper.readValue(json, List.class);
log.info("Deserializing a list of plain Strings: {}", list);
assertThat(list).containsExactly("Juan Garcia", "Manuel Perez");
}
Puedes analizar un array JSON de Strings a una lista de manera muy directa. Ten en cuenta que en el ejemplo la lista no está tipada, consulta el siguiente ejemplo si quieres usar tipos explícitos.
Deserializar Strings envueltos como una lista de objetos String
@Test
public void deserializeToListOfStringUsingCustomModule() throws IOException {
var mapper = new ObjectMapper();
var deserializer = new StdDeserializer<String>(String.class) {
@Override
public String deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException {
return ((TextNode) p.getCodec().readTree(p).get("string")).textValue();
}
};
mapper.registerModule(
new SimpleModule().addDeserializer(String.class, deserializer));
var json = "[{\"string\":\"Juan Garcia\"},{\"string\":\"Manuel Perez\"}]";
// Puedes usar esta opción o puedes deserializar a String[]
var stringCollectionType = mapper.getTypeFactory()
.constructCollectionType(List.class, String.class);
List<String> values = mapper.readValue(json, stringCollectionType);
log.info("Using a custom deserializer to extract field values: {}", values);
assertThat(values).containsExactly("Juan Garcia", "Manuel Perez");
}
Uno de los ejemplos de serialización en esta guía mostró cómo envolver valores String en objetos JSON sobre la marcha. Esta es la implementación del deserializador para ese caso específico: puedes implementar tu propio deserializador y usar el parser para leer el árbol JSON y extraer valores. Este es solo un caso simple, pero puedes construir un deserializador más complejo usando el mismo enfoque.
Ten en cuenta que este ejemplo también demuestra cómo instruir a ObjectMapper para deserializar una lista tipada. Solo necesitas crear un objeto CollectionType y pasarlo al analizar el contenido JSON.
Deserializar valores usando JsonNode
@Test
public void deserializeListOfStringObjectsUsingTree() throws IOException {
var mapper = new ObjectMapper();
var json = "[{\"string\":\"Juan Garcia\"},{\"string\":\"Manuel Perez\"}]";
var values = mapper.readTree(json).findValuesAsText("string");
log.info("Using the JSON tree reader to extract field values: {}", values);
assertThat(values).containsExactly("Juan Garcia", "Manuel Perez");
}
Este código hace lo mismo que el anterior, pero usa el método readTree() del ObjectMapper para obtener un objeto JsonNode y luego usar sus métodos para analizar los valores. En este caso, el método findValuesAsText() es todo lo que necesitas para extraer todos los valores que coincidan con ese nombre de campo a una lista.
Hay algunos otros métodos útiles en esa clase para recorrer el JSON, recuperar valores usando una ruta, etc. Es una forma conveniente de leer contenido JSON sin necesidad de crear todas las clases Java correspondientes.
Deserializar a un objeto Java simple
@Test
public void deserializeListOfPersonDoesNotWork() {
var mapper = new ObjectMapper();
var json = "{\"name\":\"Juan Garcia\",\"birthdate\":[1980,9,15]}";
var throwable = catchThrowable(() -> mapper.readValue(json, Person.class));
log.info("You need to use an empty constructor: {}", throwable.getMessage());
assertThat(throwable).isInstanceOf(InvalidDefinitionException.class);
}
Si intentas usar la clase Person mostrada anteriormente para deserializar el contenido JSON correspondiente, obtendrás un error. Esto es porque no tenemos un constructor vacío que Jackson pueda usar durante su lógica de deserialización (usando reflection). Hay dos formas comunes de hacer que un objeto Java simple sea candidato para la deserialización JSON: incluir un constructor vacío o usar anotaciones de Jackson. Los siguientes dos ejemplos cubren esos escenarios.
Deserializar a un objeto Java simple con un constructor vacío
public class PersonEC {
private String name;
private LocalDate birthdate;
public PersonEC() {
}
public String getName() {
return name;
}
public LocalDate getBirthdate() {
return birthdate;
}
@Override
public String toString() {
return "PersonEC{" +
"name='" + name + '\'' +
", birthdate=" + birthdate +
'}';
}
}
@Test
public void deserializeListOfPersonEmptyConstructor() throws IOException {
var mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
var json = "{\"name\":\"Juan Garcia\",\"birthdate\":[1980,9,15]}";
var value = mapper.readValue(json, PersonEC.class);
log.info("Deserializing a simple POJO with empty constructor: {}", value);
assertThat(value.getName()).isEqualTo("Juan Garcia");
assertThat(value.getBirthdate())
.isEqualTo(LocalDate.of(1980, 9, 15));
}
Puedes crear una clase separada según el ejemplo o puedes modificar la existente. Ahora instruyes al ObjectMapper para deserializar usando la clase con el constructor vacío y todo debería funcionar como se espera.
Ten en cuenta que también puedes usar el JavaTimeModule en tiempo de deserialización. En este caso, el ejemplo está usando el formato de array predeterminado para LocalDate.
Deserializar a un objeto Java simple usando anotaciones
public class PersonAnnotated {
private final String name;
private final LocalDate birthdate;
@JsonCreator
public PersonAnnotated(
@JsonProperty("name") String name,
@JsonProperty("birthdate") LocalDate birthdate) {
this.name = name;
this.birthdate = birthdate;
}
public String getName() {
return name;
}
public LocalDate getBirthdate() {
return birthdate;
}
}
@Test
public void deserializePersonAnnotated() throws IOException {
var mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
var json = "{\"name\":\"Juan Garcia\",\"birthdate\":[1980,9,15]}";
var value = mapper.readValue(json, PersonAnnotated.class);
log.info("Deserializing a simple POJO with @JsonCreator: {}", value);
assertThat(value.getName()).isEqualTo("Juan Garcia");
assertThat(value.getBirthdate())
.isEqualTo(LocalDate.of(1980, 9, 15));
}
Una forma popular de usar Jackson es aprovechando las anotaciones incluidas en la biblioteca jackson-annotations. Como puedes ver, podemos anotar el constructor que debe usarse para crear el objeto (@JsonCreator) y también anotamos los parámetros con los nombres de los campos, usando @JsonProperty.
Deserializar a una clase Java diferente
public class PersonV2 {
private final String name;
private final LocalDate birthdate;
private final List<String> hobbies;
@JsonCreator
public PersonV2(@JsonProperty("name") String name,
@JsonProperty("birthdate") LocalDate birthdate,
@JsonProperty("hobbies") List<String> hobbies) {
this.name = name;
this.birthdate = birthdate;
this.hobbies = hobbies;
}
public String getName() {
return name;
}
public LocalDate getBirthdate() {
return birthdate;
}
public List<String> getHobbies() {
return hobbies;
}
}
@Test
public void deserializePersonV2AsPersonFails() {
var mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
var json = "{\"name\":\"Juan Garcia\",\"birthdate\":[1980,9,15]," +
"\"hobbies\":[\"football\",\"squash\"]}";
var throwable = catchThrowable(() -> mapper.readValue(json, PersonEC.class));
log.info("Trying to deserialize with unknown properties" +
" fails by default: {}", throwable.getMessage());
assertThat(throwable).isInstanceOf(UnrecognizedPropertyException.class);
}
Este código de ejemplo está intentando deserializar un objeto JSON con un campo extra que no existe en PersonEC, hobbies. Este campo en realidad pertenece a un objeto PersonV2 serializado. Como muestra este ejemplo, no puedes obtener compatibilidad hacia atrás por defecto en este caso ya que la propiedad desconocida causa un error. Pero esto tiene una solución muy fácil, como verás en la siguiente sección.
Deserializar a un objeto Java ignorando campos desconocidos con configuración
@Test
public void deserializePersonV2AsPersonIgnoringProperties() throws IOException {
var mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
var json = "{\"name\":\"Juan Garcia\",\"birthdate\":[1980,9,15]," +
"\"hobbies\":[\"football\",\"squash\"]}";
PersonEC value = mapper.readValue(json, PersonEC.class);
log.info("Deserializing a simple POJO ignoring unknown properties: {}", value);
assertThat(value.getName()).isEqualTo("Juan Garcia");
assertThat(value.getBirthdate())
.isEqualTo(LocalDate.of(1980, 9, 15));
}
Puedes configurar el ObjectMapper para ignorar propiedades desconocidas como en este ejemplo, estableciendo el valor de DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES a false. Como resultado, la propiedad hobbies se ignora ya que no hay campo con ese nombre en la clase PersonEC. Con esta configuración, todas las propiedades desconocidas para todos los objetos deserializados con este mapper serán ignoradas.
Deserializar a un objeto Java ignorando campos desconocidos con anotaciones
@JsonIgnoreProperties(ignoreUnknown = true)
public class PersonAnnotated {
// ...
}
@Test
public void deserializePersonV2AsPersonAnnotated() throws IOException {
var mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
var json = "{\"name\":\"Juan Garcia\",\"birthdate\":[1980,9,15]," +
"\"hobbies\":[\"football\",\"squash\"]}";
var value = mapper.readValue(json, PersonAnnotated.class);
log.info("Using @JsonIgnoreProperties to ignore unknown fields: {}", value);
assertThat(value.getName()).isEqualTo("Juan Garcia");
assertThat(value.getBirthdate())
.isEqualTo(LocalDate.of(1980, 9, 15));
}
También puedes lograr el mismo resultado que en el ejemplo anterior anotando las clases que son el objetivo de la deserialización con @JsonIgnoreProperties. De esta manera puedes elegir para qué clases quieres ser flexible (o estricto, en caso de que también cambies la configuración predeterminada).
Ignorar campos JSON puede ser arriesgado pero también es una forma de implementar compatibilidad hacia atrás cuando las clases solo están agregando nuevos campos en nuevas versiones. También puede ayudar a implementar Domain-Driven Design si estás haciendo copias superficiales de tus datos en diferentes módulos del sistema. Entonces, puedes tener clases que contengan solo los campos que te interesan e ignorar otros de forma segura.
Deserializar un objeto Java a un Map de key-values
@Test
public void deserializePersonV2AsMap() throws IOException {
var mapper = new ObjectMapper();
var json = "{\"name\":\"Juan Garcia\",\"birthdate\":[1980,9,15]," +
"\"hobbies\":[\"football\",\"squash\"]}";
Map value = mapper.readValue(json, Map.class);
log.info("Deserializing a JSON object as a map: {}", value);
assertThat(value.get("name")).isEqualTo("Juan Garcia");
assertThat(value.get("birthdate"))
.isEqualTo(Lists.newArrayList(1980, 9, 15));
assertThat(value.get("hobbies"))
.isEqualTo(Lists.newArrayList("football", "squash"));
}
A veces, puede ser conveniente deserializar objetos Java como tipos simples. En estos casos, puedes instruir a Jackson para deserializar el objeto a un Map. Contendrá nombres de campos como claves y su contenido deserializado como valores.
Ten en cuenta que los deserializadores predeterminados se aplican para tipos de campo básicos en el Map. Jackson deserializa la fecha como una colección, al igual que el array de hobbies.
Deserializar una lista de objetos Java a una lista de objetos Map
@Test
public void deserializeListOfPersonV2AsListOfMaps() throws IOException {
var mapper = new ObjectMapper();
var json = "[{\"name\":\"Juan Garcia\",\"birthdate\":[1980,9,15]," +
"\"hobbies\":[\"football\",\"squash\"]}," +
"{\"name\":\"Manuel Perez\",\"birthdate\":\"1987-07-23\"}]";
// Esto no es necesario ya que es el comportamiento predeterminado (deserializar a map)
var mapCollectionType = mapper.getTypeFactory()
.constructCollectionType(List.class, Map.class);
// También puedes usar List.class en lugar de mapCollectionType
List<Map> value = mapper.readValue(json, mapCollectionType);
log.info("Deserializing objects as a list of maps: {}", value);
assertThat(value.get(0).get("name")).isEqualTo("Juan Garcia");
assertThat(value.get(1).get("name")).isEqualTo("Manuel Perez");
}
Si quieres aplicar el enfoque anterior a una colección de objetos, puedes usar el CollectionType ya cubierto para especificar el tipo de colección que quieres obtener como resultado. Pero en este caso, no lo necesitas. El comportamiento predeterminado del ObjectMapper al deserializar objetos en una lista es crear una lista de maps de campo-valor.
Deserializar una lista de objetos Java
@Test
public void deserializeListOfPersonV2AsListOfObjects() throws IOException {
var mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
var mapCollectionType = mapper.getTypeFactory()
.constructCollectionType(List.class, PersonV2.class);
var json = "[{\"name\":\"Juan Garcia\",\"birthdate\":[1980,9,15]," +
"\"hobbies\":[\"football\",\"squash\"]}," +
"{\"name\":\"Manuel Perez\",\"birthdate\":\"1987-07-23\"}]";
List<PersonV2> value = mapper.readValue(json, mapCollectionType);
log.info("Deserializing to a list of POJOs: {}", value);
assertThat(value.get(0).getName()).isEqualTo("Juan Garcia");
assertThat(value.get(1).getName()).isEqualTo("Manuel Perez");
assertThat(value.get(1).getHobbies()).isNull();
}
En este caso, quieres establecer explícitamente el tipo de la colección a PersonV2, de lo contrario obtendrás un Map como se mostró antes. Recuerda que esta estrategia para pasar tipos es necesaria debido a Type Erasure, que te impide usar genéricos como en List<PersonV2> value = mapper.readValue(json, List<PersonV2>.class);.
Ten en cuenta que el objeto sin el campo hobbies se deserializa correctamente, y su valor se establece en null. No necesitas ignorar la propiedad en este caso ya que el campo no está presente en JSON.
Deserializar Java Optionals
@Test
public void deserializeListOfPersonV2AsListUsingOptional() throws IOException {
var mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.registerModule(new Jdk8Module());
var mapCollectionType = mapper.getTypeFactory()
.constructCollectionType(List.class, PersonV2Optional.class);
var json = "[{\"name\":\"Juan Garcia\",\"birthdate\":[1980,9,15]," +
"\"hobbies\":[\"football\",\"squash\"]}," +
"{\"name\":\"Manuel Perez\",\"birthdate\":\"1987-07-23\"}]";
List<PersonV2Optional> value = mapper.readValue(json, mapCollectionType);
log.info("Deserializing to a list of POJOs using Optional values: {}", value);
assertThat(value.get(0).getHobbies())
.isPresent()
.hasValueSatisfying(hobbies -> assertThat(hobbies).hasSize(2));
assertThat(value.get(1).getHobbies()).isEmpty();
}
Una forma más conveniente de tratar con campos no presentes es hacerlos Optional en tus POJOs. Eso te permite usar su poderosa API para establecer valores predeterminados, ejecutar código condicional, etc. Este es el mismo ejemplo que antes, solo que con una versión modificada de PersonV2 que contiene hobbies como Optional<List>.
Jackson puede ser lo suficientemente inteligente como para transformar valores no presentes a Optionals vacíos en lugar de nulls. Sin embargo, para que esto funcione correctamente, necesitas incluir la dependencia jackson-datatype-jdk8 y configurar el ObjectMapper registrando el Jdk8Module.
Get More Architecture Insights
Join hundreds of software architects and engineers getting practical insights delivered to their inbox.