I'm using Simple Framework for XML serialization/deserialization stuff and while the first is easy, I have issues with the latter. So, I receive XML response from a server and it looks like this:
<?xml version="1.0" ?>
<tables>
<table name="result" a="context" b="name">
<r a="stuff1" b="blahblah" />
</table>
<table name="response" a="error" b="reason">
<r a="0" b="" />
</table>
</tables>
Yes, it has 2 elements with name "table". The catch is that the first "table" element may have more than 3 attributes, which means, I can't just create an universal entity for "table" tag. So, my current code for deserialized entity looks like this:
@Root(name = "tables", strict = false)
public class Response {
@Element(name = "table", required = false)
@Path("//table[@name='result']")
Result resultTable;
@Element(name = "table")
@Path("//table[@name='result']")
Response responseTable;
public Result getResultTable() {
return resultTable;
}
public void setResultTable(Result resultTable) {
this.resultTable = resultTable;
}
public Response getResponseTable() {
return responseTable;
}
public void setResponseTable(Response responseTable) {
this.responseTable = responseTable;
}
}
Unfortunately, it doesn't work: I get an exception:
org.simpleframework.xml.core.PathException: Path '//[@name='result']' in field 'resultTable'
com.package.Response.resultTable references document root
I tried different XPath options like simply wildcarting nodes:
@Path("//*[@name='result']")
@Path("*[@name='result']")
but this didn't work either. So, is it my fault due to incorrect XPath options or limitations of Simple Framework:
One thing to note when using such annotations, is that only a subset of the XPath expression syntax is supported. For example, element and attribute references can not be taken from the root of the document, only references within the current context are allowed.
and should I then do it with other XML deserializers? Thanks.
You could try go workaround this by using an inline-list . Another - and to my mind better solution : Use a Converter
for the "if name is result deserialize as Result, if response deserialize as response" part. This sounds more complicated than it actually is!
Since there are two Response
classes - one used for tables
and one as table
, i named the latter one ResponseTable
; ResultTable
is Result
in your example. I guess you have some packages instead to prevent this.
For both tables, a class Content
is used to model the <r ... />
element.
@Root
public class ResponseTable // 'Response' in your code
{
@Attribute(name = "name")
private String name;
@Attribute(empty = "a")
private String a;
@Attribute(empty = "b")
private String b;
@Element(name = "r")
private Content r;
// ...
}
@Root
public class ResultTable // 'Result' in your code
{
@Attribute(name = "name")
private String name;
@Attribute(empty = "a")
private String a;
@Attribute(empty = "b")
private String b;
@Element(name = "r")
private Content r;
// ...
}
@Root()
public class Content
{
@Attribute(name = "a")
private String a;
@Attribute(name = "b")
private String b;
// ...
}
And here comes the interesting part:
@Root(name = "tables", strict = false)
@Convert(Response.ResponseConverter.class)
public class Response
{
@Element(name = "table")
private ResultTable resultTable;
@Element(name = "table2")
private ResponseTable responeTable;
// ...
static class ResponseConverter implements Converter<Response>
{
private final Serializer ser = new Persister();
@Override
public Response read(InputNode node) throws Exception
{
Response resp = new Response();
InputNode n = node.getNext();
while( n != null )
{
switch( n.getAttribute("name").getValue() )
{
case "result":
resp.resultTable = ser.read(ResultTable.class, n);
break;
case "response":
resp.responeTable = ser.read(ResponseTable.class, n);
break;
default:
throw new RuntimeException("Unsupported table: "
+ n.getAttribute("name").getValue());
}
n = node.getNext();
}
return resp;
}
@Override
public void write(OutputNode node, Response value) throws Exception
{
// Implement as needed (hint: again use Serializer here)
throw new UnsupportedOperationException("Not supported yet.");
}
}
}
What happens here:
@Convert
is used to specify a Converter
implementation which implements (de-)serialization of the Response
class ResponseConverter
is only used to determine the type (= Response or Result table) of a <table …>…</table>
element Serializer
is used - no need to do the whole work manually! I haven't implemented the write()
part, but as you can see it's not that difficult; you can utilize a Serializer
again to do the main work.
Converter
: Response
Result
This makes it possible to have multiple classes which serialize to xml, even if they share the same element name.
There's just one thing to notice: For @Convert
an AnnotationStrategy
must be set:
Serializer ser = new Persister(new AnnotationStrategy());
// ^^^^^^^^^^^^^^^^^^^^
final String xml = …
Response response = ser.read(Response.class, xml);
System.out.println(response)
Note: There's no need to use an AnnotationStrategy
strategy within your Converter
- as long as you don't rely on another Converter
there.
Output
(generated toString()
methods)
Response{resultTable=ResultTable{name=result, a=context, b=name, r=Content{a=stuff1, b=blahblah}}, responeTable=ResponseTable{name=response, a=error, b=reason, r=Content{a=0, b=}}}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.