O artigo possui as seguintes seções:

  • Introdução
  • Serializando/Deserializando Objeto Simples (XML)
  • Serializando/Deserializando Objeto Composto (XML)
  • Trabalhando com JSON
  • Ignorando novo campo no XML: método unmarshall
  • Ignorando gravação de campo no XML: método marshall
  • Conversores Personalizados
  • Usando Anotação

Introdução

A biblioteca XStream foi criada visando facilitar a manipulação de XML em Java, através da serialização/deserialização de objetos usando representação em XML. Veremos nesse artigo como trabalhar com essa biblioteca.

Nota: Utilizaremos nos exemplos desse artigo a versão 1.4.4, que pode ser obtida aqui.

Serializando/Deserializando Objeto Simples

package br.com.devmedia.entity;

public class Livro {
    
    private int ano;
    private String titulo;
    private String isbn;

    public Livro() {
    }

    public Livro(int ano, String titulo, String isbn) {
        this.ano = ano;
        this.titulo = titulo;
        this.isbn = isbn;
    }

    public int getAno() {
        return ano;
    }

    public void setAno(int ano) {
        this.ano = ano;
    }

    public String getIsbn() {
        return isbn;
    }

    public void setIsbn(String isbn) {
        this.isbn = isbn;
    }
    
    public String getTitulo() {
        return titulo;
    }

    public void setTitulo(String titulo) {
        this.titulo = titulo;
    }
}
Listagem 1. Classe Livro
package br.com.devmedia.app;

import br.com.devmedia.entity.Livro;
import com.thoughtworks.xstream.XStream;

public class SerializacaoXML {
    
    public static void main(String[] args) {
        XStream xstream = new XStream();
        Livro livro = new Livro(2000, "Assim Falou Zaratustra", "123");
        String xml = xstream.toXML(livro);
        System.out.println("XML = ");
        System.out.println(xml);
    }
}
Listagem 2. Serializando objeto
Saída:
XML = 
2000
Assim Falou Zaratustra
123

Nota: É necessário adicionar as dependências xstream-1.4.4.jar, xmlpull-1.1.3.1.jar e xpp3_min-1.1.4c.jar no seu projeto. Esses jars estão dentro desse zip.

É relativamente simples gerar a representação XML de um objeto Java usando XStream. Note que o nó raiz do XML é <br.com.devmedia.entity.livro>, ou seja, o nome totalmente qualificado da classe. Para gerar um nome mais amigável, usaremos o método alias do XStream: xstream.alias("livro", Livro.class);</br.com.devmedia.entity.livro>

E assim a nova saída será:

2000
Assim Falou Zaratustra
123

Para converter o XML novamente num objeto, basta usar o método fromXML:

package br.com.devmedia.app;

import br.com.devmedia.entity.Livro;
import com.thoughtworks.xstream.XStream;

public class SerializaDeserializaXML {
    
    public static void main(String[] args) {        
        XStream xstream = new XStream();
        Livro livro = new Livro(2000, "Assim Falou Zaratustra", "123");
        // Serializando objeto
        xstream.alias("livro", Livro.class);
        String xml = xstream.toXML(livro);
        
        // Deserializando objeto
        Livro novoLivro = (Livro)xstream.fromXML(xml);

        System.out.println("Titulo = " + novoLivro.getTitulo());
        System.out.println("Ano = " + novoLivro.getAno());
        System.out.println("ISBN = " + novoLivro.getIsbn());
    }
}
Listagem 3. Serializando/Deserializando objeto
Titulo = Assim Falou Zaratustra
Ano = 2000
ISBN = 123
Saída

Serializando/Deserializando Objeto Composto

package br.com.devmedia.entity;

public class Livro2 {
    
    private int ano;
    private String titulo;
    private String isbn;
    private Editora editora;

    public Livro2() {
    }

    public Livro2(int ano, String titulo, String isbn, Editora editora) {
        this.ano = ano;
        this.titulo = titulo;
        this.isbn = isbn;
        this.editora = editora;
    }

