CodeGym /Corsi /JAVA 25 SELF /Configurazione della serializzazione XML: adapter persona...

Configurazione della serializzazione XML: adapter personalizzati

JAVA 25 SELF
Livello 47 , Lezione 4
Disponibile

1. Utilizzo degli adapter (@XmlJavaTypeAdapter)

JAXB è davvero simile a un cambio automatico: finché tutto è standard — funziona alla perfezione, ma appena incontra qualcosa di insolito, senza intervento manuale non se ne esce. Immagina di aggiungere alla tua classe un campo di tipo LocalDate o BigDecimal. JAXB si smarrisce — semplicemente non sa come trasformarli in XML e viceversa. Oppure vuoi che la data non appaia come la lunga stringa 2024-06-01T00:00:00, ma nel formato abituale 01.06.2024. O magari hai un oggetto che ha più senso memorizzare come attributo invece che come elemento, o una collezione con oggetti annidati che richiede una rappresentazione particolare.

Tutte queste situazioni si risolvono attraverso gli adapter. Con il loro aiuto puoi indicare a JAXB in che modo serializzare e deserializzare i campi complessi, impostare il formato desiderato o persino saltare i dati non necessari. Questo è il «cambio manuale» che dà flessibilità laddove l’automatico non basta.

Che cos’è un adapter?

Un adapter è una classe speciale che dice a JAXB: «Se incontri questo tipo, serializzalo così e deserializzalo cosà». In Java un adapter implementa la classe astratta javax.xml.bind.annotation.adapters.XmlAdapter<ValueType, BoundType>, dove:

  • ValueType — come i dati saranno rappresentati in XML (di solito String, a volte Integer, Long o anche un altro oggetto).
  • BoundType — il tipo reale nella tua classe Java (ad esempio, LocalDate).

Esempio: serializzazione di un campo di tipo LocalDate

import java.time.LocalDate;
import javax.xml.bind.annotation.*;

@XmlRootElement
public class Person {
    private String name;
    private LocalDate birthDate; // Qui c'è un problema!

    public Person() {} // JAXB richiede un costruttore pubblico senza argomenti

    public Person(String name, LocalDate birthDate) {
        this.name = name;
        this.birthDate = birthDate;
    }

    @XmlElement
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }

    @XmlElement
    public LocalDate getBirthDate() { return birthDate; }
    public void setBirthDate(LocalDate birthDate) { this.birthDate = birthDate; }
}

Se provi a serializzare un oggetto del genere, JAXB lancerà un’eccezione:

javax.xml.bind.JAXBException: class java.time.LocalDate nor any of its super class is known to this context.

Passo 1: creiamo l’adapter

import javax.xml.bind.annotation.adapters.XmlAdapter;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

// Adapter per la conversione LocalDate <-> String
public class LocalDateAdapter extends XmlAdapter<String, LocalDate> {
    private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("dd.MM.yyyy");

    @Override
    public LocalDate unmarshal(String v) throws Exception {
        return (v == null || v.isEmpty()) ? null : LocalDate.parse(v, FORMATTER);
    }

    @Override
    public String marshal(LocalDate v) throws Exception {
        return (v == null) ? null : v.format(FORMATTER);
    }
}
  • marshal — converte l’oggetto Java (LocalDate) in una stringa per l’XML.
  • unmarshal — converte la stringa dall’XML di nuovo in un oggetto Java.

Passo 2: annotiamo il campo o il getter

@XmlJavaTypeAdapter(LocalDateAdapter.class)
public LocalDate getBirthDate() { return birthDate; }

Oppure si può mettere l’annotazione direttamente sul campo:

@XmlJavaTypeAdapter(LocalDateAdapter.class)
private LocalDate birthDate;

Passo 3: verifichiamo il risultato

Ora, durante la serializzazione, l’oggetto apparirà così:

<Person>
    <name>Ivan</name>
    <birthDate>01.06.2024</birthDate>
</Person>

E al contrario — leggendo dall’XML, la stringa "01.06.2024" verrà convertita in un oggetto LocalDate.

