简体   繁体   中英

Change Element tag based on the concrete implementation of Abstract class value

Say I have an abstract class named AbstractItem that is used as a field in another class. When I use XStream to generate the XML I want the element tag to be based on the concrete implementation of the instance of AbstractItem .

What I get:

<Test>
  <item class="Item1" name="name 1" description="description 1"/>
</Test>

What I want:

<Test>
  <Item1 name="name 1" description="description 1"/>
</Test>

I tried setting the alias on the XStream instance by doing:

stream.alias("Item1", Item1.class);

and also using:

stream.aliasType("Item1", Item1.class);

Neither one of the above worked.


For the sake of clarity here is a runnable example of the above:

Test.java

import com.thoughtworks.xstream.annotations.XStreamAsAttribute;

public abstract class AbstractItem {
    @XStreamAsAttribute
    public String name;
}

AbstractItem.java

import com.thoughtworks.xstream.annotations.XStreamAsAttribute;

public class Item1 extends AbstractItem {
    @XStreamAsAttribute
    public String description;
}

Item1.java

 import com.thoughtworks.xstream.annotations.XStreamAsAttribute; public class Item1 extends AbstractItem { @XStreamAsAttribute public String description; } 


UPDATE: I have attempted to do this using a converter class, but it still is not right:

 stream.registerConverter( new Converter(){ @Override public boolean canConvert(Class type) { if (AbstractItem.class.isAssignableFrom(type)){ return true; } return false; } @Override public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) { AbstractItem item = (AbstractItem)source; if(source instanceof Item1){ writer.startNode("Item1"); writer.addAttribute("description",((Item1)item).description); } else if(source instanceof Item2){ writer.startNode("Item2"); writer.addAttribute("description", ((Item2)item).description); } else { writer.startNode("Item"); } writer.addAttribute("name", item.name); writer.endNode(); } @Override public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { // TODO Auto-generated method stub AbstractItem item = null; String nodeName = reader.getNodeName(); if (nodeName.equals("Item1")){ item = new Item1(); ((Item1)item).description = reader.getAttribute("description"); } else if (nodeName.equals("Item2")){ item = new Item2(); ((Item2)item).description = reader.getAttribute("description"); } item.name = reader.getAttribute("name"); return item; } }); 

The result I get now is:

 <Test> <item class="Item1"> <Item1 description="description 1" name="name 1"/> </item> </Test> 

I found the that the only way to accomplish this is with a custom converter for the class containing the Object I want to manipulate the element tag for. In the example in the question it would be a custom converter for the Test classand it would look like:

import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;

public class BasicConverter implements Converter {

    @Override
    public boolean canConvert(Class type) {
        return Test.class.isAssignableFrom(type);
    }

    @Override
    public void marshal(Object source, HierarchicalStreamWriter writer,
            MarshallingContext context) {
        if (((Test) source).item instanceof Item1) {
            writer.startNode("Item1");
            writer.addAttribute("description", ((Item1)((Test) source).item).description);
        } else if (((Test) source).item instanceof Item2) {
            writer.startNode("Item2");
            writer.addAttribute("description", ((Item2)((Test) source).item).description);
        }
        writer.addAttribute("name", ((Test) source).item.name);
        writer.endNode();
    }

    @Override
    public Object unmarshal(HierarchicalStreamReader reader,
            UnmarshallingContext context) {
        Test test = new Test();

        reader.moveDown();
        String nodeName = reader.getNodeName();
        AbstractItem item = null;       
        if (nodeName.equals("Item1")) {
            item = new Item1();
            ((Item1)item).description = reader.getAttribute("description");
        } else if (nodeName.equals("Item2")) {
            item = new Item2();
            ((Item2)item).description = reader.getAttribute("description");
        }   
        item.name = reader.getAttribute("name");    
        ((Test)test).item = item;
        reader.moveUp();
        return test;
    }
}

This gives the output I was looking for above, but to me is not really satisfactory. The reason being, is that the actual class I need to use this for has tons of fields, some that use their own custom converters, custom alias, etc. Plus it will essentially disregard all the annotations on the Test class, besides those defined at the class level. Plus as your class grows you have to update this converter to handle those new fields, or they will be included.

Ideally I would like a converter that does everything as defined by annotations, except for certain fields. There currently is not one that I know of. What I am in the process of doing is extending the com.thoughtworks.xstream.converters.reflection.ReflectionConverter class to accomplish this. But it requires copying more code from the underlying implementation then I particularly care for.

I am facing the same problem with XStream. I have previously used Commons digester which uses reflection to read the xml. This will give you the functionality that you request when reading your xml. The downside is that it isn't able to write the xml. I prefered the digester due to that there was no need to change anything in the java classes (no annotation or converter stuff) and it is quit easy to set up the rules for parsing the xml. But then again it hasn't all the functionality that you get from XStream or Jaxb. Right now I have to solve this problem using XStream (or Jaxb) so I guess I have to write the needed converter stuff. Thanks for your example.

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