使用XStream序列化、反序列化XML数据时遇到的各种问题

前端之家收集整理的这篇文章主要介绍了使用XStream序列化、反序列化XML数据时遇到的各种问题前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

转载:http://www.blogjava.net/DLevin/archive/2012/11/30/392240.html

首先对于简单的引用,XStream使用起来确实比较简单,比如自定义标签属性、使用属性和使用子标签的定义等:

@XStreamAlias( " request )
public class XmlRequest1{
private static XStreamxstream;
{
xstream
= new XStream();
xstream.autodetectAnnotations(
true );
}

@XStreamAsAttribute
Stringfrom;

@XStreamAsAttribute
@XStreamAlias(
calculate-method StringcalculateMethod;

@XStreamAlias(
request-time private DaterequestTime;

@XStreamAlias(
input-files List < InputFileInfo > inputFiles;

StringtoXml(XmlRequest1request){
StringWriterwriter
StringWriter();
writer.append(Constants.XML_HEADER);
xstream.toXML(request,writer);
return writer.toString();
}
XmlRequest1toInstance(StringxmlContent){
(XmlRequest1)xstream.fromXML(xmlContent);
}

@XStreamAlias(
input-file InputFileInfo{
Stringtype;
StringfileName;

}
void main(String[]args){
XmlRequest1request
buildXmlRequest();
System.out.println(XmlRequest1.toXml(request));
}
XmlRequest1buildXmlRequest(){

}
}

对以上Request定义,我们可以得到如下结果:

<? xmlversion="1.0"encoding="UTF-8" ?>
< request from ="levin@host" ="advanced" > 2012-11-2817:11:54.664UTC </ type DATA fileName data.2012.11.29.dat CALENDAR calendar.2012.11.29.dat >

可惜这个世界不会那么清净,这个格式有些时候貌似并不符合要求,比如request-time的格式、input-files的格式,我们实际需要的格式是这样的:

20121128T17:51:05 input-file ="DATA" ="CALENDAR" 对不同Date格式的支持可以是用Converter实现,在XStream中默认使用自己实现的DateConverter,它支持的格式是:yyyy-MM-dd HH:mm:ss.S 'UTC',然而我们现在需要的格式是yyyy-MM-dd’T’HH:mm:ss,如果使用XStream直接注册DateConverter,可以使用配置自己的DateConverter,但是由于DateConverter的构造函数的定义以及@XStreamConverter的构造函数参数的支持方式的限制,貌似DateConverter不能很好的支持注解方式的注册,因而我时间了一个自己的DateConverter支持注解:

LevinDateConverter extends DateConverter{
LevinDateConverter(StringdateFormat){
super (dateFormat, String[]{dateFormat});
}
}

requestTime字段中需要加入以下注解定义:

@XStreamConverter(value LevinDateConverter. ,strings { yyyyMMdd'T'HH:mm:ss })
@XStreamAlias(
DaterequestTime;

对集合类,XStream提供了@XStreamImplicit注解,以将集合中的内容摊平到上一层XML元素中,其中itemFieldName的值为其使用的标签名,此时InputFileInfo类中不需要@XStreamAlias标签的定义:

@XStreamImplicit(itemFieldName inputFiles;

InputFileInfo中的字段,type作为属性很容易,只要为它加上@XStreamAsAttribute注解即可,而将fileName作为input-file标签的一个内容字符串,则需要使用ToAttributedValueConverter,其中Converter的参数为需要作为字符串内容的字段名:

ToAttributedValueConverter. })
InputFileInfo{
@XStreamAsAttribute
StringfileName;

}

XStream对枚举类型的支持貌似不怎么好,默认注册EnumSingleValueConverter只是使用了Enum提供的name()和静态的valueOf()方法enum转换成String或将String转换回enum。然而有些时候XML的字符串和类定义的enum值并不完全匹配,最常见的就是大小写的不匹配,此时需要写自己的Converter。在这种情况下,我一般会在enum中定义一个name属性,这样就可以自定义enum的字符串表示。比如有TimePeriodenum

enum TimePeriod{
MONTHLY(
monthly ),WEEKLY( weekly daily );

Stringname;

StringgetName(){
name;
}

TimePeriod(Stringname){
this .name TimePeriodtoEnum(StringtimePeriod){
try {
Enum.valueOf(TimePeriod. catch (Exceptionex){
for (TimePeriodperiod:TimePeriod.values()){
if (period.getName().equalsIgnoreCase(timePeriod)){
period;
}
}
throw IllegalArgumentException( Cannotconvert< + timePeriod >toTimePeriodenum );
}
}
}

我们可以编写以下Converter以实现对枚举类型的更宽的容错性:

LevinEnumSingleNameConverter EnumSingleValueConverter{
final StringCUSTOM_ENUM_NAME_METHOD getName ;
StringCUSTOM_ENUM_VALUE_OF_METHOD toEnum ;

Class <? Enum <?>> enumType;

LevinEnumSingleNameConverter(Class type){
(type);
.enumType type;
}

@Override
StringtoString(Objectobj){
Methodmethod
getCustomEnumNameMethod();
(method == null ){
.toString(obj);
}
else (String)method.invoke(obj,(Object[]) );
}
.toString(obj);
}
}
}

@Override
ObjectfromString(Stringstr){
Methodmethod
getCustomEnumStaticValueOfMethod();
enhancedFromString(str);
}
method.invoke( enhancedFromString(str);
}
}

MethodgetCustomEnumNameMethod(){
enumType.getMethod(CUSTOM_ENUM_NAME_METHOD,(Class <?> []) ;
}
}

MethodgetCustomEnumStaticValueOfMethod(){
{
Methodmethod
enumType.getMethod(CUSTOM_ENUM_VALUE_OF_METHOD,0)">);
(method.getModifiers() Modifier.STATIC){
method;
}
;
}
ObjectenhancedFromString(Stringstr){
.fromString(str);
}
(Enum item:enumType.getEnumConstants()){
(item.name().equalsIgnoreCase(str)){
item;
}
}
IllegalStateException( Cannotconverter< str >toenum< enumType 如下方式使用即可:

@XStreamAsAttribute
@XStreamAlias( time-period )
@XStreamConverter(value
LevinEnumSingleNameConverter. TimePeriodtimePeriod;

double类型,貌似默认的DoubleConverter实现依然不给力,它不支持自定义的格式,比如我们想在序列化的时候用一下格式:###,##0.0########,此时又需要编写自己的Converter

FormatableDoubleConverter DoubleConverter{
Stringpattern;
DecimalFormatformatter;

FormatableDoubleConverter(Stringpattern){
.pattern pattern;
.formatter DecimalFormat(pattern);
}

@Override
StringtoString(Objectobj){
(formatter formatter.format(obj);
}
}

@Override
ObjectfromString(Stringstr){
!= formatter.parse(str);
}
(Exceptione){
Cannotparse< >todoublevalue }
}

StringgetPattern(){
pattern;
}
}

使用方式和之前的Converter类似:

@XStreamAsAttribute
@XStreamConverter(value FormatableDoubleConverter. ###,##0.0######## double value;

最后,还有两个XStream没法实现的,或者说我没有找到一个更好的实现方式的场景。第一种场景是XStream不能很好的处理对象组合问题:

在面向对象编程中,一般尽量的倾向于抽取相同的数据成一个类,而通过组合的方式构建整个数据结构。比如Student类中有nameaddressAddress是一个类,它包含citycodestreet等信息,此时如果要对Student对象做如下格式序列化:

student name =”Levin”>
<city shanghai city street zhangjiang code 201203 student 貌似我没有找到可以实现的方式,XStream能做是在中间加一层address标签。对这种场景的解决方案,一种是将Address中的属性平摊到Student类中,另一种是让Student继承自Address类。不过貌似这两种都不是比较理想的办法。

第二种场景是XStream不能很好的处理多态问题:

比如我们有一个Trade类,它可能表示不同的产品:

Trade{
StringtradeId;
Productproduct;

}
abstract Product{
Stringname;
Product(Stringname){
name;
}

}
FX ratio;
FX(){
( fx );
}

}
Future maturity;
Future(){
future );
}

}

通过一些简单的设置,我们能得到如下XML格式:

trades trade trade-id ="001" product class ="levin.xstream.blog.FX" ="fx" ratio ="0.59" /> trade ="002" ="levin.xstream.blog.Future" ="future" maturity ="2.123" >

作为数据文件,对Java类的定义显然是不合理的,因而简单一些,我们可以编写自己的Converterclass属性product去除

xstream.registerConverter( ProductConverter(
xstream.getMapper(),xstream.getReflectionProvider()));

ProductConverter(Mappermapper,ReflectionProviderreflectionProvider){
(mapper,reflectionProvider);
}

@Override
boolean canConvert(@SuppressWarnings( rawtypes )Classtype){
Product. .isAssignableFrom(type);
}

@Override
protected ObjectinstantiateNewInstance(HierarchicalStreamReaderreader,UnmarshallingContextcontext){
ObjectcurrentObject
context.currentObject();
(currentObject currentObject;
}

Stringname
reader.getAttribute( .equals(name)){
reflectionProvider.newInstance(FX. reflectionProvider.newInstance(Future. );
}
>product );
}
}

在所有Production上定义@XStreamAlias(“product”)注解。这时的XML输出结果为:

然而如果有人希望XML输出结果如下呢?

fx future 大概找了一下,可能可以定义自己的Mapper解决,不过XStream的源码貌似比较复杂,没有时间深究这个问题,留着以后慢慢解决吧。

补充:

对Map类型数据,XStream默认使用以下格式显示

< map class ="linked-hash-map" >
entry string key1 </ value1 key2 value2 map >

但是对一些简单的Map,我们希望如下显示

entry key ="key1" value ="value1" /> ="key2" ="value2" >

对这种需求需要通过编写Converter解决,继承自MapConverter,覆盖以下函数,这里的Map默认key和value都是String类型,如果他们不是String类型,需要另外添加逻辑:

@SuppressWarnings( " rawtypes )
@Override
public void marshal(Objectsource,HierarchicalStreamWriterwriter,
MarshallingContextcontext){
Mapmap
= (Map)source;
for (Iteratoriterator map.entrySet().iterator();iterator.hasNext();){
Entryentry
(Entry)iterator.next();
ExtendedHierarchicalStreamWriterHelper.startNode(writer,mapper()
.serializedClass(Map.Entry.
class writer.addAttribute( unchecked })
protected putCurrentEntryIntoMap(HierarchicalStreamReaderreader,
UnmarshallingContextcontext,Mapmap,Maptarget){
Objectkey
reader.getAttribute( );
Objectvalue
);

target.put(key,value);
}

但是只是使用Converter,得到的结果多了一个class属性

>

在XStream中,如果定义的字段是一个父类或接口,在序列化是会默认加入class属性以确定反序列化时用的类,为了去掉这个class属性,可以定义默认的实现类来解决(虽然感觉这种解决方案不太好,但是目前还没有找到更好的解决方案)。

xstream.addDefaultImplementation(LinkedHashMap.class);

猜你在找的XML相关文章