2. Applicare l’adapter a un campo, a un getter o a un’intera classe

L’adapter si può applicare in modi diversi.

A un singolo campo o getter: è il caso più frequente.

@XmlJavaTypeAdapter(LocalDateAdapter.class)
private LocalDate birthDate;

All’intera classe: se vuoi che JAXB serializzi sempre un certo tipo tramite un adapter, puoi annotare direttamente la classe:

@XmlJavaTypeAdapter(LocalDateAdapter.class)
public class LocalDate { ... }

Di solito si fa per le proprie classi, non per quelle standard (la classe LocalDate non può essere modificata).

Alle collezioni: puoi serializzare, ad esempio, List<LocalDate> tramite un adapter che trasforma l’elenco di date in un elenco di stringhe.

3. Personalizzazione dei nomi di elementi e attributi

A volte i requisiti per la struttura XML sono rigidi: ad esempio, il committente vuole che il campo si chiami non <birthDate>, ma <birth_date>, oppure che la data di nascita sia un attributo invece di un elemento.

Modifica del nome dell’elemento

@XmlElement(name = "birth_date")
public LocalDate getBirthDate() { return birthDate; }

Nell’XML ora ci sarà:

<birth_date>01.06.2024</birth_date>

Serializzazione come attributo

@XmlAttribute(name = "birth_date")
public LocalDate getBirthDate() { return birthDate; }

Nell’XML:

<Person birth_date="01.06.2024">
    <name>Ivan</name>
</Person>

Combinazione con l’adapter

@XmlAttribute(name = "birth_date")
@XmlJavaTypeAdapter(LocalDateAdapter.class)
public LocalDate getBirthDate() { return birthDate; }

4. Casi pratici

Esclusione di campi (@XmlTransient)

A volte è necessario che un campo non finisca affatto nell’XML (ad esempio, un identificatore interno, una password, dati temporanei).

@XmlTransient
private String internalCode;

Un campo del genere verrà ignorato durante serializzazione e deserializzazione.

Formattazione dei numeri

Supponiamo che tu abbia un campo con un importo monetario:

private BigDecimal balance;

JAXB non sa serializzare BigDecimal nel formato che ti serve (per esempio con due decimali, con la virgola). Scriviamo un adapter:

import javax.xml.bind.annotation.adapters.XmlAdapter;
import java.math.BigDecimal;

public class BigDecimalAdapter extends XmlAdapter<String, BigDecimal> {
    @Override
    public BigDecimal unmarshal(String v) throws Exception {
        return (v == null || v.isEmpty()) ? null : new BigDecimal(v.replace(",", "."));
    }

    @Override
    public String marshal(BigDecimal v) throws Exception {
        return (v == null) ? null : String.format("%.2f", v);
    }
}

E lo usiamo:

@XmlJavaTypeAdapter(BigDecimalAdapter.class)
private BigDecimal balance;

Strutture annidate

Se hai oggetti annidati, ad esempio:

public class Address {
    private String city;
    private String street;
    // ...
}

JAXB serializza automaticamente gli oggetti annidati come elementi. Ma se ti serve, ad esempio, che city sia un attributo e street — un elemento, usa le annotazioni:

public class Address {
    @XmlAttribute
    private String city;
    @XmlElement
    private String street;
}

5. Esempio: configurazione completa della serializzazione con un adapter

Facciamo evolvere l’applicazione: ora abbiamo la classe Person con data di nascita e saldo.

import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import java.math.BigDecimal;
import java.time.LocalDate;

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Person {
    @XmlElement
    private String name;

    @XmlAttribute(name = "birth_date")
    @XmlJavaTypeAdapter(LocalDateAdapter.class)
    private LocalDate birthDate;

    @XmlElement
    @XmlJavaTypeAdapter(BigDecimalAdapter.class)
    private BigDecimal balance;

    @XmlTransient
    private String password;

    public Person() {}

    public Person(String name, LocalDate birthDate, BigDecimal balance, String password) {
        this.name = name;
        this.birthDate = birthDate;
        this.balance = balance;
        this.password = password;
    }

    // getter e setter...
}