    public Editora getEditora() {
        return editora;
    }

    public void setEditora(Editora editora) {
        this.editora = editora;
    }

    public int getAno() {
        return ano;
    }

    public void setAno(int ano) {
        this.ano = ano;
    }

    public String getIsbn() {
        return isbn;
    }

    public void setIsbn(String isbn) {
        this.isbn = isbn;
    }
    
    public String getTitulo() {
        return titulo;
    }

    public void setTitulo(String titulo) {
        this.titulo = titulo;
    }
}
Listagem 4. Classe Livro2 com atributo Editora
package br.com.devmedia.entity;

public class Editora {
    
    private String nome;
    private Endereco endereco;

    public Editora() {
    }

    public Editora(String nome, Endereco endereco) {
        this.nome = nome;
        this.endereco = endereco;
    }

    public Endereco getEndereco() {
        return endereco;
    }

    public void setEndereco(Endereco endereco) {
        this.endereco = endereco;
    }

    public String getNome() {
        return nome;
    }

    public void setNome(String nome) {
        this.nome = nome;
    }
}
Listagem 5. Classe Editora com atributo Endereco
package br.com.devmedia.entity;

public class Endereco {
   
    private int numero;
    private String logradouro;
    private String CEP;

    public Endereco() {
    }

    public Endereco(int numero, String logradouro, String CEP) {
        this.numero = numero;
        this.logradouro = logradouro;
        this.CEP = CEP;
    }

    public String getCEP() {
        return CEP;
    }

    public void setCEP(String CEP) {
        this.CEP = CEP;
    }

    public String getLogradouro() {
        return logradouro;
    }

    public void setLogradouro(String logradouro) {
        this.logradouro = logradouro;
    }

    public int getNumero() {
        return numero;
    }

    public void setNumero(int numero) {
        this.numero = numero;
    }
}
Listagem 6. Classe Endereco
package br.com.devmedia.app;

import br.com.devmedia.entity.Editora;
import br.com.devmedia.entity.Endereco;
import br.com.devmedia.entity.Livro2;
import com.thoughtworks.xstream.XStream;

public class SerializaDeserializaObjetoComposto {
    
    public static void main(String[] args) {
        XStream xstream = new XStream();
        Endereco endereco = new Endereco(100, "Wall Street", "XXXXXX-XXX");
        Editora editora = new Editora("Abril", endereco);        
        Livro2 livro = new Livro2(2000, "Assim Falou Zaratustra", "123", editora);
        xstream.alias("livro", Livro2.class);
        xstream.alias("editora", Editora.class);
        xstream.alias("endereco", Endereco.class);
        String xml = xstream.toXML(livro);
        System.out.println("XML = ");
        System.out.println(xml);        
        
        Livro2 novoLivro = (Livro2)xstream.fromXML(xml);
        System.out.println("Titulo = " + novoLivro.getTitulo());
        System.out.println("Ano = " + novoLivro.getAno());
        System.out.println("ISBN = " + novoLivro.getIsbn());
        System.out.println("Editora = " + novoLivro.getEditora().getNome());
        System.out.println("Endereco = " 
        + novoLivro.getEditora().getEndereco().getCEP());
        System.out.println("Logradouro = " 
        + novoLivro.getEditora().getEndereco().getLogradouro());
    }
}
Listagem 7. Serializando/Deserializando Objeto Livro2
XML = 

;  2000
Assim Falou Zaratustra
123

Abril

100
Wall Street
XXXXXX-XXX



Titulo = Assim Falou Zaratustra
Ano = 2000
ISBN = 123
Editora = Abril
Endereco = XXXXXX-XXX
Logradouro = Wall Street
Saída

A serialização/deserialização de objetos complexos é praticamente idêntica a de objetos simples.

Trabalhando com JSON

Ao invés de XML, podemos também usar JSON para a representação de objetos:

Nota: A dependência jettison-1.2.jar deve ser adicionada ao projeto.

package br.com.devmedia.app;

import br.com.devmedia.entity.Livro;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.json.JettisonMappedXmlDriver;

public class SerializaDeserializaJSON {
    
    public static void main(String[] args) {
        
        XStream xstream = new XStream(new JettisonMappedXmlDriver());
        Livro livro = new Livro(2000, "Assim Falou Zaratustra", "123");
        xstream.alias("livro", Livro.class);
        String json = xstream.toXML(livro);
        System.out.println("JSON = ");
        System.out.println(json);
        
        Livro novoLivro = (Livro)xstream.fromXML(json);
        System.out.println("Titulo = " + novoLivro.getTitulo());
        System.out.println("Ano = " + novoLivro.getAno());
        System.out.println("ISBN = " + novoLivro.getIsbn());
    }
}
Listagem 8. Usando JSON com Objeto Simples
JSON = 
{"livro":{"ano":2000,"titulo":"Assim Falou Zaratustra","isbn":123}}
  Titulo = Assim Falou Zaratustra
 Ano = 2000
 ISBN = 123
Saída

Para trabalhar com JSON, basta passar para o construtor da classe XStream o driver JettisonMappedXmlDriver(). Se nenhum driver for definido, o default será XppDriver(). Podemos definir outros drivers, como DomDriver, StaxDriver, etc. Todos os drivers são subclasses (direta ou indiretamente) da classe AbstractDriver, que implementa a interface HierarchicalStreamDriver.

A versão JSON da classe Livro2 seria:

{"livro":{"ano":2000,"titulo":"Assim Falou Zaratustra","isbn":123,"editora":  
{"nome":"Abril","endereco":{"numero":100,"logradouro":"Wall Street","CEP":"XXXXXX-XXX"}}}}
Listagem 9. JSON de Livro2

Ignorando novo campo no XML: método unmarshall

Imagine que tenhamos gravado a representação XML de um objeto Livro num arquivo .xml:

2000
Assim Falou Zaratustra
123

E que por algum motivo, adicionamos um novo atributo na classe Livro, no caso, o campo valor (double). O que acontece se tentarmos deserializar o XML, transformando-o num objeto?

Nesse caso, a deserialização irá funcionar e o campo valor do objeto livro terá valor 0.0.

Agora, e se adicionarmos uma tag no XML e não adicionarmos um campo valor para a classe Livro?

package br.com.devmedia.app;

import br.com.devmedia.entity.Livro;
import com.thoughtworks.xstream.XStream;

public class DeserializacaoNovoCampo {
    
    public static void main(String[] args) {
        
        StringBuilder xml = new StringBuilder();
        xml.append("<livro><ano>2000</ano>");
        xml.append("<titulo>Assim Falou Zaratustra</titulo>");
        xml.append("<isbn>123</isbn>");
        xml.append("<valor>10.0</valor>");
        xml.append("</livro>");
        
        XStream xstream = new XStream();
        xstream.alias("livro", Livro.class);
        
        Livro novoLivro = (Livro)xstream.fromXML(xml.toString());
        System.out.println("Titulo = " + novoLivro.getTitulo());
        System.out.println("Ano = " + novoLivro.getAno());
        System.out.println("ISBN = " + novoLivro.getIsbn());
    }
}
Listagem 10. DeserializacaoNovoCampo

No caso, será lançada a seguinte exceção:

Exception in thread "main" 
com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter
$UnknownFieldException: No such field br.com.devmedia.entity.Livro.valor.

Como ignorar o novo campo no XML sem alterar a classe Livro? Para isso devemos usar o conceito de conversor do XStream:

package br.com.devmedia.converter;

import br.com.devmedia.entity.Livro;
import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;

public class LivroConverter implements Converter {

    @Override
    public boolean canConvert(Class clazz) {
        return clazz.equals(Livro.class);
    }

    @Override
    public void marshal(Object value, HierarchicalStreamWriter writer,
            MarshallingContext context) {
    }

    @Override
    public Object unmarshal(HierarchicalStreamReader reader,
            UnmarshallingContext context) {

        Livro livro = new Livro();
        while (reader.hasMoreChildren()) {
            reader.moveDown();
            if ("ano".equals(reader.getNodeName())) {
                Integer ano = (Integer)context.convertAnother(livro, Integer.class);
                livro.setAno(ano);
            } else if ("titulo".equals(reader.getNodeName())) {
                String titulo = (String) context.convertAnother(livro, String.class);
                livro.setTitulo(titulo);
            } else if ("isbn".equals(reader.getNodeName())) {
                String isbn = (String) context.convertAnother(livro, String.class);
                livro.setIsbn(isbn);
            }            
            reader.moveUp();
        }
        
        return livro;
    }
}
Listagem 11. Conversor Personalizado

Um conversor personalizado para a classe Livro foi criado. Ele irá processar todos os atributos do XML, menos a tag.

Para criar um conversor, basta criar uma classe que implementa a interface Converter e implementar seus três métodos:

  • public boolean canConvert(Class clazz): Indica as classes que podemos manipular.
  • public void marshal: Faz a conversão do objeto em XML.
  • public Object unmarshal: Faz a conversão do XML em objeto.

Para o método canConvert, estamos indicando que apenas instâncias da classe Livro serão manipuladas (ou seja, excluindo até mesmos possíveis subclasses de Livro).

No exemplo, temos um XML alterado (tag adicionada). Queremos transformar esse XML em um objeto Livro, ignorando essa nova tag. Por isso, dentro do while, processamos todas as tag, ignorando apenas a tag .

Uma vez criado o conversor personalizado, devemos registrá-lo através do método registerConverter da classe XStream:

package br.com.devmedia.app;

import br.com.devmedia.converter.LivroConverter;
import br.com.devmedia.entity.Livro;
import com.thoughtworks.xstream.XStream;

public class DeserializacaoNovoCampo2 {
    
    public static void main(String[] args) {
        
        StringBuilder xml = new StringBuilder();
        xml.append("<livro><ano>2000</ano>");
        xml.append("<titulo>Assim Falou Zaratustra</titulo>");
        xml.append("<isbn>123</isbn>");
        xml.append("<valor>10.0</valor>");
        xml.append("</livro>");
        
        XStream xstream = new XStream();
        xstream.alias("livro", Livro.class);

        // Registrando conversor personalizado
        xstream.registerConverter(new LivroConverter());
        
        Livro novoLivro = (Livro)xstream.fromXML(xml.toString());
        System.out.println("Titulo = " + novoLivro.getTitulo());
        System.out.println("Ano = " + novoLivro.getAno());
        System.out.println("ISBN = " + novoLivro.getIsbn());
    }
}
Listagem 12. Convertendo XML alterado
Titulo = Assim Falou Zaratustra
Ano = 2000
ISBN = 123
Saída

Nessa nova versão, usando o LivroConverter, a tag do XML será ignorada, e a exceção não será lançada.

Ignorando gravação de campo no XML: método marshall

Da mesma forma que podemos ignorar uma tag nova no XML, podemos também personalizar a geração do XML, ignorando a gravação de certos campos. Imagine que não seja necessário gravar o campo isbn no XML. Para executar essa tarefa, criaremos um conversor personalizado:

package br.com.devmedia.converter;

import br.com.devmedia.entity.Livro;
import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;

public class LivroConverter2 implements Converter {

    @Override
    public boolean canConvert(Class clazz) {
        return clazz.equals(Livro.class);
    }

    @Override
    public void marshal(Object value, HierarchicalStreamWriter writer,
            MarshallingContext context) {
        
        Livro livro = (Livro)value;
        writer.startNode("ano");
        context.convertAnother(livro.getAno());
        writer.endNode();            

        writer.startNode("titulo");
        context.convertAnother(livro.getTitulo());
        writer.endNode();        
    }

    @Override
    public Object unmarshal(HierarchicalStreamReader reader,
            UnmarshallingContext context) {
        return null;
    }
}
Listagem 13. LivroConverter2

