简体   繁体   中英

Deserializing multiple different tags (with the same name) with SimpleFramework

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!


Classes

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.

ResponseTable

@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;

    // ...
}

ResultTable

@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;

    // ...
}

Content

@Root()
public class Content
{
    @Attribute(name = "a")
    private String a;
    @Attribute(name = "b")
    private String b;

    // ...
}

Response

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:

  1. @Convert is used to specify a Converter implementation which implements (de-)serialization of the Response class
  2. The implementation ResponseConverter is only used to determine the type (= Response or Result table) of a <table …>…</table> element
  3. For the actual deserialization a normal 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.


To sum it up the Converter :

  • If Table with Attribute name = response :Deserialize as Response
  • If table with Attribute name = result : Deserialize as Result
  • Else: Throw an execption

This makes it possible to have multiple classes which serialize to xml, even if they share the same element name.


Usage

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM