【问题标题】:How do I, using XStream, serialize/deserialize objects in a type hierarchy?如何使用 XStream 序列化/反序列化类型层次结构中的对象?
【发布时间】:2013-08-02 13:24:28
【问题描述】:

我正在使用 XStream。目前,用其他东西替换 XStream 并不容易。

我有一个接口 (MyInterface) 和几个实现该接口的子类(在下面的示例代码中,有一个称为 MyImplementation)。

我想序列化和反序列化子类的实例。我发现如果我将类属性放入 XML 中,我可以很好地反序列化:

<myInterfaceElement class="myPackage.MyImplementation">
  <field1>value1</field1>
  <field2>value2</field2>
</myInterfaceElement>

但是,我不知道如何让 XStream 写入类属性。序列化时如何让 XStream 包含类属性?或者是否有另一种方法来序列化/反序列化类层次结构,以便所有实现的元素名称相同,并且每个子类都可以定义自己的字段?

这里有一个 MyInterface、MyImplementation 的示例,这是一个试图使其工作的 JUnit 测试用例。 deserializeWithClassAttribute 测试通过,而 classAttributeSetInResult 失败。


package myPackage;

public interface MyInterface {

}

package myPackage;

public class MyImplementation implements MyInterface {
    public String field1;
    public String field2;

    public MyImplementation(String field1, String field2) {
        this.field1 = field1;
        this.field2 = field2;
    }
}

package myPackage;
import org.junit.Test;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;

import static org.junit.Assert.*;


public class xstreamTest {
    @Test
    public void classAttributeSetInResult() {
        MyInterface objectToSerialize = new MyImplementation("value1", "value2");

        final XStream xStream = new XStream(new DomDriver());
        xStream.alias("myInterfaceElement", MyInterface.class);

        String xmlResult = xStream.toXML(objectToSerialize).toString();

        String expectedResult = 
"<myInterfaceElement class=\"myPackage.MyImplementation\">\n" +
"  <field1>value1</field1>\n" +
"  <field2>value2</field2>\n" +
"</myInterfaceElement>";

        assertEquals(expectedResult, xmlResult);
    }

    @Test
    public void deserializeWithClassAttribute() {
        String inputXmlString = 
"<myInterfaceElement class=\"myPackage.MyImplementation\">\r\n" +
"  <field1>value1</field1>\r\n" +
"  <field2>value2</field2>\r\n" +
"</myInterfaceElement>";

        final XStream xStream = new XStream(new DomDriver());

        MyInterface result = (MyInterface)xStream.fromXML(inputXmlString);
        assertTrue("Instance of MyImplementation returned", result instanceof MyImplementation);

        MyImplementation resultAsMyImplementation = (MyImplementation)result;
        assertEquals("Field 1 deserialized", "value1", resultAsMyImplementation.field1);
        assertEquals("Field 2 deserialized", "value2", resultAsMyImplementation.field2);
    }
}

【问题讨论】:

    标签: java polymorphism xstream


    【解决方案1】:

    我通过以下操作解决了这个问题(感谢 McD 提示使用转换器):

    1. 添加一个扩展 ReflectionConverter 的自定义转换器:

      package myPackage;
      
      import com.thoughtworks.xstream.converters.MarshallingContext;
      import com.thoughtworks.xstream.converters.reflection.ReflectionConverter;
      import com.thoughtworks.xstream.converters.reflection.ReflectionProvider;
      import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
      import com.thoughtworks.xstream.mapper.Mapper;
      
      public class MyInterfaceConverter extends ReflectionConverter {
      
          public MyInterfaceConverter(Mapper mapper, ReflectionProvider reflectionProvider) {
              super(mapper, reflectionProvider);
          }
      
          @Override
          public void marshal(Object original, final HierarchicalStreamWriter writer, final MarshallingContext context) {
              writer.addAttribute("class", original.getClass().getCanonicalName());
              super.marshal(original, writer, context);
          }
      
          @SuppressWarnings("rawtypes")
          @Override
          public boolean canConvert(Class type) {
              return MyInterface.class.isAssignableFrom(type);
          }
      
      }
      
    2. 在我设置 xStream 时注册新的转换器:

      @Test
      public void classAttributeSetInResult() {
          MyInterface objectToSerialize = new MyImplementation("value1", "value2");
      
          final XStream xStream = new XStream(new DomDriver());
          xStream.alias("myInterfaceElement", MyImplementation.class);
          xStream.registerConverter(new MyInterfaceConverter(xStream.getMapper(), xStream.getReflectionProvider()));
      
          String xmlResult = xStream.toXML(objectToSerialize).toString();
      
          String expectedResult = 
              "<myInterfaceElement class=\"myPackage.MyImplementation\">\n" +
              "  <field1>value1</field1>\n" +
              "  <field2>value2</field2>\n" +
              "</myInterfaceElement>";
      
          assertEquals(expectedResult, xmlResult);
      }
      

    希望这将有助于其他人。如果有人有更好的想法,请告诉我!

    【讨论】:

    • 当然,你是对的。对像这些简单的类使用“标准”XStream-serialization-mechanisms 要好得多。我从一个需要自定义转换器的更复杂的类中选择了我的示例。
    • 好主意。我发现手动添加类名作为属性会产生异常,因为它已经由 XStream 自动添加。将编辑您的答案以包含解组方法的示例。
    【解决方案2】:

    我会使用自定义转换器来解决这个问题:

    你的类/接口:

    public static interface MyInterface {
        public String getField1();
        public String getField2();
    }
    
    public static class MyImplementation implements MyInterface {
        public String field1;
        public String field2;
    
        public MyImplementation(String field1, String field2) {
            this.field1 = field1;
            this.field2 = field2;
        }
    
        public String getField1() { return field1; }
        public String getField2() { return field2; }
    }
    

    相当快速和肮脏的转换器:

    public static class MyInterfaceConverter implements Converter {
        private static final String ATTR_NAME_CLASS  = "concrete-class";
        private static final String NODE_NAME_FIELD1 = "field1";
        private static final String NODE_NAME_FIELD2 = "field2";
    
        public boolean canConvert(Class type)
        {
            return type.equals(MyImplementation.class);
        }
    
        public void marshal(Object obj, HierarchicalStreamWriter writer, MarshallingContext context)
        {
            if (obj == null)
                // no need to save null-objects
                return;
    
            final String type = obj.getClass().getSimpleName();
            final MyInterface myInterface;
            if (obj instanceof MyImplementation)
                myInterface = (MyInterface) obj;
            // else if (...)
            //      ...
            else
                throw new IllegalArgumentException("Cannot convert objects of type " + obj.getClass());
    
            writer.addAttribute(ATTR_NAME_CLASS, type);
            marshalAttribute(writer, context, NODE_NAME_FIELD1, myInterface.getField1());
            marshalAttribute(writer, context, NODE_NAME_FIELD2, myInterface.getField2());
        }
    
        private static void marshalAttribute(HierarchicalStreamWriter writer, MarshallingContext context, String attrName, Object val)
        {
            if (val != null) {
                writer.startNode(attrName);
                context.convertAnother(val);
                writer.endNode();
            }
        }
    
        public Object unmarshal(HierarchicalStreamReader reader,
                UnmarshallingContext context)
        {
            final String type = reader.getAttribute(ATTR_NAME_CLASS);
            String field1Value = null, field2Value = null;
            while (reader.hasMoreChildren()) {
                reader.moveDown();
                if (NODE_NAME_FIELD1.equals(reader.getNodeName()))
                    field1Value = (String)context.convertAnother(null, String.class);
                else if (NODE_NAME_FIELD2.equals(reader.getNodeName()))
                    field2Value = (String)context.convertAnother(null, String.class);
                reader.moveUp();
            }
    
            if (MyImplementation.class.getSimpleName().equals(type)) {
                return new MyImplementation(field1Value, field2Value);
            }
            throw new IllegalArgumentException("Cannot unmarshal objects of type " + type);
        }
    }
    

    测试/使用/XStream-Initialization:

    @Test
    public void classAttributeSetInResult() {
        MyInterface objectToSerialize = new MyImplementation("value1", "value2");
    
        final XStream xStream = new XStream(new DomDriver());
        xStream.alias("myInterfaceElement", MyImplementation.class);
        // xStream.alias("myInterfaceElement", OtherImplementation.class);
        xStream.registerConverter(new MyInterfaceConverter());
    
        String xmlResult = xStream.toXML(objectToSerialize).toString();
    
        String expectedResult = 
    "<myInterfaceElement concrete-class=\"MyImplementation\">\r\n" +
    "  <field1>value1</field1>\r\n" +
    "  <field2>value2</field2>\r\n" +
    "</myInterfaceElement>";
    
        assertEquals(expectedResult, xmlResult);
    }
    
    @Test
    public void deserializeWithClassAttribute() {
        String inputXmlString = 
    "<myInterfaceElement concrete-class=\"MyImplementation\">\r\n" +
    "  <field1>value1</field1>\r\n" +
    "  <field2>value2</field2>\r\n" +
    "</myInterfaceElement>";
    
        final XStream xStream = new XStream(new DomDriver());
        xStream.alias("myInterfaceElement", MyImplementation.class);
        // xStream.alias("myInterfaceElement", OtherImplementation.class);
        xStream.registerConverter(new MyInterfaceConverter());
    
        MyInterface result = (MyInterface)xStream.fromXML(inputXmlString);
        assertTrue("Instance of MyImplementation returned", result instanceof MyImplementation);
    
        MyImplementation resultAsMyImplementation = (MyImplementation)result;
        assertEquals("Field 1 deserialized", "value1", resultAsMyImplementation.field1);
        assertEquals("Field 2 deserialized", "value2", resultAsMyImplementation.field2);
    }
    

    【讨论】:

    • 谢谢 MrD。如果对于 MyInterface 接口的每个新实现,我修改 MyInterfaceConverter,这看起来会起作用。由于每个实现也可以有它自己的字段属性,这也意味着 MyInterfaceConverter 需要对所有这些实现的深入了解。我认为这对我的需求不切实际。我想知道是否有办法通过更多的反思来做到这一点?但是假设我添加了这些反射,那不是真的重复了 XStream 应该为我做的事情吗?
    猜你喜欢
    • 1970-01-01
    • 2013-01-10
    • 1970-01-01
    • 2013-03-24
    • 2015-04-13
    • 2023-03-05
    • 1970-01-01
    • 2019-04-09
    • 2016-05-27
    相关资源
    最近更新 更多