Search code examples
javaderived-classsimple-framework

Simple-XML - how to serialize derived classes in a collection?


I want to serialize a object hierarchy including a list of objects which derive from the base class 'Thing'. This works fine, including deserialization - but XML-Simple insists in writing an attribute which specifies the actual used Java-class

when I create a xml file with the java code below, the content is like this:

    <example1>
       <things>
          <fruit class="com.mumpitz.simplexmltest.Apple" id="17">
             <sugar>212</sugar>
          </fruit>
          <fruit class="com.mumpitz.simplexmltest.Orange" id="25" weight="11.2"/>
       </things>
    </example1>

but this is not what I want. I'd like to have

    <example1>
       <things>
          <apple id="17">
             <sugar>212</sugar>
          </apple>
          <orange id="25" weight="11.2"/>
       </things>
    </example1>

'apple' and 'orange' elements without a class attribute, not 'fruit' with such an attribute. Is this possible?

(The second xml complies to a existing schema; adding extra attributes is not an option)

Here's the code:

    package com.mumpitz.simplexmltest;

    import java.io.File;
    import java.util.ArrayList;

    import org.simpleframework.xml.Attribute;
    import org.simpleframework.xml.Element;
    import org.simpleframework.xml.ElementList;
    import org.simpleframework.xml.Root;
    import org.simpleframework.xml.Serializer;
    import org.simpleframework.xml.core.Persister;

    class Fruit {
        @Attribute(name = "id")
        protected final int id;
        Fruit(
                @Attribute(name = "id")
                int id) {
            this.id = id;
        }
        int getObjectId() {
            return id;
        }
    }

    @Root
    class Apple extends Fruit {
        private final int sugar;
        @Element(type = Fruit.class)
        public Apple(
                @Attribute(name = "id")
                int id,
                @Element(name = "sugar")
                int sugar) {
            super(id);
            this.sugar = sugar;
        }

        @Element(name = "sugar")
        public int getSugar() {
            return this.sugar;
        }

        @Override
        public String toString() {
            return "id: " + id + ", sugar: " + sugar;
        }
    }

    @Root
    class Orange extends Fruit {
        @Attribute
        public double weight;

        public Orange(
                @Attribute(name = "id")
                int id) {
            super(id);
        }

        @Override
        public String toString() {
            return "id: " + id + ", weight: " + weight;
        }
    }

    @Root
    public class Example1 {
        @ElementList
        public ArrayList<Fruit> things = new ArrayList<Fruit>();

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            sb.append("things:\n");
            for (int i=0; i<things.size(); i++) {
                sb.append(" " + things.get(i).toString() + "\n");
            }
            return sb.toString();
        }


        //////////////////////////////////

        static Example1 createDummy() {
            Example1 d = new Example1();
            d.things.add(new Apple(17, 212));
            Orange or = new Orange(25);
            or.weight = 11.2;
            d.things.add(or);
            return d;
        }

        static String msg;
        static Example1 res;

        static public String getMessage() {
            String m = msg;
            msg = null;
            return m;
        }

        static public boolean write(String path) {
            Serializer serializer = new Persister();
            Example1 example = Example1.createDummy();
            File result = new File(path);

            try {
                serializer.write(example, result);
            } catch (Exception e) {
                e.printStackTrace();
                msg = e.getMessage();
                return false;
            }
            return true;
        }

        static public boolean read(String path) {
            Serializer serializer = new Persister();
            File source = new File(path);

            try {
                res = serializer.read(Example1.class, source);
            } catch (Exception e) {
                e.printStackTrace();
                msg = e.getMessage();
                return false;
            }
            return true;
        }

        public static Object getResult() {
            return res;
        }
    }

Solution

  • some hours later I found the solution. You simply have to

    1. Read the manual
    2. Use the @ElementListUnion annotation

      package com.mumpitz.simplexmltest;
      
      import java.io.File;
      import java.util.ArrayList;
      import java.util.List;
      
      import org.simpleframework.xml.Attribute;
      import org.simpleframework.xml.Element;
      import org.simpleframework.xml.ElementList;
      import org.simpleframework.xml.ElementListUnion;
      import org.simpleframework.xml.Root;
      import org.simpleframework.xml.Serializer;
      import org.simpleframework.xml.core.Persister;
      
      // the base class
      @Element
      class Thing {
      
          static int count=0;
      
          Thing() {
              this.id = ++count;
          }
      
          @Attribute
          protected int id;
      
          public int getId() {
              return id;
          }
      }
      
      // first derived class
      @Element
      class Car extends Thing {
          @Attribute
          private String name;
      
          Car(@Attribute(name="name") String name) {
              this.name = name;
          }
      
          public String getName() {
              return name;
          }
          @Override
          public String toString() {
              return "ID: " + id + " Car: " + name;
          }
      }
      
      // second derived class
      @Element
      class House extends Thing {
          @Attribute
          private int price;
      
          House(@Attribute(name="price") int price) {
              this.price = price;
          }
      
          public int getPrice() {
              return this.price;
          }
          @Override
          public String toString() {
              return "ID: " + id + " House: " + price;
          }
      }
      
      
      // a class with a list of base class instances
      @Root(name="ListOfThings")
      public class Example4 {
      
          // specify the derived classes used in the list
          @ElementListUnion({
              @ElementList(entry="house", inline=true, type=House.class),
              @ElementList(entry="car", inline=true, type=Car.class)
          })
          private ArrayList<Thing> list = new ArrayList<Thing>();
      
          public void add(Thing t) {
              list.add(t);
          }
      
          public List<Thing> getProperties() {
              return list;
          }
      
          @Override
          public String toString() {
              StringBuilder sb = new StringBuilder();
              sb.append("Example4 contains " + list.size() + " elements:\n");
              for (Thing t : list) {
                  sb.append(" " + t.toString() + "\n");
              }
              return sb.toString();
          }
      
      
          //////////////////////////////////
          // test code
          //////////////////////////////////
          static String msg;
          static Example4 res;
      
          static public String getMessage() {
              String m = msg;
              msg = null;
              return m;
          }
      
          static private Example4 createDummy() {
              Example4 d = new Example4();
              d.add(new Car("Mercedes"));
              d.add(new House(34000000));
              d.add(new Car("VW"));
              d.add(new House(230000));
              return d;
          }
      
          //////////////////////////////////
          // serialize / deserialize
          //////////////////////////////////
          static public boolean write(String path) {
      
              Serializer serializer = new Persister();
              File result = new File(path);
              Example4 example = Example4.createDummy();
      
              try {
                  serializer.write(example, result);
              } catch (Exception e) {
                  e.printStackTrace();
                  msg = e.getMessage();
                  return false;
              }
              return true;
          }
      
          static public boolean read(String path) {
              Serializer serializer = new Persister();
              File source = new File(path);
      
              try {
                  res = serializer.read(Example4.class, source);
              } catch (Exception e) {
                  e.printStackTrace();
                  msg = e.getMessage();
                  return false;
              }
              return true;
          }
      
          public static Object getResult() {
              return res;
          }
      }