Xstream Custom XStream Converter

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

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
번호 제목 글쓴이 날짜 조회 수
50 XpressEngine(XE) 에서 엮인글 스팸 방지법 file 황제낙엽 2017.08.25 639
49 [Hibernate] 페이징 처리 정리 황제낙엽 2007.02.26 540
48 [Hibernate] Hibernate 프레임워크를 이용한 효율적인 개발 전략 황제낙엽 2007.01.30 483
47 XStream 배우기 : 별칭(Alias) 배우기 황제낙엽 2011.04.20 447
46 JSTL과 Velocity를 활용한 UI 레이어 구현 방법 황제낙엽 2007.01.30 433
45 XStream 배우기 : 변환기(Converter) 배우기 황제낙엽 2011.04.20 409
44 Map <-> XML (2) 황제낙엽 2011.04.29 401
43 부트스트랩 4 와 5 버전 차이를 알려줘 (from ChatGPT) 황제낙엽 2023.02.24 323
» Custom XStream Converter 황제낙엽 2011.04.26 313
41 [Hibernate] Hibernate Tutorial 황제낙엽 2012.11.15 287
40 Vanilla JS Datepicker 에서 locales 지정 (한국어로 출력) 황제낙엽 2023.06.19 254
39 [Hibernate] Hibernate 와 Ant 에서 composite id 사용하기 예제 file 황제낙엽 2005.11.29 252
38 검색엔진 루씬 Lucene... Analyzer의 선택 황제낙엽 2007.11.27 217
37 [Hibernate] 하이버네이트 참조문서 버전 3.2 cr3의 최신 업데이트 한글 번역본 file 황제낙엽 2007.07.03 202
36 XE 서버 이전 계획 file 황제낙엽 2018.08.29 197
35 벨로시티에서 loop 작성 황제낙엽 2005.12.13 193
34 Chatbot 용 디자인 템플릿 (ChatGPT) file 황제낙엽 2024.01.28 170
33 XML -> (Map) XML 황제낙엽 2011.04.29 167
32 Bootstrap 5 Gallery Examples & Freebies 황제낙엽 2023.09.26 161
31 Tiles2 간단한 예제 맛보기 황제낙엽 2009.10.20 153