Introduction au Jackson ObjectMapper

1. Vue d'ensemble

Ce didacticiel se concentre sur la compréhension de la classe Jackson ObjectMapper et sur la sérialisation des objets Java en JSON et la désérialisation de la chaîne JSON en objets Java.

Pour en savoir plus sur la bibliothèque Jackson en général, le didacticiel Jackson est un bon point de départ.

2. Dépendances

Ajoutons d'abord les dépendances suivantes au pom.xml :

 com.fasterxml.jackson.core jackson-databind 2.11.1  

Cette dépendance ajoutera également de manière transitoire les bibliothèques suivantes au chemin de classe:

  1. annotations-jackson
  2. jackson-core

Utilisez toujours les dernières versions du référentiel central Maven pour jackson-databind .

3. Lecture et écriture avec ObjectMapper

Commençons par les opérations de base de lecture et d'écriture.

L' API readValue simple de l' ObjectMapper est un bon point d'entrée. Nous pouvons l'utiliser pour analyser ou désérialiser le contenu JSON dans un objet Java.

En outre, du côté de l'écriture, nous pouvons utiliser l' API writeValue pour sérialiser n'importe quel objet Java en tant que sortie JSON.

Nous utiliserons la classe Car suivante avec deux champs comme objet pour sérialiser ou désérialiser tout au long de cet article:

public class Car { private String color; private String type; // standard getters setters }

3.1. Objet Java en JSON

Voyons un premier exemple de sérialisation d'un objet Java en JSON à l'aide de la méthode writeValue de la classe ObjectMapper :

ObjectMapper objectMapper = new ObjectMapper(); Car car = new Car("yellow", "renault"); objectMapper.writeValue(new File("target/car.json"), car); 

La sortie de ce qui précède dans le fichier sera:

{"color":"yellow","type":"renault"} 

Les méthodes writeValueAsString et writeValueAsBytes de la classe ObjectMapper génèrent un JSON à partir d'un objet Java et renvoient le JSON généré sous forme de chaîne ou de tableau d'octets:

String carAsString = objectMapper.writeValueAsString(car); 

3.2. JSON en objet Java

Vous trouverez ci-dessous un exemple simple de conversion d'une chaîne JSON en objet Java à l'aide de la classe ObjectMapper :

String json = "{ \"color\" : \"Black\", \"type\" : \"BMW\" }"; Car car = objectMapper.readValue(json, Car.class); 

La fonction readValue () accepte également d'autres formes d'entrée, comme un fichier contenant une chaîne JSON:

Car car = objectMapper.readValue(new File("src/test/resources/json_car.json"), Car.class);

ou une URL:

Car car = objectMapper.readValue(new URL("file:src/test/resources/json_car.json"), Car.class);

3.3. JSON à Jackson JsonNode

Alternativement, un JSON peut être analysé dans un objet JsonNode et utilisé pour récupérer des données à partir d'un nœud spécifique:

String json = "{ \"color\" : \"Black\", \"type\" : \"FIAT\" }"; JsonNode jsonNode = objectMapper.readTree(json); String color = jsonNode.get("color").asText(); // Output: color -> Black 

3.4. Création d'une liste Java à partir d'une chaîne de tableau JSON

Nous pouvons analyser un JSON sous la forme d'un tableau dans une liste d'objets Java à l'aide d'un TypeReference :

String jsonCarArray = "[{ \"color\" : \"Black\", \"type\" : \"BMW\" }, { \"color\" : \"Red\", \"type\" : \"FIAT\" }]"; List listCar = objectMapper.readValue(jsonCarArray, new TypeReference
    
     (){}); 
    

3.5. Création d'une carte Java à partir d'une chaîne JSON

De même, nous pouvons analyser un JSON dans une carte Java :

String json = "{ \"color\" : \"Black\", \"type\" : \"BMW\" }"; Map map = objectMapper.readValue(json, new TypeReference(){}); 

4. Fonctionnalités avancées

L'une des plus grandes forces de la bibliothèque Jackson est le processus de sérialisation et de désérialisation hautement personnalisable.

Dans cette section, nous allons passer en revue certaines fonctionnalités avancées où la réponse JSON d'entrée ou de sortie peut être différente de l'objet qui génère ou consomme la réponse.

4.1. Configuration de la fonctionnalité de sérialisation ou de désérialisation

Lors de la conversion d'objets JSON en classes Java, au cas où la chaîne JSON contiendrait de nouveaux champs, le processus par défaut entraînera une exception:

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

Une autre fonctionnalité petite mais utile disponible via la classe DeserializationFeature est la possibilité de générer le type de collection que nous voulons à partir d'une réponse JSON Array.

Par exemple, nous pouvons générer le résultat sous forme de tableau:

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

Ou sous forme de liste :

String jsonCarArray = "[{ \"color\" : \"Black\", \"type\" : \"BMW\" }, { \"color\" : \"Red\", \"type\" : \"FIAT\" }]"; ObjectMapper objectMapper = new ObjectMapper(); List listCar = objectMapper.readValue(jsonCarArray, new TypeReference
    
     (){}); // print cars
    

Plus d'informations sur la gestion des collections avec Jackson sont disponibles ici.

5. Conclusion

Jackson est une bibliothèque de sérialisation / désérialisation JSON solide et mature pour Java. L' API ObjectMapper fournit un moyen simple d'analyser et de générer des objets de réponse JSON avec beaucoup de flexibilité. Cet article aborde les principales fonctionnalités qui rendent la bibliothèque si populaire.

Le code source qui accompagne l'article se trouve à l'adresse over sur GitHub.