Convertir un JSON a objetos y objetos a JSON con JSON-B, Gson y Jackson en Java

Escrito por el , actualizado el .
java planeta-codigo programacion
Enlace permanente Comentarios

Java

Continuando la serie de pequeños artículos sobre cómo procesar JSON después de ver anteriormente otras dos formas, Generar, procesar y modificar documentos JSON con JSON-P en Java y Usar expresiones JSONPath para extraer datos de un JSON en Java, en este artículo hay una tercera.

En las dos primeras si los datos son muchos o son todos la tarea de recuperar los datos uno a uno requiere una buena cantidad de código. Dado que un JSON no son nada más que valores, arrays y mapas utilizando la estructuras de datos equivalentes de Java se puede hacer una correspondencia entre los datos del JSON a objetos POJO siguiendo ciertas convenciones.

Una vez que los datos han sido cargados en una instancia de una clase se recuperan los datos con los correspondientes métodos get del objeto, además dado que las propiedades de los objetos tiene un tipo se realiza la conversión adecuada para convertir el dato del JSON al tipo de la propiedad del objeto.

Hay tres librerías distintas populares para hacer este binding entre JSON y objetos. JSON-B, Gson y Jackson siendo la primera la más estándar en el lenguaje Java. En los siguientes ejemplos dada una cadena con JSON y la clase raíz a la que hacer la correspondencia de las propiedades se crea una instancia de Comprador y múltiples de Direccion. La correspondencia entre las propiedades del JSON y del objeto se hace en base al nombre.

Se utilizan los métodos toJson() tanto en JSON-B como en Gson y el método writeValueAsString() en Jackson para convertir a JSON y los métodos fromJson() y readValue() para convertir desde JSON a objetos. Estos métodos devuelven una instancia de la clase raíz indicada y acceder a las propiedades se hace con los correspondientes getter.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package io.github.picodotdev.blogbitix.javajson;

...

public class Main {

    public static void main(String[] args) throws Exception {
        Comprador comprador = buildComprador();
        List<Comprador> compradores = List.of(buildComprador(), buildComprador());
        ...

        // JSON-B
        JsonbConfig config = new JsonbConfig().withAdapters(new JsonbLocalDateAdapter());
        Jsonb jsonb = JsonbBuilder.create(config);

        json = jsonb.toJson(comprador);
        comprador = jsonb.fromJson(json, Comprador.class);
        ...
        System.out.printf("JSON-B: %s%n", json);
        System.out.printf("JSON-B (comprador): %s, %s, %d%n", comprador.getNombre(), comprador.getFechaNacimiento(), comprador.getDirecciones().size());

        // Gson
        GsonBuilder builder = new GsonBuilder();
        builder.registerTypeAdapter(LocalDate.class, new GsonLocalDateTypeAdapter());
        Gson gson = builder.create();;

        json = gson.toJson(comprador);
        comprador = gson.fromJson(json, Comprador.class);
        ...
        System.out.printf("Gson: %s%n", json);
        System.out.printf("Gson (comprador): %s, %s, %d%n", comprador.getNombre(), comprador.getFechaNacimiento(), comprador.getDirecciones().size());

        // Jackson
        ObjectMapper mapper = new ObjectMapper();
        SimpleModule module = new SimpleModule();
        module.addSerializer(LocalDate.class, new JacksonLocalDateSerializer());
        module.addDeserializer(LocalDate.class, new JacksonLocalDateDeserializer());
        mapper.registerModule(module);

        json = mapper.writeValueAsString(comprador);
        comprador = mapper.readValue(json, Comprador.class);
        ...
        System.out.printf("Jackson: %s%n", json);
        System.out.printf("Jackson (comprador): %s, %s, %d%n", comprador.getNombre(), comprador.getFechaNacimiento(), comprador.getDirecciones().size());
        
        ...
    }

    private static Comprador buildComprador() {
        Comprador comprador = new Comprador();
        comprador.setNombre("Juan");
        comprador.setFechaNacimiento(LocalDate.now());
        comprador.getDirecciones().add(buildDireccion());
        comprador.getDirecciones().add(buildDireccion());
        return comprador;
    }

    private static Direccion buildDireccion() {
        return new Direccion("calle", "ciudad", "codigoPostal", "pais");
    }
}
Main-1.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package io.github.picodotdev.blogbitix.javajson;

...

public class Comprador {

    private String nombre;
    private LocalDate fechaNacimiento;
    private List<Direccion> direcciones;

    public Comprador() {
        direcciones = new ArrayList<>();
    }

    public String getNombre() {
        return nombre;
    }

    public void setNombre(String nombre) {
        this.nombre = nombre;
    }

    public LocalDate getFechaNacimiento() {
        return fechaNacimiento;
    }

    public void setFechaNacimiento(LocalDate fechaNacimiento) {
        this.fechaNacimiento = fechaNacimiento;
    }

    public List<Direccion> getDirecciones() {
        return direcciones;
    }

    public void setDirecciones(List<Direccion> direcciones) {
        this.direcciones = direcciones;
    }
}
Comprador.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package io.github.picodotdev.blogbitix.javajson;

public class Direccion {

    private String calle;
    private String ciudad;
    private String codigoPostal;
    private String pais;

    public Direccion() {
    }

    public Direccion(String calle, String ciudad, String codigoPostal, String pais) {
        this.calle = calle;
        this.ciudad = ciudad;
        this.codigoPostal = codigoPostal;
        this.pais = pais;
    }

    public String getCalle() {
        return calle;
    }

    public void setCalle(String calle) {
        this.calle = calle;
    }

    public String getCiudad() {
        return ciudad;
    }

    public void setCiudad(String ciudad) {
        this.ciudad = ciudad;
    }

    public String getCodigoPostal() {
        return codigoPostal;
    }

    public void setCodigoPostal(String codigoPostal) {
        this.codigoPostal = codigoPostal;
    }

    public String getPais() {
        return pais;
    }

    public void setPais(String pais) {
        this.pais = pais;
    }
}
Direccion.java
1
2
3
4
5
6
JSON-B: {"direcciones":[{"calle":"calle","ciudad":"ciudad","codigoPostal":"codigoPostal","pais":"pais"},{"calle":"calle","ciudad":"ciudad","codigoPostal":"codigoPostal","pais":"pais"}],"fechaNacimiento":"2019-02-01","nombre":"Juan"}
JSON-B (comprador): Juan, 2019-02-01, 2
Gson: {"nombre":"Juan","fechaNacimiento":"2019-02-01","direcciones":[{"calle":"calle","ciudad":"ciudad","codigoPostal":"codigoPostal","pais":"pais"},{"calle":"calle","ciudad":"ciudad","codigoPostal":"codigoPostal","pais":"pais"}]}
Gson (comprador): Juan, 2019-02-01, 2
Jackson: {"nombre":"Juan","fechaNacimiento":"2019-02-01","direcciones":[{"calle":"calle","ciudad":"ciudad","codigoPostal":"codigoPostal","pais":"pais"},{"calle":"calle","ciudad":"ciudad","codigoPostal":"codigoPostal","pais":"pais"}]}
Jackson (comprador): Juan, 2019-02-01, 2
System.out

El funcionamiento de las tres librerías es similar varían los nombres de los métodos que tienen sus API, se proporciona la cadena JSON y la clase de la que se crea una instancia en la que se cargan los datos. En el caso de que el JSON sea una lista o array de varios elementos el tipo indicado en el que cargar los datos cambia respecto a cargar únicamente un objeto.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
public class Main {

    public static void main(String[] args) throws Exception {
        Comprador comprador = buildComprador();
        ...

        // JSON-B
        compradores = jsonb.fromJson(arrayJson, new ArrayList<Comprador>(){}.getClass().getGenericSuperclass());

        // Gson
        compradores = gson.fromJson(arrayJson, new TypeToken<List<Comprador>>(){}.getType());

        // Jackson
        compradores = mapper.readValue(arrayJson, new TypeReference<List<Comprador>>(){});
        
        ...
    }

    ...
}
Main-2.java

Para añadir tipos de datos que no están entre los básicos de JSON como es una fecha cada librería proporciona interfaces o clases abstractas para hacer la conversión desde el dato a un tipo de JSON y desde JSON al tipo del dato. En este caso para un tipo de dato LocalDate.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package io.github.picodotdev.blogbitix.javajson;

...

public class JsonbLocalDateAdapter implements JsonbAdapter<LocalDate, JsonString> {

    @Override
    public JsonString adaptToJson(LocalDate obj) throws Exception {
        return Json.createValue(obj.format(DateTimeFormatter.ISO_LOCAL_DATE));
    }

    @Override
    public LocalDate adaptFromJson(JsonString obj) throws Exception {
        return LocalDate.parse(obj.getString(), DateTimeFormatter.ISO_LOCAL_DATE);
    }
}
JsonLocalDateAdapter.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package io.github.picodotdev.blogbitix.javajson;

...

public class GsonLocalDateTypeAdapter extends TypeAdapter<LocalDate> {

    @Override
    public void write(JsonWriter out, LocalDate value) throws IOException {
        out.value(value.format(DateTimeFormatter.ISO_LOCAL_DATE));
    }

    @Override
    public LocalDate read(JsonReader in) throws IOException {
        return LocalDate.parse(in.nextString(), DateTimeFormatter.ISO_LOCAL_DATE);
    }
}
GsonLocalDateTypeAdapter.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
package io.github.picodotdev.blogbitix.javajson;

...

public class JacksonLocalDateSerializer extends JsonSerializer<LocalDate> {

    @Override
    public void serialize(LocalDate value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        gen.writeString(value.format(DateTimeFormatter.ISO_LOCAL_DATE));
    }
}
JacksonLocalDateSerializer.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
package io.github.picodotdev.blogbitix.javajson;

...

public class JacksonLocalDateDeserializer extends JsonDeserializer<LocalDate> {

    @Override
    public LocalDate deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        return LocalDate.parse(p.getValueAsString(), DateTimeFormatter.ISO_LOCAL_DATE);
    }
}
JacksonLocalDateDeserializer.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
...

dependencies {
    implementation "javax.json:javax.json-api:1.1"
    implementation "javax.json.bind:javax.json.bind-api:1.0"
    implementation "javax.json.bind:javax.json.bind-api:1.0"
    implementation "com.google.code.gson:gson:2.8.5"
    implementation "com.fasterxml.jackson.core:jackson-databind:2.9.8"

    ...

    runtimeOnly "org.eclipse:yasson:1.0"
    runtimeOnly "org.glassfish:javax.json:1.1"
}

plugins {
    id 'java'
    id 'application'
}

mainClassName = 'io.github.picodotdev.blogbitix.javajson.Main'

repositories {
    jcenter()
}
build.gradle
Terminal

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub y probarlo en tu equipo ejecutando siguiente comando:
./gradlew run

Referencia:


Comparte el artículo: