CodeGym /Cursos /JAVA 25 SELF /Configuração da serialização XML: adaptadores personaliza...

Configuração da serialização XML: adaptadores personalizados

JAVA 25 SELF
Nível 47 , Lição 4
Disponível

1. Uso de adaptadores (@XmlJavaTypeAdapter)

JAXB é realmente como uma transmissão automática: enquanto tudo é padrão — ele funciona perfeitamente, mas basta aparecer algo incomum e já será preciso intervenção manual. Imagine que você adicionou ao seu classe um campo do tipo LocalDate ou BigDecimal. JAXB ficará perdido — ele simplesmente não sabe como transformá-los em XML e de volta. Ou você quer que a data não pareça uma string longa como 2024-06-01T00:00:00, e sim no formato familiar 01.06.2024. Ou talvez você tenha um objeto que faça mais sentido ficar em um atributo, e não em um elemento, ou uma coleção com objetos aninhados que exigem uma representação especial.

Todas essas situações são resolvidas com adaptadores. Com eles você pode dizer ao JAXB exatamente como serializar e desserializar campos complexos, definir o formato necessário ou até pular dados desnecessários. É o “câmbio manual” que dá flexibilidade onde o automático já não dá conta.

O que é um adaptador?

Um adaptador é uma classe especial que diz ao JAXB: “Se encontrar este tipo, serialize assim e desserialize assado”. Em Java, o adaptador estende a classe abstrata javax.xml.bind.annotation.adapters.XmlAdapter<ValueType, BoundType>, em que:

  • ValueType — como os dados serão representados no XML (geralmente String, às vezes Integer, Long ou até outro objeto).
  • BoundType — o tipo real no seu classe Java (por exemplo, LocalDate).

Exemplo: serialização de um campo do tipo LocalDate

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

@XmlRootElement
public class Person {
    private String name;
    private LocalDate birthDate; // Aqui está o problema!

    public Person() {} // JAXB exige um construtor público sem parâmetros

    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 você tentar serializar esse objeto, JAXB lançará uma exceção:

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

Passo 1: Criar o adaptador

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

// Adaptador para conversão de 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 o objeto Java (LocalDate) em uma string para o XML.
  • unmarshal — converte a string do XML de volta em um objeto Java.

Passo 2: Anotar o campo ou o getter

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

Ou você pode colocar a anotação diretamente no campo:

@XmlJavaTypeAdapter(LocalDateAdapter.class)
private LocalDate birthDate;

Passo 3: Verificar o resultado

Agora, na serialização, o objeto ficará assim:

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

E no caminho inverso — ao ler do XML, a string "01.06.2024" se converterá em um objeto LocalDate.

2. Aplicando o adaptador a um campo, getter ou à classe inteira

O adaptador pode ser aplicado de maneiras diferentes.

A um campo ou getter específico: este é o caso mais comum.

@XmlJavaTypeAdapter(LocalDateAdapter.class)
private LocalDate birthDate;

À classe inteira: se você quiser que o JAXB sempre serialize um certo tipo por meio de um adaptador, pode anotar o próprio tipo:

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

Normalmente isso é feito para suas próprias classes, não para as padrão (a classe LocalDate não pode ser modificada).

Para coleções: é possível serializar, por exemplo, List<LocalDate> usando um adaptador que transforma a lista de datas em uma lista de strings.

3. Personalizando nomes de elementos e atributos

Às vezes os requisitos para a estrutura do XML são rígidos: por exemplo, o cliente quer que o campo se chame não <birthDate>, mas <birth_date>, ou que a data de nascimento seja um atributo, e não um elemento.

Alterando o nome do elemento

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

No XML agora ficará:

<birth_date>01.06.2024</birth_date>

Serializando como atributo

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

No XML:

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

Combinando com o adaptador

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

4. Casos práticos

Ignorando campos (@XmlTransient)

Às vezes é necessário que um campo não apareça no XML de forma alguma (por exemplo, um identificador interno, senha, dados temporários).

@XmlTransient
private String internalCode;

Esse campo será ignorado na serialização e na desserialização.

Formatação de números

Suponha que você tenha um campo com um valor monetário:

private BigDecimal balance;

JAXB não sabe serializar BigDecimal no formato que você precisa (por exemplo, com duas casas decimais, usando vírgula). Vamos escrever um adaptador:

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 então usamos:

@XmlJavaTypeAdapter(BigDecimalAdapter.class)
private BigDecimal balance;

Estruturas aninhadas

Se você tem objetos aninhados, por exemplo:

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

JAXB serializa objetos aninhados como elementos por padrão. Mas se você precisar que, por exemplo, city seja um atributo e street — um elemento, use as anotações:

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

5. Exemplo: configuração completa da serialização com adaptador

Vamos evoluir o aplicativo: agora temos a classe Person com data de nascimento 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;
    }

    // getters e setters...
}

O que obtivemos:

  • O nome é serializado como elemento <name>.
  • A data de nascimento é serializada como atributo <Person birth_date="01.06.2024">.
  • O saldo é serializado como elemento <balance>1234.56</balance>.
  • A senha não vai para o XML de forma alguma.

Arquivo XML:

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

6. Tratando coleções e objetos aninhados

JAXB sabe lidar com coleções, se elas estiverem corretamente anotadas. Por exemplo, se a pessoa tiver uma lista de endereços:

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

No XML isso ficará assim:

<addresses>
    <address city="Berlim">
        <street>Alexanderplatz, 1</street>
    </address>
    <address city="Limassol">
        <street>Anexartisias, 10</street>
    </address>
</addresses>

Se o tipo na coleção for não padrão (por exemplo, List<LocalDate>), é possível aplicar o adaptador ao elemento da coleção:

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

7. Exemplo: serialização e desserialização com adaptador

Serialização

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); // Irá imprimir o XML no console

Desserialização

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. Erros típicos ao configurar serialização e adaptadores

Erro nº 1: ausência de construtor público sem parâmetros. Se ele não existir, o JAXB não conseguirá criar o objeto na desserialização e lançará uma exceção.

Erro nº 2: aplicação incorreta do adaptador. Se você colocar @XmlJavaTypeAdapter no campo errado ou esquecer o getter/setter, o JAXB não saberá como serializar o tipo desejado.

Erro nº 3: incompatibilidade de formato na desserialização. Se no XML a data estiver num formato não suportado pelo seu adaptador (por exemplo, "2024-06-01" em vez de "01.06.2024"), o método unmarshal lançará uma exceção.

Erro nº 4: tentar serializar um tipo não suportado pelo JAXB sem adaptador. Exemplos típicos — LocalDate, BigDecimal, Map, tipos complexos definidos pelo usuário.

Erro nº 5: ignorar coleções aninhadas sem anotações. Sem @XmlElementWrapper, a coleção pode ser serializada de forma diferente do esperado, ou o JAXB pode nem conseguir ler corretamente o XML de volta.

Erro nº 6: aplicar o adaptador à coleção em vez de ao elemento. Se você quiser serializar os elementos da lista por meio de um adaptador, coloque a anotação no elemento, e não na própria coleção (por exemplo, @XmlJavaTypeAdapter no campo do elemento, ou no campo da lista indicando o tipo do elemento, como nos exemplos acima).

1
Pesquisa/teste
Serialização de XML, nível 47, lição 4
Indisponível
Serialização de XML
Serialização de XML
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION