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;
}
}
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);
}
}
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());
}
}
Titulo = Assim Falou Zaratustra
Ano = 2000
ISBN = 123
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;
}
}
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;
}
}
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;
}
}
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());
}
}
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
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());
}
}
JSON =
{"livro":{"ano":2000,"titulo":"Assim Falou Zaratustra","isbn":123}}
Titulo = Assim Falou Zaratustra
Ano = 2000
ISBN = 123
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"}}}}
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());
}
}
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;
}
}
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());
}
}
Titulo = Assim Falou Zaratustra
Ano = 2000
ISBN = 123
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;
}
}
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);
}
}
XML =
2000
Assim Falou Zaratustra
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;
}
}
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);
}
}
XML =
2000
Assim Falou Zaratustra
123
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.