-->

Polymorphism in XStream serialization and deserial

2020-07-20 03:10发布

问题:

I have these classes:

@XStreamAlias("person")
public class PersonConfig {

    private AnimalConfig animalConfig;

}

public interface AnimalConfig {}

@XStreamAlias("dog");
public class DogConfig extend AnimalConfig {}

@XStreamAlias("cat");
public class CatConfig extend AnimalConfig {}

And I would like to be able to deserialize this xml with the classes above:

<person>
    <dog/>
<person>

As well as deserialize this xml too, with the same classes:

<person>
    <cat/>
<person>

So that in both cases, the PersonConfig's field animalConfig is filled. In the first XML with a DogConfig instance and in the second XML with a CatConfig instance.

Is this possible by adding some annotation to make this work?

回答1:

It seems XStream does not allow you to do it easily.

Your question is similar to this one, asking for managing something like a xsd:choice with XStream.

If you don't necessarily need to use XStream, JAXB will allow you to do it easily :

@XmlRootElement(name="person")
public class PersonConfig {

    private AnimalConfig animalConfig;

    @XmlElementRefs({
        @XmlElementRef(name="cat", type=CatConfig.class),
        @XmlElementRef(name="dog", type=DogConfig.class)
    })
    public AnimalConfig getAnimalConfig() {
        return animalConfig;
    }

    public void setAnimalConfig(AnimalConfig animalConfig) {
        this.animalConfig = animalConfig;
    }
}

After some researches, listing all available classes for your property can be avoided if you choose to use the XmlAdapter. In Blaise Doughan link, the example uses an abstract class, not an interface.

Edit :

As Blaise Doughan said in its comment, @XmlElementRef is better suited for this purpose. Code has been updated accordingly.



回答2:

You can write a converter.

public class CustomConverter implements Converter {

   public void marshal(Object source, HierarchicalStreamWriter writer,
        MarshallingContext context) {
     // TODO: Get annotation value from object 'source' with name of tag via Reflection.
     // Or add a method to the AnimalConfig interface giving you tag name to put to serialization output.
   }

   public Object unmarshal(HierarchicalStreamReader reader,
        UnmarshallingContext context) {
     // TODO: use reflection to create animal object based on what you xml tag you have at hahd.
     return context.convertAnother(context.currentObject(), SomeAnimalClazz.class);
   }

   public boolean canConvert(Class type) {
     return type.equals(AnimalConfig.class);
   }
 }

There's a disadvantage: polymorphism will require you to use Java Reflection API and performance degradation.



回答3:

This is quite easy. You just have to do it right and not like my previous speakers. When you process the annotations, XStream can assign those classes.

@XStreamAlias("person")
public class PersonConfig {

    private AnimalConfig animalConfig;

    public String toXml() {
        XStream xstream = new XStream();
        xstream.processAnnotations(DogConfig.class);
        xstream.processAnnotations(CatConfig.class);
        return xstream.toXML(this);
    }
}

public interface AnimalConfig {}

@XStreamAlias("dog");
public class DogConfig implements AnimalConfig {}

@XStreamAlias("cat");
public class CatConfig implements AnimalConfig {}


回答4:

It works out of the box, with out any annotations...

private static interface Test {
    String getName();

    Params getParams();
}

private static interface Params {
}

private static class OneParams implements Params {
    private String oneValue;

    public String getOneValue() {
        return oneValue;
    }

    public void setOneValue(String oneValue) {
        this.oneValue = oneValue;
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("OneParams [oneValue=");
        builder.append(oneValue);
        builder.append("]");
        return builder.toString();
    }

}

private static class TwoParams implements Params {
    private String twoValue;

    public String getTwoValue() {
        return twoValue;
    }

    public void setTwoValue(String twoValue) {
        this.twoValue = twoValue;
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("TwoParams [twoValue=");
        builder.append(twoValue);
        builder.append("]");
        return builder.toString();
    }

}

private static class OneTest implements Test {
    private String name;

    private Params params;

    @Override
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public Params getParams() {
        return params;
    }

    public void setParams(Params params) {
        this.params = params;
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("OneTest [name=");
        builder.append(name);
        builder.append(", params=");
        builder.append(params);
        builder.append("]");
        return builder.toString();
    }

}

---- now deserialize like this...

    System.out
            .println(ser
                    .deserialize("<XStreamTest_-OneTest><name>OneTest</name><params class=\"XStreamTest$OneParams\"><oneValue>1</oneValue></params></XStreamTest_-OneTest>"));

    System.out
            .println(ser
                    .deserialize("<XStreamTest_-OneTest><name>TwoTest</name><params class=\"XStreamTest$TwoParams\"><twoValue>2</twoValue></params></XStreamTest_-OneTest>"));


标签: java xstream