Introduzione a Jackson ObjectMapper

1. Panoramica

Questo tutorial si concentra sulla comprensione della classe Jackson ObjectMapper e su come serializzare gli oggetti Java in JSON e deserializzare la stringa JSON in oggetti Java.

Per capire di più sulla biblioteca di Jackson in generale, il Jackson Tutorial è un buon punto di partenza.

2. Dipendenze

Aggiungiamo prima le seguenti dipendenze al pom.xml :

 com.fasterxml.jackson.core jackson-databind 2.11.1  

Questa dipendenza aggiungerà anche in modo transitorio le seguenti librerie al classpath:

  1. jackson-annotations
  2. jackson-core

Usa sempre le ultime versioni dal repository centrale di Maven per jackson-databind .

3. Lettura e scrittura utilizzando ObjectMapper

Cominciamo con le operazioni di lettura e scrittura di base.

La semplice API readValue di ObjectMapper è un buon punto di ingresso. Possiamo usarlo per analizzare o deserializzare il contenuto JSON in un oggetto Java.

Inoltre, per quanto riguarda la scrittura, possiamo utilizzare l' API writeValue per serializzare qualsiasi oggetto Java come output JSON.

Useremo la seguente classe Car con due campi come oggetto da serializzare o deserializzare in questo articolo:

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

3.1. Oggetto Java in JSON

Vediamo un primo esempio di serializzazione di un oggetto Java in JSON utilizzando il metodo writeValue della classe ObjectMapper :

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

L'output di quanto sopra nel file sarà:

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

I metodi writeValueAsString e writeValueAsBytes della classe ObjectMapper generano un JSON da un oggetto Java e restituiscono il JSON generato come stringa o come matrice di byte:

String carAsString = objectMapper.writeValueAsString(car); 

3.2. Oggetto da JSON a Java

Di seguito è riportato un semplice esempio di conversione di una stringa JSON in un oggetto Java utilizzando la classe ObjectMapper :

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

La funzione readValue () accetta anche altre forme di input, come un file contenente una stringa JSON:

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

o un URL:

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

3.3. JSON a Jackson JsonNode

In alternativa, un JSON può essere analizzato in un oggetto JsonNode e utilizzato per recuperare i dati da un nodo specifico:

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

3.4. Creazione di un elenco Java da una stringa di array JSON

Possiamo analizzare un JSON sotto forma di un array in un elenco di oggetti Java utilizzando un TypeReference :

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

3.5. Creazione di Java Map dalla stringa JSON

Allo stesso modo, possiamo analizzare un JSON in una mappa Java :

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

4. Funzionalità avanzate

Uno dei maggiori punti di forza della libreria Jackson è il processo di serializzazione e deserializzazione altamente personalizzabile.

In questa sezione, esamineremo alcune funzionalità avanzate in cui la risposta JSON di input o di output può essere diversa dall'oggetto che genera o consuma la risposta.

4.1. Configurazione della funzione di serializzazione o deserializzazione

Durante la conversione di oggetti JSON in classi Java, nel caso in cui la stringa JSON abbia alcuni nuovi campi, il processo predefinito genererà un'eccezione:

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

Un'altra piccola ma utile funzionalità disponibile tramite la classe DeserializationFeature è la capacità di generare il tipo di raccolta che desideriamo da una risposta JSON Array.

Ad esempio, possiamo generare il risultato come un array:

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 come elenco :

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

Ulteriori informazioni sulla gestione delle raccolte con Jackson sono disponibili qui.

5. conclusione

Jackson è una libreria di serializzazione / deserializzazione JSON solida e matura per Java. L' API ObjectMapper fornisce un modo semplice per analizzare e generare oggetti di risposta JSON con molta flessibilità. Questo articolo ha discusso le caratteristiche principali che rendono la libreria così popolare.

Il codice sorgente che accompagna l'articolo può essere trovato su GitHub.