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).
GO TO FULL VERSION