JSON-B es una especificación standard de Java que nos permite convertir objeto hacia y desde mensajes JSON empleando un API de alto nivel y sin tener -necesariamente- conocimientos más profundos respecto al formato de JSON y su especificación. No debe confundirse con JSON-P, el cual constituye un API para procesar (parsear, generar, transformar, consultar) mensajes JSON.

Una de sus principales ventajas es que es consistente con JAXB y otros APIs de JavaEE y es muy simple de usar.

El código fuente de este artículo esta disponible en https://github.com/FlechaRoja/JSON-B

POM


Para proceder a usar JSON-B necesitamos agregar algunas dependencias a nuestro POM.

<dependencies>
       <dependency>
           <groupId>javax.json.bind</groupId>
           <artifactId>javax.json.bind-api</artifactId>
           <version>1.0</version>
       </dependency>

       <dependency>
           <groupId>org.eclipse</groupId>
           <artifactId>yasson</artifactId>
           <version>1.0</version>
       </dependency>

       <dependency>
           <groupId>org.glassfish</groupId>
           <artifactId>javax.json</artifactId>
           <version>1.1</version>
       </dependency>
   </dependencies>

Serializar y Deserializar


Vamos a suponer que tenemos una clase Clientes, la cual consta de varios atributos. Incluyendo una fecha de nacimiento usando el nuevo Date-Time API introducido en Java 8.

public class Clientes {
  private String nombre;
  private String primerApellido;
  private String segundoApellido;
  private LocalDate fechaNacimiento;
  private Boolean activo;

private List<Telefonos> telefonos;

Y a su vez, un cliente tiene una colección de ‘n’ Telefonos.

public class Telefonos {
    private String codPais;
    private String numero;
     ....

Y deseamos poder convertir ese objeto a una representación JSON. Para ello se realiza lo siguiente:

  Jsonb jsonb = JsonbBuilder.create();
  String result = jsonb.toJson(cliente);

El valor que obtenemos en result es el siguiente:

  {"activo":true,"fechaNacimiento":"1975-01-01","nombre":"Juan","primerApellido":"Perez","segundoApellido":"Perez"}

El proceso es extremadamente simple y no requiere de ninguna modificación a las clases Clientes y Telefonos.

¿Cómo construimos una instancia de Clientes con base en ese String? De manera trivial:

  cliente = jsonb.fromJson(result, Clientes.class);

Ahora bien, si la colección de Telefonos tuviera valores el proceso de serialización y deserialización es el mismo; pero el JSON que se genera varia de la siguiente manera:

  Telefonos tel = new Telefonos();
  tel.setCodPais("506");
  tel.setNumero("22222222");
  cliente.addTelefono(tel);

  tel = new Telefonos();
  tel.setCodPais("506");
  tel.setNumero("1111111");
  cliente.addTelefono(tel);

Y el JSON es:

{"activo":true,"fechaNacimiento":"1975-01-01","nombre":"Juan","primerApellido":"Perez","segundoApellido":"Perez",
"telefonos":[{"codPais":"506","numero":"22222222"},{"codPais":"506","numero":"1111111"}]}

Opciones Avanzadas


Si deseamos comenzar a manipular la forma en que se genera el JSON, solo debemos establecer la configuración que sea de nuestro interés.

Formato y Nomenclatura

Supongamos que queremos que el nombre de los atributos de nuestras clases sea en minúsculas y separados por un guión; y también que tengan un formato más legible para un ser humano.

   JsonbConfig config = new JsonbConfig()
             .withFormatting(true)
             .withPropertyNamingStrategy(PropertyNamingStrategy.LOWER_CASE_WITH_DASHES);

   Jsonb jsonb = JsonbBuilder.create(config);
   String result = jsonb.toJson(cliente);

Y el JSON resultante sería:

{
    "activo": true,
    "fecha-nacimiento": "1975-01-01",
    "nombre": "Juan",
    "primer-apellido": "Perez",
    "segundo-apellido": "Perez",
    "telefonos": [
        {
            "cod-pais": "506",
            "numero": "22222222"
        },
        {
            "cod-pais": "506",
            "numero": "1111111"
        }
    ]
}

Existen 6 estrategias de nomenclatura, que son:

  • IDENTITY (primerApellido) Esta se emplea por omisión.

  • LOWER_CASE_WITH_DASHES (primer-apellido)

  • LOWER_CASE_WITH_UNDERSCORES (primer_apellido)

  • UPPER_CAMEL_CASE (PrimerApellido)

  • UPPER_CAMEL_CASE_WITH_SPACES (Primer Apellido)

  • CASE_INSENSITIVE (pRiMeRaPeLlIdO) (suponiendo que así se nombre a la propiedad)

  • O tu propia implementación de la interfaz JsonbNamingStrategy

Ordenamiento

¿Qué sucede si queremos cambiar el orden en el cual se genera cada atributo? Nuevamente es solo modificar la configuración de nuestro interés; en este caso pasamos del ordenamiento por omisión lexicográfico al ‘Any’.

JsonbConfig config = new JsonbConfig()
    .withFormatting(true)
    .withPropertyNamingStrategy(PropertyNamingStrategy.LOWER_CASE_WITH_DASHES)
    .withPropertyOrderStrategy(PropertyOrderStrategy.ANY);

Y el JSON resultante sería:

{
    "primer-apellido": "Perez",
    "fecha-nacimiento": "1975-01-01",
    "segundo-apellido": "Perez",
    "telefonos": [
        {
            "numero": "22222222",
            "cod-pais": "506"
        },
        {
            "numero": "1111111",
            "cod-pais": "506"
        }
    ],
    "nombre": "Juan",
    "activo": true
}

Existen 3 estrategias de ordenamiento de las propiedades:

  • LEXICOGRAPHICAL (A-Z) Es la que se usa por omisión.

  • ANY (No esta definido el ordenamiento, dependería de la implementación, generalmente va de acuerdo al orden en el que se definieron las propiedades)

  • REVERSE (Z- A)

Manejo de Nulos

El comportamiento por omisión de JSON-B es tal que las propiedades con valor null no son representadas en el JSON. En ocasiones eso no es deseable, pero se requiere de solo una instrucción para cambiar ese accionar.

  Clientes cliente = new Clientes();
  cliente.setFechaNacimiento(LocalDate.parse("1975-01-01"));
  cliente.setNombre("Juan");
  cliente.setPrimerApellido("Perez");
  cliente.setSegundoApellido("Perez");
  cliente.setActivo(Boolean.TRUE);

  JsonbConfig config = new JsonbConfig()
               .withFormatting(true)
               .withNullValues(true) // Indicamos que los valores en null se representen.
               .withPropertyNamingStrategy(PropertyNamingStrategy.LOWER_CASE_WITH_DASHES);

  Jsonb jsonb = JsonbBuilder.create(config);
  String result = jsonb.toJson(cliente);
{
    "activo": true,
    "blob": null,
    "fecha-nacimiento": "1975-01-01",
    "nombre": "Juan",
    "primer-apellido": "Perez",
    "segundo-apellido": "Perez",
    "telefonos": null
}

Cambiar el nombre de una propiedad

Supongamos que necesitamos cambiar el nombre de una propiedad al generar el JSON. En nuestro ejemplo, necesitamos que el atributo ‘codPais’ de la clase Telefonos se genere con el nombre ‘codigo-pais’.

Para hacer el cambio solo necesitamos agregar la anotación @JsonbProperty

public class Telefonos {
    @JsonbProperty("codigo-pais")
    private String codPais;
    private String numero;

Y el JSON resultante sería:

{
    "activo": true,
    "fechaNacimiento": "1975-01-01",
    "nombre": "Juan",
    "primerApellido": "Perez",
    "segundoApellido": "Perez",
    "telefonos": [
        {
            "codigo-pais": "506",
            "numero": "22222222"
        },
        {
            "codigo-pais": "506",
            "numero": "1111111"
        }
    ]
}

Modificar la representación de la Fecha

En el caso de que el formato de las fechas no sea el que necesitamos, se puede establecer otro diferente por medio de la anotación @JsonbDateFormat

  @JsonbDateFormat("dd.MM.yyyy")
  private LocalDate fechaNacimiento;

Y el JSON resultante sería:

{
    "activo": true,
    "fechaNacimiento": "01.01.1975",
    "nombre": "Juan",
    "primerApellido": "Perez",
    "segundoApellido": "Perez",
    "telefonos": [
        {
            "codigo-pais": "506",
            "numero": "22222222"
        },
        {
            "codigo-pais": "506",
            "numero": "1111111"
        }
    ]
}

Codificación de Binarios

Afortunadamente JSON-B permite el soporte nativo de datos binarios. Por omisión se usa el ‘encoding’ BYTE.

Agregamos un byte[] en Clientes.

public class Clientes {

    private String nombre;
    private String primerApellido;
    private String segundoApellido;

    @JsonbDateFormat("dd.MM.yyyy")
    private LocalDate fechaNacimiento;
    private Boolean activo;

    // Dato binario
    private byte[] blob;

Y para generar el JSON, se agrega esta configuración:

  Clientes cliente = new Clientes();
  cliente.setFechaNacimiento(LocalDate.parse("1975-01-01"));
  cliente.setNombre("Juan");
  cliente.setPrimerApellido("Perez");
  cliente.setSegundoApellido("Perez");
  cliente.setActivo(Boolean.TRUE);
  cliente.setBlob("prueba de encoding".getBytes());


  JsonbConfig config = new JsonbConfig()
               .withFormatting(true)
               .withNullValues(true)
               .withBinaryDataStrategy(BinaryDataStrategy.BASE_64)  // Encoding
               .withPropertyNamingStrategy(PropertyNamingStrategy.LOWER_CASE_WITH_DASHES);

  Jsonb jsonb = JsonbBuilder.create(config);
  String result = jsonb.toJson(cliente);

Y el JSON resultante sería:

{
    "activo": true,
    "blob": "cHJ1ZWJhIGRlIGVuY29kaW5n",
    "fecha-nacimiento": "01.01.1975",
    "nombre": "Juan",
    "primer-apellido": "Perez",
    "segundo-apellido": "Perez",
    "telefonos": null
}

Existen 3 estrategias de codificación:

  • BYTE Es la que se usa por omisión.

  • BASE_64

  • BASE_64_URL

Conclusión


La incorporación de JSON-B permite hacer uso de un API de alto nivel que facilita notoriamente el consumo y generación de mensajes JSON.

Su semejanza con el resto de APIs standard de JavaEE permite a los desarrolladores tener una curva de aprendizaje mínima y maximar su productividad.

Referencias