JAXB和继承

时间:2013-05-07 22:35:37

标签: jaxb

我正在尝试读取一个JSON文件,如:

{
  "a": "abc",
  "data" : {
      "type" : 1,
      ...
  }
}

其中......部分可根据类型替换:

{
  "a": "abc",
  "data" : {
      "type" : 1,
      "b" : "bcd"
  }
}

或:

{
  "a": "abc",
  "data" : {
      "type" : 2,
      "c" : "cde",
      "d" : "def",
  }
}

对于我的生活,我无法弄清楚用于实现这一目的的正确的JAXB注释/类。 如果需要,我没有在数据块之外移动类型变量的问题。

我正在使用Glassfish 3.1.2.2。

编辑:

根据Perception提供的代码,我做了一个快速尝试......虽然在glassfish中不起作用:

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = As.PROPERTY, property = "type")
@JsonSubTypes(
{
    @JsonSubTypes.Type(value = DataSubA.class, name = "1"),
    @JsonSubTypes.Type(value = DataSubB.class, name = "2") 
})
@XmlRootElement
public abstract class Data implements Serializable 
{
    private static final long serialVersionUID = 1L;

    public Data() 
    {
        super();
    }
}

@XmlRootElement
@XmlAccessorType(XmlAccessType.NONE)
public class DataSubA 
    extends Data 
{
    private static final long serialVersionUID = 1L;

    @XmlElement
    private BigDecimal expenditure;

    public DataSubA() {
        super();
    }

    public DataSubA(final BigDecimal expenditure) {
        super();
        this.expenditure = expenditure;
    }

    @Override
    public String toString() {
        return String.format("%s[expenditure = %s]\n", 
                             getClass().getSimpleName(), getExpenditure());
    }

    public BigDecimal getExpenditure() {
        return expenditure;
    }

    public void setExpenditure(BigDecimal expenditure) {
        this.expenditure = expenditure;
    }
}

@XmlRootElement
@XmlAccessorType(XmlAccessType.NONE)
public class DataSubB 
    extends Data 
{
    private static final long serialVersionUID = 1L;

    @XmlElement
    private String name;

    @XmlElement
    private Integer age;

    public DataSubB() 
    {
        super();
    }

    public DataSubB(final String name, final Integer age) 
    {
        super();
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() 
    {
        return String.format("%s[name = %s, age = %s]\n", 
                             getClass().getSimpleName(), getName(), getAge());
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}

@XmlRootElement
@XmlAccessorType(XmlAccessType.NONE)
public class DataWrapper
{ 
    @XmlElement
    private Data data;

    public Data getData() {
        return data;
    }

    public void setData(Data data) {
        this.data = data;
    }
}

一个简单的POST将其收录:

@Stateless
@Path("x")
public class Endpoint
{
    @POST
    @Consumes(
    {
        MediaType.APPLICATION_JSON,
    })
    @Produces(
    {
        MediaType.APPLICATION_JSON,
    })
    public String foo(final DataWrapper wrapper)
    {
        return ("yay");
    }
}

当我传入JSON时:

{
    "data" : 
    {
        "type" : 1,
        "expenditure" : 1
    }
}

我收到的消息如下:

Can not construct instance of Data, problem: abstract types can only be instantiated with additional type information
 at [Source: org.apache.catalina.connector.CoyoteInputStream@28b92ec1; line: 2, column: 5] (through reference chain: DataWrapper["data"])

1 个答案:

答案 0 :(得分:11)

DataClass上添加一个指定所有子类的@XmlSeeAlso注释:

@XmlRootElement
@XmlSeeAlso({DataSubA.class, DataSubB.class})
public abstract class Data implements Serializable {

然后在每个子类上使用@XmlType注释指定类型名称。

@XmlType(name="1")
public class DataSubA extends Data {

<强>更新

注意:我是EclipseLink JAXB (MOXy)主管,是JAXB (JSR-222)专家组的成员。

JAXB(JSR-222)规范不包括JSON绑定。 JAX-RS允许您通过JAXB注释指定JSON映射的方式有多种:

  1. JAXB实现以及像Jettison这样将StAX事件转换为JSON的库(请参阅:http://blog.bdoughan.com/2011/04/jaxb-and-json-via-jettison.html
  2. 通过利用提供JSON绑定的JAXB impl(请参阅:http://blog.bdoughan.com/2011/08/json-binding-with-eclipselink-moxy.html
  3. 利用JSON绑定工具,支持某些JAXB元数据(即Jackson)。
  4. 由于您的模型似乎没有按预期对注释做出反应,我猜您正在使用方案3.下面我将演示解决方案,就像您使用方案2一样。

    <强> DataWrapper

    import javax.xml.bind.annotation.*;
    
    @XmlRootElement
    @XmlAccessorType(XmlAccessType.FIELD)
    public class DataWrapper { 
    
        private String a;
        private Data data;
    
    }
    

    数据

    import javax.xml.bind.annotation.*;
    
    @XmlAccessorType(XmlAccessType.FIELD)
    @XmlSeeAlso({DataSubA.class, DataSubB.class})
    public class Data {
    
    }
    

    <强> DataSubA

    import javax.xml.bind.annotation.XmlType;
    
    @XmlType(name="1")
    public class DataSubA extends Data {
    
        private String b;
    
    }
    

    <强> DataSubB

    import javax.xml.bind.annotation.XmlType;
    
    @XmlType(name="2")
    public class DataSubB extends Data {
    
        private String c;
        private String d;
    
    }
    

    jaxb.properties

    要将MOXy指定为JAXB提供程序,您需要在与域模型相同的程序包中包含名为jaxb.properties的文件,并带有以下条目(请参阅:http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html):

    javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
    

    <强>演示

    import java.util.*;
    import javax.xml.bind.*;
    import javax.xml.transform.stream.StreamSource;
    import org.eclipse.persistence.jaxb.JAXBContextProperties;
    
    public class Demo {
    
        public static void main(String[] args) throws Exception {
            Map<String, Object> properties = new HashMap<String, Object>();
            properties.put(JAXBContextProperties.MEDIA_TYPE, "application/json");
            properties.put(JAXBContextProperties.JSON_INCLUDE_ROOT, false);
            JAXBContext jc = JAXBContext.newInstance(new Class[] {DataWrapper.class}, properties);
    
            Unmarshaller unmarshaller = jc.createUnmarshaller();
            StreamSource json = new StreamSource("src/forum16429717/input.json");
            DataWrapper dataWrapper = unmarshaller.unmarshal(json, DataWrapper.class).getValue();
    
            Marshaller marshaller = jc.createMarshaller();
            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
            marshaller.marshal(dataWrapper, System.out);
        }
    
    }
    

    <强> input.json /输出

    MOXy可以读取数值2作为继承指示符,但目前它总是将其写为"2"。我已打开以下增强请求来解决此问题:http://bugs.eclipse.org/407528

    {
       "a" : "abc",
       "data" : {
          "type" : "2",
          "c" : "cde",
          "d" : "def"
       }
    }
    

    了解更多信息

    以下链接将帮助您在JAX-RS实现中使用MOXy。