JAXB 和继承 [英] JAXB and inheritance
问题描述
我正在尝试读取 JSON 文件,例如:
I am trying to read a JSON file like:
{
"a": "abc",
"data" : {
"type" : 1,
...
}
}
其中 ... 部分可根据以下类型替换:
where the ... part is replaceable based on the type like:
{
"a": "abc",
"data" : {
"type" : 1,
"b" : "bcd"
}
}
或:
{
"a": "abc",
"data" : {
"type" : 2,
"c" : "cde",
"d" : "def",
}
}
在我的一生中,我无法找出正确的 JAXB 注释/类来实现这一目标.如果需要,将类型变量移出数据块没有问题.
For the life of me I cannot figure out the proper JAXB annotations/classes to use to make this happen. I don't have an issue moving the type variable outside of the data block if needed.
我使用的是 Glassfish 3.1.2.2.
I'm using Glassfish 3.1.2.2.
基于 Perception 提供的代码,我做了一个快速的尝试......但在 glassfish 中不起作用:
Based on the code provided by Perception I did a quick attempt... doesn't work in glassfish though:
@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:
And a simple POST that takes it in:
@Stateless
@Path("x")
public class Endpoint
{
@POST
@Consumes(
{
MediaType.APPLICATION_JSON,
})
@Produces(
{
MediaType.APPLICATION_JSON,
})
public String foo(final DataWrapper wrapper)
{
return ("yay");
}
}
当我传入 JSON 时:
When I pass in JSON like:
{
"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"])
推荐答案
在 DataClass
上添加一个 @XmlSeeAlso
注释,指定所有子类:
On the DataClass
add an @XmlSeeAlso
annotation that specifies all of the subclasses:
@XmlRootElement
@XmlSeeAlso({DataSubA.class, DataSubB.class})
public abstract class Data implements Serializable {
然后在每个子类上使用 @XmlType
注释来指定类型名称.
Then on each of the subclasses use the @XmlType
annotation to specify the type name.
@XmlType(name="1")
public class DataSubA extends Data {
<小时>
更新
注意:我是 EclipseLink JAXB (MOXy) 领导和 JAXB (JSR-222) 的成员 专家组.
JAXB (JSR-222) 规范不包括 JSON 绑定.JAX-RS 允许您通过 JAXB 注释指定 JSON 映射的方式有多种:
The JAXB (JSR-222) specification doesn't cover JSON-binding. There are different ways JAX-RS allows you to specify JSON mapping via JAXB annotations:
- 一个 JAXB 实现加上一个像 Jettison 这样的库,可以将 StAX 事件转换为 JSON(参见:http://blog.bdoughan.com/2011/04/jaxb-and-json-via-jettison.html)
- 通过利用提供 JSON 绑定的 JAXB 实现(请参阅:http://blog.bdoughan.com/2011/08/json-binding-with-eclipselink-moxy.html)
- 利用 JSON 绑定工具来支持某些 JAXB 元数据(即 Jackson).
由于您的模型似乎没有对注释做出预期的反应,我猜您正在使用场景 3.下面我将演示解决方案,就像您使用场景 2 一样.
Since your model doesn't seem to be reacting as expected to the annotations I'm guessing you are using scenario 3. Below I will demonstrate the solution as if you were using scenario 2.
数据包装器
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):
To specify MOXy as your JAXB provider you need to include a file called jaxb.properties
in the same package as your domain model with the following entry (see: 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.
MOXy can read in the numeric value 2
as the inheritance indicator, but currently it will always write it out as "2"
. I have opened the following enhancement request to address this issue: http://bugs.eclipse.org/407528.
{
"a" : "abc",
"data" : {
"type" : "2",
"c" : "cde",
"d" : "def"
}
}
了解更多信息
以下链接将帮助您在 JAX-RS 实现中使用 MOXy.
The following link will help you use MOXy in a JAX-RS implementation.
这篇关于JAXB 和继承的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!