Deserialize XML elements to different types, based on attribute

天大地大妈咪最大 提交于 2019-12-24 08:48:35

问题


I have an XML document with many Entity elements, which each have an attribute of either type="foo" or type="bar". See this sample:

<RootNode>
    <Entities>
        <Entity type="foo">
            <Price>1</Price>
        </Entity>

        <Entity type="bar">
            <URL>www.google.co.uk</URL>
        </Entity>

        <Entity type="foo">
            <Price>77</Price>
        </Entity>
    </Entities>
</RootNode>

I need a way to tell Simple to deserialize the Entity elements with type="foo" into a List<FooEntity> and the elements with type="bar" into a List<BarEntity>.

How can I do this?

Here is the code I currently have if you want to play around with it:

public class App {
    public static void main(String[] args) throws Exception {
        Reader r = Files.newBufferedReader(
            Paths.get("/path/to/file.xml"),
            Charset.defaultCharset()
        );
        Serializer serializer = new Persister();

        RootNode root = serializer.read(RootNode.class, r);
        System.out.println(root.getFooEntities().size());
        System.out.println(root.getBarEntities().size());
    }
}

@Root(name = "RootNode")
class RootNode {

    // TODO: What annotations to put here?
    private List<FooEntity> fooEntities;

    // TODO: What annotations to put here?
    private List<BarEntity> barEntities;

    public List<FooEntity> getFooEntities() { return fooEntities; }
    public List<BarEntity> getBarEntities() { return barEntities; }
}

class FooEntity {
    @Element(name = "URL")
    private String url;
}

class BarEntity {
    @Element(name = "Price")
    private int price;
}

回答1:


Now the real problem ...

// TODO: What annotations to put here?
private List<BarEntity> barEntities;

My answer: none! Or at least, it doesn't matter!

Attributes like type here are just strings and can't make any decisions. But there's another nice way:

  1. Implement a Converter for RootNode which does the decision
  2. Use a Serializer to do the actual work of deserializing each entity.

I made some modifications to your classes, but nothing spectacular has been changed. The toString()-method is for testing only - implement as you need it.

Class FooEntity

@Root(name = "Entity")
public class FooEntity
{
    @Attribute(name = "type")
    private String type;
    @Element(name = "Price")
    private int price;

    /*
     * NOTE: A default ctor is required - visibile doesn't matter
     */

    @Override
    public String toString()
    {
        return "FooEntity{" + "price=" + price + '}';
    }
}

Class BarEntity

@Root(name = "Entity")
public class BarEntity
{
    @Attribute(name = "type")
    private String type;
    @Element(name = "URL")
    private String url;

    /*
     * NOTE: A default ctor is required - visibile doesn't matter
     */

    @Override
    public String toString()
    {
        return "BarEntity{" + "url=" + url + '}';
    } 
}

Class RootNode

@Root(name = "RootNode")
@Convert(RootNodeConverter.class)   // <--- Important!
class RootNode
{
    private List<FooEntity> fooEntities;
    private List<BarEntity> barEntities;


    public RootNode()
    {
        // This has to be done somewhere ...
        this.fooEntities = new ArrayList<>();
        this.barEntities = new ArrayList<>();
    }


    public List<FooEntity> getFooEntities()
    {
        return fooEntities;
    }

    public List<BarEntity> getBarEntities()
    {
        return barEntities;
    }

    @Override
    public String toString()
    {
        return "RootNode{" + "fooEntities=" + fooEntities + ", barEntities=" + barEntities + '}';
    }
}

And finally the Converter-implementation:

Class RootNodeConverter

public class RootNodeConverter implements Converter<RootNode>
{
    @Override
    public RootNode read(InputNode node) throws Exception
    {
        RootNode root = new RootNode();
        final InputNode entities = node.getNext("Entities");
        InputNode child;

        while( ( child = entities.getNext() ) != null )
        {
            if( child.getName().equals("Entity") == false )
            {
                continue; //  Not an Entity
            }

            final Serializer ser = new Persister();

            switch(child.getAttribute("type").getValue())
            {
                case "foo":
                    root.getFooEntities().add(ser.read(FooEntity.class, child));
                    break;
                case "bar":
                    root.getBarEntities().add(ser.read(BarEntity.class, child));
                    break;
                default:
                    // Not a Foo nor a Bar - what now!?
                    break;
            }
        }

        return root;
    }


    @Override
    public void write(OutputNode node, RootNode value) throws Exception
    {
        throw new UnsupportedOperationException("Not implemented yet!");
    }
}

There are some things to optimize, eg. add a root.addBar(ser.read(BarEntity.class, child)) or errorhandling in general.

Btw. instead of two lists, you can maintain a single one (if relevant). Just make a superclass for the entities. You can move the type-attribute to there too.

Usage

Here's an example how to use:

final String input = "<RootNode>\n"
        + "    <Entities>\n"
        + "        <Entity type=\"foo\">\n"
        + "            <Price>1</Price>\n"
        + "        </Entity>\n"
        + "\n"
        + "        <Entity type=\"bar\">\n"
        + "            <URL>www.google.co.uk</URL>\n"
        + "        </Entity>\n"
        + "\n"
        + "        <Entity type=\"foo\">\n"
        + "            <Price>77</Price>\n"
        + "        </Entity>\n"
        + "    </Entities>\n"
        + "</RootNode>";


final Serializer ser = new Persister(new AnnotationStrategy()); // <-- Note the strategy!

RootNode root = ser.read(RootNode.class, input);
System.out.println(root);

Nothing really spectacular here too ...

Output:

RootNode{fooEntities=[FooEntity{price=1}, FooEntity{price=77}], barEntities=[BarEntity{url=www.google.co.uk}]}


来源:https://stackoverflow.com/questions/25854774/deserialize-xml-elements-to-different-types-based-on-attribute

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!