1. Información general
Este tutorial se centra en comprender la clase Jackson ObjectMapper y cómo serializar objetos Java en JSON y deserializar cadenas JSON en objetos Java.
Para comprender más sobre la biblioteca de Jackson en general, el Tutorial de Jackson es un buen lugar para comenzar.
2. Dependencias
Primero agreguemos las siguientes dependencias al pom.xml :
com.fasterxml.jackson.core jackson-databind 2.11.1
Esta dependencia también agregará de manera transitiva las siguientes bibliotecas a la ruta de clases:
- Jackson-anotaciones
- jackson-core
Utilice siempre las últimas versiones del repositorio central de Maven para jackson-databind .
3. Leer y escribir con ObjectMapper
Comencemos con las operaciones básicas de lectura y escritura.
La API readValue simple de ObjectMapper es un buen punto de entrada. Podemos usarlo para analizar o deserializar contenido JSON en un objeto Java.
Además, en el lado de la escritura, podemos usar la API writeValue para serializar cualquier objeto Java como salida JSON.
Usaremos la siguiente clase Car con dos campos como objeto para serializar o deserializar a lo largo de este artículo:
public class Car { private String color; private String type; // standard getters setters }
3.1. Objeto Java a JSON
Veamos un primer ejemplo de serialización de un objeto Java en JSON usando el método writeValue de la clase ObjectMapper :
ObjectMapper objectMapper = new ObjectMapper(); Car car = new Car("yellow", "renault"); objectMapper.writeValue(new File("target/car.json"), car);
La salida de lo anterior en el archivo será:
{"color":"yellow","type":"renault"}
Los métodos writeValueAsString y writeValueAsBytes de la clase ObjectMapper generan un JSON a partir de un objeto Java y devuelven el JSON generado como una cadena o como una matriz de bytes:
String carAsString = objectMapper.writeValueAsString(car);
3.2. JSON a objeto Java
A continuación se muestra un ejemplo simple de convertir una cadena JSON en un objeto Java utilizando la clase ObjectMapper :
String json = "{ \"color\" : \"Black\", \"type\" : \"BMW\" }"; Car car = objectMapper.readValue(json, Car.class);
La función readValue () también acepta otras formas de entrada, como un archivo que contiene una cadena JSON:
Car car = objectMapper.readValue(new File("src/test/resources/json_car.json"), Car.class);
o una URL:
Car car = objectMapper.readValue(new URL("file:src/test/resources/json_car.json"), Car.class);
3.3. JSON a Jackson JsonNode
Alternativamente, un JSON se puede analizar en un objeto JsonNode y usar para recuperar datos de un nodo específico:
String json = "{ \"color\" : \"Black\", \"type\" : \"FIAT\" }"; JsonNode jsonNode = objectMapper.readTree(json); String color = jsonNode.get("color").asText(); // Output: color -> Black
3.4. Creación de una lista de Java a partir de una cadena de matriz JSON
Podemos analizar un JSON en forma de matriz en una lista de objetos Java usando una TypeReference :
String jsonCarArray = "[{ \"color\" : \"Black\", \"type\" : \"BMW\" }, { \"color\" : \"Red\", \"type\" : \"FIAT\" }]"; List listCar = objectMapper.readValue(jsonCarArray, new TypeReference
(){});
3.5. Creación de un mapa de Java a partir de una cadena JSON
Del mismo modo, podemos analizar un JSON en un mapa de Java :
String json = "{ \"color\" : \"Black\", \"type\" : \"BMW\" }"; Map map = objectMapper.readValue(json, new TypeReference
4. Funciones avanzadas
Una de las mayores fortalezas de la biblioteca Jackson es el proceso de serialización y deserialización altamente personalizable.
En esta sección, veremos algunas características avanzadas donde la respuesta JSON de entrada o salida puede ser diferente del objeto que genera o consume la respuesta.
4.1. Configuración de la función de serialización o deserialización
Al convertir objetos JSON en clases Java, en caso de que la cadena JSON tenga algunos campos nuevos, el proceso predeterminado dará como resultado una excepción:
String jsonString = "{ \"color\" : \"Black\", \"type\" : \"Fiat\", \"year\" : \"1970\" }";
The JSON string in the above example in the default parsing process to the Java object for the Class Car will result in the UnrecognizedPropertyException exception.
Through the configure method, we can extend the default process to ignore the new fields:
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); Car car = objectMapper.readValue(jsonString, Car.class); JsonNode jsonNodeRoot = objectMapper.readTree(jsonString); JsonNode jsonNodeYear = jsonNodeRoot.get("year"); String year = jsonNodeYear.asText();
Yet another option is based on the FAIL_ON_NULL_FOR_PRIMITIVES, which defines if the null values for primitive values are allowed:
objectMapper.configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, false);
Similarly, FAIL_ON_NUMBERS_FOR_ENUM controls if enum values are allowed to be serialized/deserialized as numbers:
objectMapper.configure(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS, false);
You can find the comprehensive list of serialization and deserialization features on the official site.
4.2. Creating Custom Serializer or Deserializer
Another essential feature of the ObjectMapper class is the ability to register a custom serializer and deserializer.
Custom serializers and deserializers are very useful in situations where the input or the output JSON response is different in structure than the Java class into which it must be serialized or deserialized.
Below is an example of a custom JSON serializer:
public class CustomCarSerializer extends StdSerializer { public CustomCarSerializer() { this(null); } public CustomCarSerializer(Class t) { super(t); } @Override public void serialize( Car car, JsonGenerator jsonGenerator, SerializerProvider serializer) { jsonGenerator.writeStartObject(); jsonGenerator.writeStringField("car_brand", car.getType()); jsonGenerator.writeEndObject(); } }
This custom serializer can be invoked like this:
ObjectMapper mapper = new ObjectMapper(); SimpleModule module = new SimpleModule("CustomCarSerializer", new Version(1, 0, 0, null, null, null)); module.addSerializer(Car.class, new CustomCarSerializer()); mapper.registerModule(module); Car car = new Car("yellow", "renault"); String carJson = mapper.writeValueAsString(car);
Here's what the Car looks like (as JSON output) on the client side:
var carJson = {"car_brand":"renault"}
And here's an example of a custom JSON deserializer:
public class CustomCarDeserializer extends StdDeserializer { public CustomCarDeserializer() { this(null); } public CustomCarDeserializer(Class vc) { super(vc); } @Override public Car deserialize(JsonParser parser, DeserializationContext deserializer) { Car car = new Car(); ObjectCodec codec = parser.getCodec(); JsonNode node = codec.readTree(parser); // try catch block JsonNode colorNode = node.get("color"); String color = colorNode.asText(); car.setColor(color); return car; } }
This custom deserializer can be invoked in this way:
String json = "{ \"color\" : \"Black\", \"type\" : \"BMW\" }"; ObjectMapper mapper = new ObjectMapper(); SimpleModule module = new SimpleModule("CustomCarDeserializer", new Version(1, 0, 0, null, null, null)); module.addDeserializer(Car.class, new CustomCarDeserializer()); mapper.registerModule(module); Car car = mapper.readValue(json, Car.class);
4.3. Handling Date Formats
The default serialization of java.util.Date produces a number, i.e., epoch timestamp (number of milliseconds since January 1, 1970, UTC). But this is not very human readable and requires further conversion to be displayed in a human-readable format.
Let's wrap the Car instance we used so far inside the Request class with the datePurchased property:
public class Request { private Car car; private Date datePurchased; // standard getters setters }
To control the String format of a date and set it to, e.g., yyyy-MM-dd HH:mm a z, consider the following snippet:
ObjectMapper objectMapper = new ObjectMapper(); DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm a z"); objectMapper.setDateFormat(df); String carAsString = objectMapper.writeValueAsString(request); // output: {"car":{"color":"yellow","type":"renault"},"datePurchased":"2016-07-03 11:43 AM CEST"}
To learn more about serializing dates with Jackson, read our more in-depth write-up.
4.4. Handling Collections
Otra característica pequeña pero útil disponible a través de la clase DeserializationFeature es la capacidad de generar el tipo de colección que queremos a partir de una respuesta JSON Array.
Por ejemplo, podemos generar el resultado como una matriz:
String jsonCarArray = "[{ \"color\" : \"Black\", \"type\" : \"BMW\" }, { \"color\" : \"Red\", \"type\" : \"FIAT\" }]"; ObjectMapper objectMapper = new ObjectMapper(); objectMapper.configure(DeserializationFeature.USE_JAVA_ARRAY_FOR_JSON_ARRAY, true); Car[] cars = objectMapper.readValue(jsonCarArray, Car[].class); // print cars
O como una lista :
String jsonCarArray = "[{ \"color\" : \"Black\", \"type\" : \"BMW\" }, { \"color\" : \"Red\", \"type\" : \"FIAT\" }]"; ObjectMapper objectMapper = new ObjectMapper(); List listCar = objectMapper.readValue(jsonCarArray, new TypeReference
(){}); // print cars
Más información sobre el manejo de cobranzas con Jackson está disponible aquí.
5. Conclusión
Jackson es una biblioteca de serialización / deserialización JSON sólida y madura para Java. La API de ObjectMapper proporciona una forma sencilla de analizar y generar objetos de respuesta JSON con mucha flexibilidad. Este artículo analiza las principales características que hacen que la biblioteca sea tan popular.
El código fuente que acompaña al artículo se puede encontrar en GitHub.