XStream: how to marshal/unmarshal lists in a custom converter? - java

I have the following class (legacy; not annotatable) that is serialized with a custom converter:
class Test {
// some other variables
List<SomeType> someTypeList;
}
A properly working converter for SomeType is already available. However I want the list to be serialized as if it was annotated with #XStreamAlias("someTypes").
In the end I expect the following format for someTypeList:
<someTypes class="list-type">
<someType>
....
</someType>
...
</someTypes>
How do I have to implement the marshal/unmarshal method to get the desired output? Calling context.convertAnother(someTypeList) didn't yield the expected result as the surrounding <someTypes> tag was missing.

You wise to get the structure:
<someTypes class="list-type">
<someType>
....
</someType>
...
</someTypes>
Look at the following code. For your list you need to tag:
#XStreamImplicit(itemFieldName="someType")
List<someType>List;
Now, depending on what you got inside, you might need to create a custom converter. To refer to that you change a bit like this:
#XStreamImplicit(itemFieldName="someType") #XStreamConverter(YourOwnConverter.class)
List<SomeType> someTypeList;
Then create a converter class (YourOwnConverter) that would know how to un/marshal:
public boolean canConvert(Class type)
{
return type.equals(SomeType.class);
}
public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context)
{
SomeType mytype = (SomeType) source;
writer.addAttribute("position", mytype.getPosition());
writer.setValue(mytype.getId());
}
public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context)
{
SomeType mytype = new SomeType();
String position = reader.getAttribute("position");
......
return mytype ;
}
Use this as example:
http://x-stream.github.io/converter-tutorial.html

Is there a addImplicitCollection called on the xstream object during configuration somewhere which causes the someTypes tag to be skipped ?

Related

xstream marshal to XML - add attribute based on number of child nodes

I'm pretty new to xstream.
I'm working on a model class that looks like the following:
#XStreamAlias("MyRootClass")
public class MyRootClass {
// A bunch of other classes as child nodes
#XStreamAlias("MyClassList")
private List<MyClass> foo;
}
Now, is there a way for me to produce the following XML when marshalling, without modifying the class?
<MyRootClass>
<!-- a bunch of other class nodes -->
<MyClassList COUNT="3">
<MyClass>MyClass 1</MyClass>
<MyClass>MyClass 2</MyClass>
<MyClass>MyClass 3</MyClass>
</MyClassList>
</MyRootClass>
The main issue is how to add the attribute "COUNT" to the list of MyClass gracefully. It will always show the number of MyClass inside MyClassList.
I am not allowed to modify the model class. However, I can implement my own converter to achieve the above.
The question is: what's the best way to do it?
There are lots of other (complex) classes within MyRootClass, and they've all been aliased & annotated. So, creating a 'marshal' method from scratch might be overkill?
welp, I figured it out.
Not sure if this is the "best" way to do it, but it works.
the key is using converters and nested converters.
this is what I ended up doing:
public class MyRootClassConverter implements Converter {
#Override
public boolean canConvert(Class type) {
return type.equals(MyRootClassConverter.class);
}
#Override
public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
MyRootClass rootClass= (MyRootClass) source;
// as necessary
writer.startNode("Other Class1");
writer.setValue("Other Class Value");
writer.endNode();
if (rootClass.getMyClassList() != null || !rootClass.getMyClassList().isEmpty()) {
writer.startNode("MyClassList");
writer.addAttribute("COUNT", String.valueOf(rootClass.getMyClassList().size()));
for (MyClass child : rootClass.getMyClassList()) {
writer.startNode("MyClass");
context.convertAnother(child); // this is where the nesting happens
writer.endNode();
}
writer.endNode();
}
}
}

XStream custom converter that can generate flat XML structure from List?

I'm using XStream and have a class with a field like the following:
private Map<String, String> data;
I want to generate XML output like this:
<key1>test data</key1>
<key2>test data</key2>
<key3>test data</key3>
So I want the map key to be the element. The mapvalue to be the XML value and I don't want the XML wrapped in an element such as <data></data>. Can anyone point to sample code that does this, or something similar?
UPDATE
This just a snippet, there is a root element.
UPDATE 2
The custom converter code I posted below almost works. I get a flat structure, but I need to remove the outer element. Any idea on that?
//this is the result need to remove <data>
<data>
<key1>test data</key1>
<key2>test data</key2>
<key3>test data</key3>
</data>
This is the code
public class MapToFlatConverter implements Converter{
public MapToFlatConverter() {
}
#Override
public boolean canConvert(Class type) {
return Map.class.isAssignableFrom(type);
}
#Override
public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
Map<String, String> map = (Map<String, String>) source;
for (Map.Entry<String, String> entry : map.entrySet()) {
writer.startNode(entry.getKey());
writer.setValue(entry.getValue().toString());
writer.endNode();
}
}
#Override
public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
//not needed at this time
return null;
}
}
I was able to get this working. The following SO post is what I ultimately did: custom converter in XStream. I needed to extend from the ReflectionConverter:
This next post helped also, though when I tried this approach the context.convertAnother() method did not seem to work. So I switched to the method in the 1st post.
Xstream Implicit Map As Attributes to Root Element

Jackson Parser To Java Object

I am using Jackson to parse object. sometime I need list of objects.
when I am using like this its working
List<MyObject> mapper.readValue(file , new TypeReference<MyObject>() {})
but when I am using it like this its not working
public class JsonMocksRepository<T>{
public T getObject() throws Exception{
return mapper.readValue(file ,new TypeReference<T>());
}
}
What I need to do ?
Basically I want to use generics to get the right class
This is because of type erasure. There is no information about the actual type represented by T available at runtime, so your TypeReference will be effectively be simply TypeReference<Object>.
If you want a generic instance of JsonMocksRepository, you will need to inject the TypeReference at construction time:
public class JsonMocksRepository<T>{
private final TypeReference<T> typeRef;
public JsonMocksRepository(TypeReference<T> typeRef) {
this.typeRef = typeRef;
}
public T getObject() throws Exception{
return mapper.readValue(file, typeRef);
}
}

Parsing with XStream - empty tags and collections

I am using XStream to convert XML into domain objects, and have come by a problem. Omitting a few details, the XML looks like this :
<airport>
<flights>
<flight>.....</flight>
<flight>.....</flight>
<flight>.....</flight>
</flights>
</airport>
There can be 0 to N flight elements. The flight elements themselves contain other elements. I have created classes for airport, flights, and flight and registered them with the xstream.alias function.
xstream = new XStream();
xstream.alias("airport", AirportPojo.class);
xstream.alias("flights", FlightsPojo.class);
xstream.alias("flight", FlightPojo.class);
xstream.useAttributeFor(AirportPojo.class, "flights");
xstream.addImplicitCollection(FlightsPojo.class, "flights", FlightPojo.class);
AirportPojo airportPojo = (AirportPojo) xstream.fromXML(xml);
So, after converting, this gives me an AirportPojo object containing a FlightsPojo object, containing a collection of FlightPojo objects. However, when there are 0 flight elements it seems that the collection of FlightPojos is null. I would expect (and prefer) the list to be initialized but with zero elements in it. How could I accomplish this? Bear in mind that I cannot use annotations as this is a legacy project.
How about implementing a custom converter?
class FlightsConverter implements Converter {
#Override
public boolean canConvert(Class clazz) {
return clazz.equals(FlightsPojo.class);
}
#Override
public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
FlightsPojo flightsPojo = new FlightsPojo();
flightsPojo.setFlights(new ArrayList<FlightPojo>());
while (reader.hasMoreChildren()) {
reader.moveDown();
FlightPojo flightPojo = (FlightPojo) context.convertAnother(flightsPojo, FlightPojo.class);
flightsPojo.getFlights().add(flightPojo);
System.out.println(reader.getValue());
reader.moveUp();
}
return flightsPojo;
}
#Override
public void marshal(Object value, HierarchicalStreamWriter writer, MarshallingContext context) {
// todo...
}
}
And hook it in like so:
XStream xstream = new XStream();
xstream.registerConverter(new FlightsConverter());
xstream.alias("airport", AirportPojo.class);
xstream.alias("flights", FlightsPojo.class);
xstream.alias("flight", FlightPojo.class);
xstream.useAttributeFor(AirportPojo.class, "flights");
AirportPojo airportPojo = (AirportPojo) xstream.fromXML(xml);
Hope this helps ;)
Looks like XStream is unable to handle implicit collections in such way.
See this part of XStream's FAQ.

Using converters with xstream when an element has a value and child elements

I have a string of XML that looks like this:
<e1 atr1="3" atr2="asdf">
<e1b atr3="3" atr4="asdf">
<e1c atr5="3" atr6="asdf"/>
TestValue1
</e1b>
<e1b atr3="3" atr4="asdf">
<e1c atr5="3" atr6="asdf"/>
TestValue2
</e1b>
</e1>
It is different than other XML I have parsed in the past because the e1b elements have values TestValue1 and TestValue2 as well as child elements (e1c).
If an element has both attributes and a value, you have to create a custom converter for xstream to be able to parse it. My attempt at that is below, but because the e1b element has attributes, child elements, AND a value, I'm not sure how to handle it. In my converter, I have left off all references to the e1c child element. What do I need to add to the converter to allow it to handle the e1c element correctly? Right now, e1c values are not getting populated when I do a xstream.fromXML().
public class e1Converter implements Converter {
#SuppressWarnings("rawtypes")
public boolean canConvert(Class clazz) {
return e1b.class == clazz;
}
public void marshal(Object object, HierarchicalStreamWriter hsw,
MarshallingContext mc) {
e1b e = (e1b) object;
hsw.addAttribute("atr3", e.getAtr3());
hsw.addAttribute("atr4", e.getAtr4());
hsw.setValue(e.getE1bValue());
}
public Object unmarshal(HierarchicalStreamReader hsr,
UnmarshallingContext uc) {
e1b e = new e1b();
e.setAtr3(hsr.getAttribute("atr3"));
e.setAtr4(hsr.getAttribute("atr4"));
e.setE1bValue(hsr.getValue());
return e;
}
}
According to Jörg on the xstream mailing list:
Actually you cannot. XStream cannot read mixed-mode XML i.e. XML where
text and child elements are mixed at the same level. The readers will
simply act in undefined behavior. This kind of XML does simply not
fit into the hierarchical stream model of XStream. What's the value
of parent here:
<parent> what <child/>is <child/> the <child/>value <child/>now? </parent
Sorry, Jörg

Categories

Resources