Nesse conversor, estamos atuando sobre o método marshall, que transforma o objeto em XML. Gravamos os 2 campos: ano e titulo, e ignoramos o campo isbn.

package br.com.devmedia.app;

import br.com.devmedia.converter.LivroConverter2;
import br.com.devmedia.entity.Livro;
import com.thoughtworks.xstream.XStream;

public class SerializacaoPersonalizadaXML {
    
    public static void main(String[] args) {
        
        Livro livro = new Livro(2000, "Assim Falou Zaratustra", "123");

        XStream xstream = new XStream();
        
        // Registrando novo conversor
        xstream.registerConverter(new LivroConverter2());        
        // Registrando alias
        xstream.alias("livro", Livro.class);
        // Gerando XML
        String xml = xstream.toXML(livro);
        
        System.out.println("XML = ");
        System.out.println(xml);
    }
}
Listagem 14. Ignorando campo na geração do XML
XML = 

2000
Assim Falou Zaratustra
Saída

Notem que a tag não foi gravada no XML.

Conversores Personalizados

Nos dois exemplos acima, ficou claro o potencial de uso de um conversor personalizado. Para criar um conversor, basta ter uma classe que implementa a interface Converter e codificar seus 3 métodos.

O XStream possui vários tipos de conversores pré-definidos. Clique Aqui para ver a lista completa. Para mais detalhes sobre como criar conversores personalizados.

Usando Anotação

package br.com.devmedia.entity;

import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamOmitField;

@XStreamAlias("livro")
public class Livro4 {
    
    private int ano;
    private String titulo;
    private String isbn;
    
    @XStreamOmitField
    private double valor;

    public Livro4() {
    }

    public Livro4(int ano, String titulo, String isbn, double valor) {
        this.ano = ano;
        this.titulo = titulo;
        this.isbn = isbn;
        this.valor = valor;
    }

    public int getAno() {
        return ano;
    }

    public void setAno(int ano) {
        this.ano = ano;
    }

    public String getIsbn() {
        return isbn;
    }

    public void setIsbn(String isbn) {
        this.isbn = isbn;
    }

    public String getTitulo() {
        return titulo;
    }

    public void setTitulo(String titulo) {
        this.titulo = titulo;
    }

    public double getValor() {
        return valor;
    }

    public void setValor(double valor) {
        this.valor = valor;
    }    
}
Listagem 14. Classe Livro4

Na classe, usamos a anotação @XstreamAlias("livro") para definir o alias. E utilizamos a anotação @XstreamOmitField para ignorar a gravação do campo valor no XML. Ao usar anotações, devemos informar esse fato ao XStream, através do método processAnnotations:

package br.com.devmedia.app;

import br.com.devmedia.entity.Livro4;
import com.thoughtworks.xstream.XStream;

public class SerializacaoAnotacaoXML {
    
    public static void main(String[] args) {
        
        XStream xstream = new XStream();
        xstream.processAnnotations(Livro4.class);
        Livro4 livro = new Livro4(2000, "Assim Falou Zaratustra", "123", 10.0);
        String xml = xstream.toXML(livro);
        System.out.println("XML = ");
        System.out.println(xml);
    }
}
Listagem 15. Usando anotações
XML = 

2000
Assim Falou Zaratustra
123
Saída

O campo valor foi ignorado na hora de gerar o XML.

Conclusão

A biblioteca XStream torna fácil a manipulação de XML dentro de aplicações Java. O ganho de produtividade é alto, portanto, sempre que possível, use o XStream para manipular XML (e até JSON) dentro de suas aplicações.

Para além do básico, temos os conversores personalizados, que ajudam a tratar representações mais complexas de tags ou valores dentro do XML.

Nota: No artigo Conhecendo o Apache Mina Framework, foi utilizado XStream para trafegar objetos de um cliente TCP/IP para um servidor, ou seja, criou-se um mecanismo próprio de serialização de objetos baseado em TCP e XML. O cliente converte um objeto POJO em XML, o envia pela rede, e o servidor transforma o XML em um objeto POJO novamente.