Jaxb and inheritance

I am trying to read a JSON file, for example:

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

where ... part changes depending on type type:

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

or:

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

In my life, I cannot find suitable JAXB annotations / classes to use to make this happen. I have no problem moving the type variable outside the data block, if necessary.

I am using Glassfish 3.1.2.2.

Edit:

Based on the code provided by Perception, I made a quick attempt ... does not work on a glass planet:

 @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; } } 

And a simple POST that takes it:

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

When I go into JSON, for example:

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

I get a message like:

 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"]) 
+6
source share
1 answer

In the DataClass add the DataClass annotation, which defines all subclasses:

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

Then, in each subclass, use the @XmlType annotation to indicate the type name.

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

UPDATE

Note. I am EclipseLink JAXB (MOXy) and a member of the JAXB Expert Group (JSR-222) .

The JAXB specification (JSR-222) does not apply to JSON binding. There are various ways in which JAX-RS allows you to specify JSON mapping through JAXB annotations:

Since your model does not respond as expected to annotations, I assume you are using scenario 3. Below I will demonstrate the solution as if you were using scenario 2.

Datawrapper

 import javax.xml.bind.annotation.*; @XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) public class DataWrapper { private String a; private Data 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

To specify MOXy as the JAXB provider, you need to include a file named jaxb.properties in the same package as your domain model, with the following entry (see: <a4> ):

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

Demo

 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 / output

MOXy can read in the numerical value of 2 as an indicator of inheritance, but currently it will always write it as "2" . I opened the following extension request to solve this problem: http://bugs.eclipse.org/407528 .

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

Additional Information

The following link will help you use MOXy in your JAX-RS implementation.

+11
source

Source: https://habr.com/ru/post/944486/


All Articles