Xstream Custom XStream Converter

황제낙엽 2011.04.26 15:07 조회 수 : 212

site_link1  
site_link2  
site_link3 http://1 

XStream is a great utility for serializing objects to XML ? and then back again.

The default XML output is neat enough ? albeit a little verbose (using fully qualified class names for example). There are plenty of tutorials available showing how to use XStream aliases to clean up the output (e.g. Manual Tweaking Output).

However, sometimes it is not enough to just tweak and alias ? more serious manipulation of the output stream is required. For example, complicated Collection-based objects come out very cumbersome (but generic ? which is the point). Luckily, XStream provides a powerful interface for custom generation (A Converter in XStream-speak).

To fully control how an object is serialised, create a new class which implements the Converter interface:

01 import com.thoughtworks.xstream.converters.Converter;
02 import com.thoughtworks.xstream.converters.MarshallingContext;
03 import com.thoughtworks.xstream.converters.UnmarshallingContext;
04 import com.thoughtworks.xstream.io.HierarchicalStreamReader;
05 import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
06   
07 public class MyCustomMapConverter implements Converter {
08   
09     @Override
10     public void marshal(Object source, HierarchicalStreamWriter writer,
11             MarshallingContext context) {
12           // TODO
13     }
14   
15     @Override
16     public boolean canConvert(Class clazz) {
17         return clazz.equals(ClassToBeConverted.class);
18     }
19   
20         @Override
21     public Object unmarshal(HierarchicalStreamReader reader,
22             UnmarshallingContext context) {
23           // TODO
24     }
25   
26 }

As can be seen above, the canConvert method tells XStream which objects should use this converter. The other two methods are called when converting objects to XML (marshal) or converting XML to objects (unmarshal).

Marshalling the object is achieved by starting and ending Nodes, while populating their attributes and values. So for example:

1 @Override
2 public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
3   for (RouterConfigRuleMap routerConfigRuleMap : routerConfigRuleMapSet.getRouterConfigRuleMapSet())
4   {
5     writer.startNode("routetype");
6     writer.addAttribute("type", routerConfigRuleMap.getRouteType().toString());
7     writer.endNode();
8 }

will iterate through a collection generating the following output:

1 <routetype type="example" />
2 <routetype type="foo" />

These patterns can become arbitrarily complex.

Unmarshalling is a little more complicated. XStream will work out which (registered ? see later) Converter to use by matching the node to the class (in my head these seems like an extremely difficult task… I’m not sure how XStream has implemented this ? maybe I should look into this!) The node (and all children) will be passed to the unmarshal method.

It is up to us to define how the target object is populated from these nodes. Methods are provided to:

  • Check if the node has children (reader.hasMoreChildren())
  • Move down to the child level (reader.moveDown())
  • Move back up to the parent level (reader.moveUp())

The moveUp/Down methods can be called as many times as necessary to walk the tree.

I provide a full example as I couldn’t find many good ones on the net:

01 @Override
02 public Object unmarshal(HierarchicalStreamReader reader,    UnmarshallingContext context) {
03   
04   RouterConfigRuleMapSet routerConfigRuleMapSet = new RouterConfigRuleMapSet();
05   
06   while (reader.hasMoreChildren()) {
07     reader.moveDown();
08   
09     RouterConfigRuleMap routerConfigRuleMap = new RouterConfigRuleMap();
10     routerConfigRuleMap.setRouteType(RouteType.valueOf(reader.getAttribute("type")));
11   
12     while (reader.hasMoreChildren()) {
13         reader.moveDown();
14         RouterConfigRule routerConfigRule = new RouterConfigRule();
15   
16         if (reader.getAttribute("extractor") != null) {
17             routerConfigRule.setExtractor(reader.getAttribute("extractor"));
18         }
19   
20         if (reader.getAttribute("formatter") != null) {
21             routerConfigRule.setFormatter(reader.getAttribute("formatter"));
22         }
23   
24         if (reader.getAttribute("distributor") != null) {
25             routerConfigRule.setDistributor(reader.getAttribute("distributor"));
26         }
27   
28         routerConfigRuleMap.putRouterConfigRule(reader.getAttribute("name"), routerConfigRule);
29   
30         reader.moveUp();
31         }
32     reader.moveUp();
33     routerConfigRuleMapSet.addRouterConfigRuleMapSet(routerConfigRuleMap);
34     }
35   return routerConfigRuleMapSet;
36 }

Out of completeness, here is the equivalent marshal implementation:

01 @Override
02 public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
03   
04   RouterConfigRuleMapSet routerConfigRuleMapSet = (RouterConfigRuleMapSet)source;
05   for (RouterConfigRuleMap routerConfigRuleMap : routerConfigRuleMapSet.getRouterConfigRuleMapSet())
06   {
07     writer.startNode("routetype");
08     writer.addAttribute("type", routerConfigRuleMap.getRouteType().toString());
09   
10     for (Object routerConfig : routerConfigRuleMap.routerConfigRuleMap.entrySet()) {
11         Entry<String, RouterConfigRule> entry = (Entry<String, RouterConfigRule>) routerConfig;
12         RouterConfigRule routerConfigRule = entry.getValue(); 
13   
14         writer.startNode("routename");
15         writer.addAttribute("name", entry.getKey().toString());
16         if (routerConfigRule.getExtractor() != null) {
17             writer.addAttribute("extractor", routerConfigRule.getExtractor());
18         }
19   
20         if (routerConfigRule.getFormatter() != null) {
21             writer.addAttribute("formatter", routerConfigRule.getFormatter());
22         }
23   
24         if (routerConfigRule.getDistributor() != null) {
25             writer.addAttribute("distributor", routerConfigRule.getDistributor());
26         }
27   
28         writer.endNode();
29     }
30     writer.endNode();
31   }
32 }

To register the Converter simply add the following line when you instantiate the XStream object:

1 XStream stream = new XStream();
2   stream.registerConverter(new RouterConfigMapConverter()); // NEW LINE
번호 제목 글쓴이 날짜 조회 수
47 XpressEngine(XE) 에서 엮인글 스팸 방지법 file 황제낙엽 2017.08.25 461
46 [Hibernate] 페이징 처리 정리 황제낙엽 2007.02.26 457
45 [Hibernate] Hibernate 프레임워크를 이용한 효율적인 개발 전략 황제낙엽 2007.01.30 389
44 XStream 배우기 : 별칭(Alias) 배우기 황제낙엽 2011.04.20 340
43 JSTL과 Velocity를 활용한 UI 레이어 구현 방법 황제낙엽 2007.01.30 339
42 XStream 배우기 : 변환기(Converter) 배우기 황제낙엽 2011.04.20 298
» Custom XStream Converter 황제낙엽 2011.04.26 212
40 [Hibernate] Hibernate Tutorial 황제낙엽 2012.11.15 166
39 [Hibernate] Hibernate 와 Ant 에서 composite id 사용하기 예제 file 황제낙엽 2005.11.29 134
38 검색엔진 루씬 Lucene... Analyzer의 선택 황제낙엽 2007.11.27 102
37 Map <-> XML (2) 황제낙엽 2011.04.29 99
36 [Hibernate] 하이버네이트 참조문서 버전 3.2 cr3의 최신 업데이트 한글 번역본 file 황제낙엽 2007.07.03 98
35 벨로시티에서 loop 작성 황제낙엽 2005.12.13 85
34 XE 서버 이전 계획 file 황제낙엽 2018.08.29 80
33 SiteMesh를 이용하여 웹 페이지의 레이아웃을 처리 황제낙엽 2007.08.13 67
32 XML -> (Map) XML 황제낙엽 2011.04.29 61
31 Jakarta Lucene (루씬) - 들어가기 황제낙엽 2007.07.30 57
30 Xdoclet 사용하기 1부 황제낙엽 2006.10.04 45
29 XStream 배우기 : 2분만에 배우는 XStream 황제낙엽 2011.04.20 40
28 Xdoclet 사용하기 2부 황제낙엽 2006.10.04 40