Cosa abbiamo ottenuto:

  • Il nome viene serializzato come elemento <name>.
  • La data di nascita viene serializzata come attributo <Person birth_date="01.06.2024">.
  • Il saldo viene serializzato come elemento <balance>1234.56</balance>.
  • La password non finisce affatto nell’XML.

File XML:

<Person birth_date="01.06.2024">
    <name>Ivan</name>
    <balance>1234.56</balance>
</Person>

6. Gestione di collezioni e oggetti annidati

JAXB sa lavorare con le collezioni se sono annotate correttamente. Ad esempio, se una persona ha un elenco di indirizzi:

@XmlElementWrapper(name = "addresses")
@XmlElement(name = "address")
private List<Address> addresses;

Nell’XML questo apparirà così:

<addresses>
    <address city="Berlin">
        <street>Aleksandrplatts, 1</street>
    </address>
    <address city="Limassol">
        <street>Anexartisias, 10</street>
    </address>
</addresses>

Se il tipo nella collezione non è standard (ad esempio, List<LocalDate>), puoi applicare un adapter all’elemento della collezione:

@XmlElementWrapper(name = "dates")
@XmlElement(name = "date")
@XmlJavaTypeAdapter(LocalDateAdapter.class)
private List<LocalDate> importantDates;

7. Esempio: serializzazione e deserializzazione con un adapter

Serializzazione

Person person = new Person(
    "Ivan",
    LocalDate.of(1990, 6, 1),
    new BigDecimal("1234.56"),
    "secretPassword"
);

JAXBContext context = JAXBContext.newInstance(Person.class);
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);

marshaller.marshal(person, System.out); // Stamperà l'XML sulla console

Deserializzazione

String xml = """
    <Person birth_date="01.06.1990">
        <name>Ivan</name>
        <balance>1234.56</balance>
    </Person>
    """;

JAXBContext context = JAXBContext.newInstance(Person.class);
Unmarshaller unmarshaller = context.createUnmarshaller();

Person person = (Person) unmarshaller.unmarshal(new StringReader(xml));
System.out.println(person.getName() + " " + person.getBirthDate() + " " + person.getBalance());

8. Errori tipici nella configurazione della serializzazione e degli adapter

Errore n. 1: mancanza di un costruttore pubblico senza argomenti. Se manca, JAXB non riuscirà a creare l’oggetto in deserializzazione e lancerà un’eccezione.

Errore n. 2: applicazione errata dell’adapter. Se metti @XmlJavaTypeAdapter sul campo sbagliato o dimentichi getter/setter, JAXB non saprà come serializzare il tipo desiderato.

Errore n. 3: mismatch del formato in deserializzazione. Se nell’XML la data è scritta in un formato non supportato dal tuo adapter (ad esempio, "2024-06-01" invece di "01.06.2024"), il metodo unmarshal lancerà un’eccezione.

Errore n. 4: tentativo di serializzare un tipo non supportato da JAXB senza adapter. Esempi tipici — LocalDate, BigDecimal, Map, tipi complessi personalizzati.

Errore n. 5: ignorare le collezioni annidate senza annotazioni. Senza @XmlElementWrapper la collezione potrebbe non essere serializzata come previsto, o JAXB potrebbe non riuscire a leggere correttamente l’XML al ritorno.

Errore n. 6: applicare l’adapter alla collezione invece che all’elemento. Se vuoi serializzare gli elementi della lista tramite un adapter, metti l’annotazione sull’elemento, non sulla collezione stessa (ad esempio, @XmlJavaTypeAdapter sopra il campo dell’elemento, oppure sopra il campo della lista specificando il tipo dell’elemento, come negli esempi sopra).

1
Sondaggio/quiz
Serializzazione XML, livello 47, lezione 4
Non disponibile
Serializzazione XML
Serializzazione XML
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION