Jackson

Introducció

Quan creem aplicacions Java, sovint necessitem convertir objectes a formats de dades que es puguen emmagatzemar, enviar per la xarxa o intercanviar amb altres sistemes. A això ho anomenem serialització (d’objectes a formats com JSON o XML) i deserialització (del format a objectes Java).

Jackson és una de les llibreries més utilitzades per la seua simplicitat, velocitat i versatilitat. Ens permet serialitzar i deserialitzar amb molt poca configuració.

Nota: En Java tradicional també podem serialitzar objectes amb la interfície java.io.Serializable i fluxos d’objectes (ObjectOutputStream, ObjectInputStream). Jackson, en canvi, treballa amb formats textuals o binaris interoperables, com JSON, XML, YAML o CSV.


Què és Jackson?

Jackson és una biblioteca de processament de dades per a Java. La seua peça central és la classe ObjectMapper, que permet convertir objectes Java en formats com JSON, XML o CSV, i viceversa.

Jackson destaca per:

  • Serialitzar objectes Java a formats JSON, XML, CSV, etc.
  • Deserialitzar aquests formats a objectes Java.
  • Utilitzar anotacions per controlar el procés de conversió.

Formats compatibles

  • JSON: jackson-databind (ObjectMapper)
  • XML: jackson-dataformat-xml (XmlMapper)
  • YAML: jackson-dataformat-yaml
  • CSV: jackson-dataformat-csv
  • Altres: jackson-dataformat-cbor, jackson-dataformat-smile

Alternatives a Jackson

  • Gson (Google)
  • Moshi (Square)
  • JAXB (Java per a XML)
  • XStream (XML)

POJOs i DTOs

En el desenvolupament d’aplicacions Java, sovint utilitzem classes simples per representar dades. Són els POJOs (Plain Old Java Objects) i els DTOs (Data Transfer Objects).

POJO

Un POJO és una classe Java senzilla que segueix les convencions estàndard: no hereta de cap classe especial ni implementa cap interfície concreta. El seu objectiu és representar dades de manera estructurada.

Característiques principals:

  • Camps privats amb mètodes getters i setters.
  • Sense dependències de frameworks.
  • Fàcil de serialitzar i deserialitzar.

Exemple:

public class Producte {
    private String nom;
    private double preu;
    private String descripcio;

    public Producte() {}

    public Producte(String nom, double preu, String descripcio) {
        this.nom = nom;
        this.preu = preu;
        this.descripcio = descripcio;
    }

    public String getNom() { return nom; }
    public void setNom(String nom) { this.nom = nom; }

    public double getPreu() { return preu; }
    public void setPreu(double preu) { this.preu = preu; }

    public String getDescripcio() { return descripcio; }
    public void setDescripcio(String descripcio) { this.descripcio = descripcio; }

    @Override
    public String toString() {
        return "Producte [nom=" + nom + ", preu=" + preu + ", descripcio=" + descripcio + "]";
    }
}

DTO

Un DTO és un tipus especial de POJO que s’utilitza per transferir dades entre capes d’una aplicació. Sovint conté només la informació necessària per a una operació concreta.

Exemple:

public class ProducteDTO {
    private String nom;
    private double preu;

    public ProducteDTO() {}
    public ProducteDTO(String nom, double preu) {
        this.nom = nom;
        this.preu = preu;
    }

    public String getNom() { return nom; }
    public void setNom(String nom) { this.nom = nom; }

    public double getPreu() { return preu; }
    public void setPreu(double preu) { this.preu = preu; }
}

Resum:

Aspecte POJO DTO
Propòsit Representar dades del domini Transferir dades entre capes
Contingut Camps i mètodes senzills Només dades essencials
Lògica de negoci Pot tindre’n No en té
Exemple Producte ProducteDTO

Anotacions en Java

Les anotacions són metadades que descriuen el codi. En Java, s’utilitzen per a documentar, validar o configurar el comportament d’una classe, mètode o camp.

Exemples comuns

  • @Override: indica que un mètode sobreescriu un altre.
  • @Deprecated: marca un element com a obsolet.
  • @SuppressWarnings: suprimeix avisos del compilador.
  • @FunctionalInterface: marca una interfície amb un únic mètode abstracte.

Anotacions en Jackson

Jackson utilitza anotacions per definir com es mapeja una classe Java a un format com JSON o XML.

Anotacions JSON més habituals

Anotació Funció
@JsonProperty("nom_json") Canvia el nom d’un camp al JSON
@JsonIgnore Exclou un camp del JSON
@JsonInclude(Include.NON_NULL) Omet els camps nuls
@JsonFormat(pattern="dd-MM-yyyy") Dona format a les dates

Exemple complet amb JSON i XML

Classe Atraccio

import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.dataformat.xml.annotation.*;
import java.time.LocalDate;

@JsonInclude(JsonInclude.Include.NON_NULL)
@JacksonXmlRootElement(localName = "atraccio")
public class Atraccio {

    @JsonProperty("nom_atraccio")
    @JacksonXmlProperty(localName = "nom_atraccio")
    private String nom;

    @JacksonXmlProperty(isAttribute = true)
    private boolean activa;

    private double alturaMinima;

    @JsonFormat(pattern = "dd-MM-yyyy")
    private LocalDate inauguracio;

    public Atraccio() {}

    public Atraccio(String nom, boolean activa, double alturaMinima, LocalDate inauguracio) {
        this.nom = nom;
        this.activa = activa;
        this.alturaMinima = alturaMinima;
        this.inauguracio = inauguracio;
    }

    // Getters i setters
    public String getNom() { return nom; }
    public void setNom(String nom) { this.nom = nom; }

    public boolean isActiva() { return activa; }
    public void setActiva(boolean activa) { this.activa = activa; }

    public double getAlturaMinima() { return alturaMinima; }
    public void setAlturaMinima(double alturaMinima) { this.alturaMinima = alturaMinima; }

    public LocalDate getInauguracio() { return inauguracio; }
    public void setInauguracio(LocalDate inauguracio) { this.inauguracio = inauguracio; }
}

Programa ExempleJackson.java

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import java.time.LocalDate;

public class ExempleJackson {
    public static void main(String[] args) {
        try {
            Atraccio atr = new Atraccio("Tirolina Gigant", true, 1.40, LocalDate.of(2022, 6, 10));

            ObjectMapper jsonMapper = new ObjectMapper();
            jsonMapper.registerModule(new JavaTimeModule());
            jsonMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

            String json = jsonMapper.writerWithDefaultPrettyPrinter().writeValueAsString(atr);
            System.out.println("JSON generat:");
            System.out.println(json);

            XmlMapper xmlMapper = new XmlMapper();
            xmlMapper.registerModule(new JavaTimeModule());
            xmlMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

            String xml = xmlMapper.writerWithDefaultPrettyPrinter().writeValueAsString(atr);
            System.out.println("\nXML generat:");
            System.out.println(xml);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Resultat

JSON:

{
  "nom_atraccio": "Tirolina Gigant",
  "activa": true,
  "alturaMinima": 1.4,
  "inauguracio": "10-06-2022"
}

XML:

<atraccio activa="true">
  <nom_atraccio>Tirolina Gigant</nom_atraccio>
  <alturaMinima>1.4</alturaMinima>
  <inauguracio>10-06-2022</inauguracio>
</atraccio>

En resum

  • Jackson és una llibreria molt potent per a serialitzar i deserialitzar objectes Java.
  • És compatible amb diversos formats (JSON, XML, YAML, CSV…).
  • L’ús de POJOs i anotacions facilita molt el treball amb dades estructurades.
  • Amb ObjectMapper i XmlMapper, podem treballar amb els dos formats principals (JSON i XML) de manera pràcticament idèntica.

Creació de POJOs a partir de JSON i XML

Quan treballem amb formats de dades com JSON o XML, moltes voltes necessitarem convertir aquestes estructures a classes Java. Això ens permet accedir a la informació amb facilitat, validar-la i processar-la dins del nostre programa.

A aquestes classes senzilles les anomenem POJOs o DTOs. Jackson i altres llibreries poden llegir o escriure dades a partir d’elles, però abans cal saber construir-les correctament.

eEr a dissenyar classes Java a partir de documents JSON o XML hem de centrar-nos en:

  • Reconéixer els tipus de dades.
  • Crear classes per a objectes i llistes.
  • Mantindre una correspondència clara entre la informació i el codi.

1. Analitzar l’estructura del document

Abans de crear les classes, cal observar l’estructura del document JSON o XML i identificar els seus components:

Estructura Significat Exemple Representació Java
Clau/etiqueta amb valor simple Dada senzilla "nom": "Anna" Atribut String nom;
Objecte dins d’un altre Composició "adreca": {"carrer": "Major"} Classe Adreca com a camp
Llista o array Repetició "llibres": [ {...}, {...} ] List<Llibre>
Valor numèric Quantitat o codi "edat": 23 int edat;
Valor booleà Estat o condició "actiu": true boolean actiu;

2. Regles generals per crear POJOs

  • Cada objecte → una classe

Si trobes un bloc amb claus ({} en JSON o <Element> en XML), crea una classe Java amb el mateix nom o un nom equivalent.

Exemple JSON:

{
  "nom": "Joan",
  "edat": 25
}

Classe Java:

public class Persona {
    private String nom;
    private int edat;

    public Persona() {}

    public String getNom() { return nom; }
    public void setNom(String nom) { this.nom = nom; }

    public int getEdat() { return edat; }
    public void setEdat(int edat) { this.edat = edat; }
}

  • Cada camp → atribut privat

Els noms de camp solen coincidir amb els del JSON/XML, però seguim les convencions Java:

  • camelCase per als atributs (dataNaixement, codiPostal).
  • Tipus Java segons el valor.
Tipus de valor Tipus Java recomanat
Text String
Enter int o Integer
Real double o BigDecimal
Booleà boolean o Boolean
Data LocalDate
Llista List<T>
Objecte intern Classe pròpia

  • Constructor buit i mètodes d’accés

Totes les classes que vulguem deserialitzar necessiten:

  • Un constructor buit (sense paràmetres).
  • Getters i setters públics per a tots els camps.
public class Producte {
    private String nom;
    private double preu;

    public Producte() {} // Constructor buit

    public String getNom() { return nom; }
    public void setNom(String nom) { this.nom = nom; }

    public double getPreu() { return preu; }
    public void setPreu(double preu) { this.preu = preu; }
}

3. Objectes anidats

Quan un element conté un altre objecte, creem una nova classe per a representar-lo.

  • Exemple JSON
{
  "nom": "Laura",
  "adreca": {
    "carrer": "Sant Pere",
    "ciutat": "Alcoi"
  }
}
  • POJOs corresponents
public class Persona {
    private String nom;
    private Adreca adreca;

    public Persona() {}
    public String getNom() { return nom; }
    public void setNom(String nom) { this.nom = nom; }

    public Adreca getAdreca() { return adreca; }
    public void setAdreca(Adreca adreca) { this.adreca = adreca; }
}

public class Adreca {
    private String carrer;
    private String ciutat;

    public Adreca() {}
    public String getCarrer() { return carrer; }
    public void setCarrer(String carrer) { this.carrer = carrer; }

    public String getCiutat() { return ciutat; }
    public void setCiutat(String ciutat) { this.ciutat = ciutat; }
}

4. Llistes i arrays

Si hi ha una repetició d’elements, utilitzem una llista (List<T>).

  • Exemple JSON
{
  "nom": "València",
  "monuments": [
    {"nom": "La Llotja"},
    {"nom": "El Micalet"}
  ]
}
  • Classes Java
import java.util.List;

public class Ciutat {
    private String nom;
    private List<Monument> monuments;

    public Ciutat() {}
    public String getNom() { return nom; }
    public void setNom(String nom) { this.nom = nom; }

    public List<Monument> getMonuments() { return monuments; }
    public void setMonuments(List<Monument> monuments) { this.monuments = monuments; }
}

public class Monument {
    private String nom;
    public Monument() {}
    public String getNom() { return nom; }
    public void setNom(String nom) { this.nom = nom; }
}

5. Correspondència entre JSON i XML

Les regles generals són les mateixes, però en XML sovint hi ha un element arrel que conté tots els altres.

Exemple XML

<Ciutats>
    <Ciutat>
        <Nom>València</Nom>
        <Poblacio>790201</Poblacio>
    </Ciutat>
    <Ciutat>
        <Nom>Sevilla</Nom>
        <Poblacio>688711</Poblacio>
    </Ciutat>
</Ciutats>
  • Classes Java equivalents
import java.util.List;

public class Ciutats {
    private List<Ciutat> ciutat;
    public Ciutats() {}
    public List<Ciutat> getCiutat() { return ciutat; }
    public void setCiutat(List<Ciutat> ciutat) { this.ciutat = ciutat; }
}

public class Ciutat {
    private String nom;
    private int poblacio;
    public Ciutat() {}
    public String getNom() { return nom; }
    public void setNom(String nom) { this.nom = nom; }
    public int getPoblacio() { return poblacio; }
    public void setPoblacio(int poblacio) { this.poblacio = poblacio; }
}

En XML és molt habitual tindre una classe contenidora (Ciutats) per agrupar diverses instàncies (Ciutat).


6. Guia pas a pas per dissenyar el model

  1. Llig el document complet. Identifica objectes, llistes i valors simples.

  2. Dibuixa un esquema o mapa. Marca cada objecte i les seues relacions.

  3. Assigna noms a les classes i camps.

    • Classes en majúscula inicial (Persona).
    • Camps en minúscula inicial (nom, edat).
  4. Decideix el tipus de cada camp.

    • Números → int o double.
    • Text → String.
    • Dates → LocalDate.
    • Objectes → altra classe.
    • Repeticions → List<T>.
  5. Crea els constructors i accessors. Recorda: constructor buit i getters/setters.

  6. Comprova la correspondència. L’estructura del POJO ha de reflectir exactament la del JSON/XML.


Exemples

Exemple 1: JSON senzill

Document

{
  "titol": "El Senyor dels Anells",
  "autor": "J.R.R. Tolkien",
  "any": 1954
}

Classe

public class Llibre {
    private String titol;
    private String autor;
    private int any;

    public Llibre() {}
    public String getTitol() { return titol; }
    public void setTitol(String titol) { this.titol = titol; }

    public String getAutor() { return autor; }
    public void setAutor(String autor) { this.autor = autor; }

    public int getAny() { return any; }
    public void setAny(int any) { this.any = any; }
}

Exemple 2: JSON amb objecte i llista

Document

{
  "nom": "Maria",
  "contacte": {
    "email": "maria@gmail.com",
    "telefon": "654321987"
  },
  "projectes": [
    {"nom": "Aplicació Mòbil"},
    {"nom": "Web Corporativa"}
  ]
}

Classes

import java.util.List;

public class Empleat {
    private String nom;
    private Contacte contacte;
    private List<Projecte> projectes;

    public Empleat() {}
    // getters i setters
}

public class Contacte {
    private String email;
    private String telefon;

    public Contacte() {}
    // getters i setters
}

public class Projecte {
    private String nom;
    public Projecte() {}
    
    // getters i setters
}

8. Exercicis

Exercici 1

A partir d’aquest JSON:

{
  "marca": "Toyota",
  "model": "Corolla",
  "any": 2020,
  "motor": {
    "cilindrada": 1800,
    "combustible": "gasolina"
  }
}

Crea les classes Cotxe i Motor amb els camps adequats, constructor buit i mètodes get/set.


Exercici 2

A partir d’aquest XML:

<Biblioteca>
  <Llibre>
    <Titol>Dune</Titol>
    <Autor>Frank Herbert</Autor>
  </Llibre>
  <Llibre>
    <Titol>Neuromàntic</Titol>
    <Autor>William Gibson</Autor>
  </Llibre>
</Biblioteca>

Crea les classes Biblioteca i Llibre perquè puguen representar aquestes dades.


Exercici 3

Observa el JSON següent:

{
  "ciutat": "Madrid",
  "temperatures": [19, 21, 18, 23, 20]
}

Crea la classe EstacioMeteorologica amb una llista d’enters per a les temperatures.


9. Taula Resum

Element JSON/XML Representació Java
Objecte Classe
Camp simple Atribut privat
Objecte dins d’un altre Classe anidada o composta
Llista o array List<T>
Valor numèric int, double
Valor booleà boolean
Text String
Data LocalDate
Constructor buit Obligatori
Getters i setters Obligatori

  • Cada objecte o element es tradueix a una classe Java.
  • Cada dada esdevé un camp privat amb els seus getters i setters.
  • Les llistes s’expressen amb List<T>.
  • Els objectes interns són altres POJOs.
  • L’estructura del JSON o XML ha de coincidir amb la del model Java.

Maven i Jackson

Maven és una eina de gestió de projectes que s’utilitza per a la construcció, dependències, i distribució de projectes Java.

Simplifica el procés de desenvolupament, ja que automatitza tasques com la compilació, la generació de paquets, i la gestió de dependències (o llibreries externes).

Estructura Bàsica d’un Projecte Maven

Un projecte Maven es compon de diversos elements clau, com ara:

  • pom.xml: Project Object Model (POM), és el fitxer de configuració principal de Maven que conté informació sobre el projecte, les dependències, els plugins, i altres configuracions.
  • src/main/java: Carpeta que conté el codi font de l’aplicació. Les classes principals del projecte es troben en aquesta carpeta.
  • src/main/resources: Carpeta que conté els recursos de l’aplicació, com el arxius JSON y XML, propietats, imatges…
  • src/test/java: Conté les classes de proves o tests.

alt text

Arxiu pom.xml

Cada projecte Maven té un arxiu pom.xml (Project Object Model) que defineix les dependencies, plugins i la configuració del projecte.

  • L’arxiu pom.xml està ubicat a la carpeta arrel del projecte.
  • Maven gestiona les dependències del projecte i les descarrega automàticament d’un repositori central.
  • Les dependències s’afegeixen a l’arxiu pom.xml dins de l’element .

  • El POM defineix les coordenades del projecte:
Element Significat Exemple
groupId Organització o domini invertit com.jaume
artifactId Nom del projecte o mòdul ProjecteMaven
version Versió de l’aplicació 1.0-SNAPSHOT


<dependencies>

    <dependency>
        <groupId>group_id</groupId>
        <artifactId>artifact_id</artifactId>
        <version>versió</version>
    </dependency>
    
    <dependency>
        <groupId>altre_group_id</groupId>
        <artifactId>altre_artifact_id</artifactId>
        <version>altre_versió</version>
    </dependency>

    <dependency>
        ...
    </dependency>

</dependencies>

On:

  • <dependencies>: contenidor de totes les dependències.
  • <dependency>: defineix una llibreria concreta a incloure.
  • <groupId>: grup/proveïdor de la dependència.
  • <artifactId>: nom concret del paquet dins del grup.
  • <version>: versió de la dependència a utilitzar.

Nota: totes les dependències es poden buscar a Maven Central (MVNRepository) i copiar al pom.xml. A IntelliJ, després de canviar el POM, fes Reload All Maven Projects (botó del panell Maven) per aplicar canvis.

Repositori Maven

  • Inicialment l’arxiu pom.xml te la següent estructura:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.jaume</groupId>
    <artifactId>ProjecteMaven</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
    <!-- Recomanat LTS  (17 o 21). Ajustem si volem usar 22 a l’aula. -->
        <maven.compiler.source>21</maven.compiler.source>
        <maven.compiler.target>21</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

</project>

Per a utilitzar Jackson en un projecte Maven, només cal afegir la dependència de Jackson al fitxer pom.xml. Això permetrà que Maven descarregue i incloga les llibreries de Jackson en el projecte.

En este cas, accedim al Repositori Maven triem la última versió i l’afegim al pom.xml ( en este cas Jackson-databind).

  • L’arxiu pom.xml quedaria:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.jaume</groupId>
    <artifactId>ProjecteMaven</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>21</maven.compiler.source>
        <maven.compiler.target>21</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>

        <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.18.0</version>
        </dependency>


    </dependencies>

</project>

Nota: En IntelliJ cada volta que modifiquem l’arxiu pom.xml hem d’actualitzar els canvis (Ctr+Mayus+O).

Dependencies de Jackson

Per a treballar amb Jackson, normalment necessitem afegir les següents dependències al nostre fitxer pom.xml:

<dependencies>
    <!-- Jackson Core -->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-core</artifactId>
        <version>2.18.0</version>
    </dependency>    
    <!-- Jackson Databind -->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.18.0</version>
    </dependency>    
    <!-- Jackson Annotations -->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-annotations</artifactId>
        <version>2.18.0</version>
    </dependency>    
</dependencies>

Aquestes dependències inclouen:

  • jackson-core: La biblioteca bàsica per al processament de JSON.
  • jackson-databind: Proporciona funcionalitats per a la serialització i deserialització d’objectes Java.
  • jackson-annotations: Conté les anotacions utilitzades per a configurar el comportament de Jackson.
  • Versió: Assegura’t d’utilitzar la versió més recent de Jackson per a garantir compatibilitat i accés a les últimes funcionalitats.
  • Altres formats:
    • XML: Si necessites treballar amb XML, afegeix la dependència jackson-dataformat-xml.
    • YAML: Per a YAML, utilitza jackson-dataformat-yaml.
    • CSV: Per a CSV, utilitza jackson-dataformat-csv.

Nota: jackson-databind ja inclou internament jackson-core i jackson-annotations, per tant no cal afegir-les per separat, excepte si es volen versions específiques.

  • Mòduls addicionals: Si utilitzes funcionalitats a necessitem treballar en altres formats, usarem biblioteques específiques com:
<!-- YAML -->
<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-yaml</artifactId>
    <version>2.18.0</version>
</dependency>

<!-- CSV -->
<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-csv</artifactId>
    <version>2.18.0</version>
</dependency>
  • O el suport per a Java 8 Date/Time API, com jackson-datatype-jsr310:
<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
    <version>2.18.0</version>
</dependency>

Que serveix per a serialitzar/deserialitzar objectes LocalDate, LocalDateTime, etc. Es a dir dades de tipus data/hora.