使用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的字符串表示。比如有TimePeriod的enum: 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类中有name、address,Address是一个类,它包含city、code、street等信息,此时如果要对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类的定义显然是不合理的,因而简单一些,我们可以编写自己的Converter将class属性从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);
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
- ruby-on-rails – 创建记录后更改Factory上的属性
- c# – 通过Exchange Web服务(EWS)erreor SSL查询全局地址列
- React Native学习笔记3:导入AndroidStudio及修改项目
- iphone – 为了加快应用程序开发,Xcode中必须知道哪些快捷方
- cocos2dx 2.x 粒子渲染时有黑色粒BUG
- 基于Vue的ajax公共方法(详解)
- ruby-on-rails-3.2 – 访问Pundit策略中的会话参数
- 《React全栈:Redux+Flux+webpack+Babel整合开发》--互动出
- c# – 有哪些好方法可以存储大量的对象,这些对象会被持久修
- cocos2dx实例开发之2D横版跑酷