Do not include few parent class elements when XML constructed from child - jaxb

Is it possible to not have few fields from parent class when XML is constructed out of the child class?
But the elements should be present when XML is constructed from parent class?
Example
Parent class
#XmlRootElement(name = "location")
#XmlType(propOrder = { "id", "name" })
#JsonPropertyOrder({ "id", "name" })
public class Parent {
private Integer id;
private String name;
#XmlElement(name = "id", nillable = true)
#JsonProperty("id")
public Integer getId() {
return super.getId();
}
#JsonProperty("id")
public void setId(Integer id) {
super.setId(id);
}
#XmlElement(name = "name", nillable = true)
#JsonProperty("name")
public String getName() {
return name;
}
#JsonProperty("name")
public void setName(String name) {
this.name = name;
}
}
Child class
#XmlRootElement(name = "location")
#XmlType(propOrder = { "id" })
#JsonPropertyOrder({ "id" })
public class Child extends Parent {
#XmlElement(name = "id", nillable = true)
#JsonProperty("id")
public Integer getId() {
return super.getId();
}
#JsonProperty("id")
public void setId(Integer id) {
super.setId(id);
}
}
I do not want the name field when XML is constructed from child class. However it should be present when XML is constructed from parent class.

Try to override the getter and setter for name in the subclass and annotate them with #JsonIgnore and/or #XmlTransient.
EDIT
Indeed, #XmlTransient does not work with polymorphism as I expected (and as #JsonIgnore do). What you can try is:
-- move all content of Parent class to an abstract Base class
-- mark Base as #XmlTransient
-- make Parent extend Base and add no content to it
-- make Child extend Base
Here is some synthetic example I worked on. It can easily be translated to your particular classes.
Class Base
#XmlRootElement(name = "location")
#XmlSeeAlso(value = {Parent.class, Child.class})
#XmlTransient
public abstract class Base {
private String a;
private String b;
#XmlElement(name = "a")
public String getA() {
return a;
}
public void setA(String a) {
this.a = a;
}
#XmlElement(name = "b", nillable = true)
public String getB() {
return b;
}
public void setB(String b) {
this.b = b;
}
}
Class Parent
#XmlRootElement(name = "location")
#XmlType
public class Parent extends Base {
}
Class Child
#XmlRootElement(name = "location")
#XmlType
public class Child extends Base {
private String c;
#XmlElement(name = "c")
public String getC() {
return c;
}
public void setC(String c) {
this.c = c;
}
#Override
#XmlTransient
public String getB(){
return super.getB();
}
#Override
public void setB(String b) {
super.setB(b);
}
}
Obviously, if the class hierarchy grows larger, it may be harder to maintain such a workaround. In those cases, you may think about choosing composition rather than inheritance.

Related

JAXB define java class for a xml file with attributes in nested elements

I want to define a java class and then use JAXB to marshalling its instances to a xml file.
The output I want looks like:
<paths>
<path action="R" kind="file" copyfrom-path="file1" copyto-path="file2">file2</path>
<path action="M" kind="file">file3</path>
</paths>
I defined a java class as follows:
#XmlRootElement(name = "paths")
#XmlAccessorType(FIELD)
public class changed_paths
{
private List<String> path;
public changed_paths()
{
path = new ArrayList<String>();
}
public List<String> getPath()
{
return path;
}
public void setPath(List<String> path)
{
this.path = path;
}
public void addPath(String p)
{
path.add(p);
}
}
Using the above java class, I can generate output xml file without the attributes of <path></path> elements. Like below:
<paths>
<path>file2</path>
<path>file3</path>
</paths>
I tried to define the attributes in changed_paths class like below :
#XmlAttribute
private String kind;
public void setKind(String kind){
this.kind = kind;
}
public String getKind(){
return this.kind;
}
But this will output a xml file with attributes "kind" in tag <paths></paths> but not in its nested <path></path> tags.
The other problem is that when the attribute name contains "-" (e.g. copyfrom-path), java won't allow me to define such variables with "-" in its name.
Can someone please tell me how to define:
1. attributes in <path></path>?
2. attributes with "-" in their names?
Can someone please give me some help?
Thank you very much!
I find the answers. To create attributes for <path></path>, I defined class path for this tag. And defined another class paths for <paths></paths> .
To create attributes with "-" in their names, I use annotation #XmlAttribute(name = "copyfrom-path")
#XmlAccessorType(FIELD)
public class path
{
#XmlAttribute
private String kind;
#XmlAttribute
private String action;
#XmlAttribute(name = "copyfrom-path")
private String copyfrom;
#XmlAttribute(name = "copyfrom-rev")
private String copyto;
#XmlValue
private String value;
public void setKind(String kind)
{
this.kind = kind;
}
public String getKind()
{
return this.kind;
}
public void setAction(String action)
{
this.action = action;
}
public String getAction()
{
return this.action;
}
public void setValue(String value)
{
this.value = value;
}
public String getValue()
{
return this.value;
}
public void setCopyfrom(String p)
{
this.copyfrom = p;
}
public String getCopyfrom()
{
return this.copyfrom;
}
public void setCopyto(String p)
{
this.copyto = p;
}
public String getCopyto()
{
return this.copyto;
}
}
#XmlRootElement(name = "paths")
#XmlAccessorType(FIELD)
public class paths
{
private List<path> paths;
public paths()
{
paths = new ArrayList<path>();
}
public List<path> getPaths()
{
return paths;
}
public void setPaths(List<path> paths)
{
this.paths = paths;
}
public void addPath(path p)
{
paths.add(p);
}
}

#XmlAttribute/#XmlValue need to reference a Java type that maps to text in XML

how to pick the value of an attribute 'name' which is a PriceEventName class type in the below case, FYI if i put #XmlAttribute above it this is turn out to an exception "an error #XmlAttribute/#XmlValue need to reference a Java type that maps to text in XML"
I looking heavily on the internet but I didn't find something similar to my case
PriceEvent class
package somepackage
import ...
import
#XmlAccessorType(XmlAccessType.FIELD)
public class PriceEvent {
#XmlElement(name="Message",namespace="someValue")
private String color;
private PriceEventName name;// this is an attribute
.
.
}
PriceEventName class
Imports ...
public class PriceEventName {
public static final int PRICEUPDATE_TYPE = 0;
public static final PriceEventName PRICEUPDATE = new PriceEventName(PRICEUPDATE_TYPE, "X-mas");
private static java.util.Hashtable _memberTable = init();
private static java.util.Hashtable init() {
Hashtable members = new Hashtable();
members.put("X-mas", PRICEUPDATE);
return members;
}
private final int type;
private java.lang.String stringValue = null;
public PriceEventName(final int type, final java.lang.String value) {
this.type = type;
this.stringValue = value;
}
public static PriceEventName valueOf(final java.lang.String string) {
java.lang.Object obj = null;
if (string != null) {
obj = _memberTable.get(string);
}
if (obj == null) {
String err = "" + string + " is not a valid PriceEventName";
throw new IllegalArgumentException(err);
}
return (PriceEventName) obj;
}
}
This is how you declare the field as an attribute with an adapter:
#XmlJavaTypeAdapter(PenAdapter.class)
#XmlAttribute
protected PriceEventName name;
public PriceEventName getName() { return name; }
public void setName(PriceEventName value) { this.name = value; }
Add you'll need to add a getter to PriceEventName:
public String getStringValue(){ return stringValue; }
And here is the adapter class:
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class PenAdapter extends XmlAdapter<String,PriceEventName> {
public PriceEventName unmarshal(String v) throws Exception {
return PriceEventName.valueOf( v );
}
public String marshal(PriceEventName v) throws Exception {
return v.getStringValue();
}
}

groovy not recognizing java getters/setters through field syntax

I have the following inheritance hierarchy defined in java.
public class BaseModel extends HashMap<String, Object> {
public String getString(String key) {
return (String)this.getOrDefault(key, "EMPTY");
}
}
public class Entity extends BaseModel {
private String id;
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
Now in a groovy script I try to do the following:
Entity entity = new Entity();
entity.id = "101";
entity.name = "Apple"
and "id" and "name" are not recognized. The funny thing is they are recognized if I do one of the following:
not inherit Entity from BaseModel
Rather than inherit BaseModel from HashMap, make HashMap a data member of BaseModel
inherit Entity directly from HashMap
At first I thought that groovy is not recognizing the "id" and "name" syntax because of extending HashMap, but #3 above proves that incorrect. I am stumped as to why this is not being recognized at this point. Can someone help me out? It should be easy enough to copy paste this and try it out yourself.
The problem seems to be the setters and getters inside the Entity Class, everything in groovy is public and it creates all the getters and setters methods.
I tested the next code in the groovy console and it worked.
public class BaseModel extends HashMap<String, Object> {
public String getString(String key) {
return (String)this.getOrDefault(key, "EMPTY");
}
}
public class Entity extends BaseModel {
private String id;
private String name;
}
Entity entity = new Entity();
entity.id = "101";
entity.name = "Apple"
println entity.id
It prints 101 in the groovyConsole output screen.
When Entity is extending from BaseModel or directly a HashMap, Entity becomes a Map. So, when we say entity.id, Groovy is trying to find an entry in the map whose key is 'id'. As there is no such entry, it prints out null.
public class Entity extends HashMap<String, String> {
private String id;
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
Entity entity = new Entity();
entity.id = "101";
entity.name = "Apple"
println entity.id //prints null
But when Entity is not extending from BaseModel anymore, entity.id will be interpreted just as a member of Entity.
public class Entity {
private String id;
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
Entity entity = new Entity();
entity.id = "101";
entity.name = "Apple"
println entity.id //prints 101

JAXB Marshalling: Creating an empty element with an attribute

I use a JAXB marshaller and I would like to add an empty element with a specific attribute. This is a dummy class:
#XmlRootElement(name="observation")
public class Observation {
#XmlAttribute
public static final String classCode = "OBS";
#XmlAttribute
public static final String moodCode = "EVN";
private String data;
#XmlElement
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
This creates the following XML:
<observation classCode="OBS" moodCode="EVN">
<data>fsdfsdfd</data>
</observation>
Is there any way to add a new element with a specific attribute only (no value at all)? E.g.
<observation classCode="OBS" moodCode="EVN">
<templateId root="2.16.840.1.113883.10.20.1.31"/>
<data>fsdfsdfd</data>
</observation>
This should do it:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "TemplateIdType")
public class TemplateIdType {
#XmlAttribute(name = "root")
protected String root;
// getter and setter
}
(And you add an element of this class to Observation.)

JAXB and inheritance

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"
}
}
or:
{
"a": "abc",
"data" : {
"type" : 2,
"c" : "cde",
"d" : "def",
}
}
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.
I'm using Glassfish 3.1.2.2.
Edit:
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;
}
}
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");
}
}
When I pass in JSON like:
{
"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"])
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 {
Then on each of the subclasses use the #XmlType annotation to specify the type name.
#XmlType(name="1")
public class DataSubA extends Data {
UPDATE
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.
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:
A JAXB implementation plus a library like Jettison that converts StAX events to JSON (see: http://blog.bdoughan.com/2011/04/jaxb-and-json-via-jettison.html)
By leveraging a JAXB impl that offers a JSON-binding (see: http://blog.bdoughan.com/2011/08/json-binding-with-eclipselink-moxy.html)
Leveraging a JSON-binding tool that offers support for some JAXB metadata (i.e Jackson).
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.
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 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
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 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"
}
}
For More Information
The following link will help you use MOXy in a JAX-RS implementation.
http://blog.bdoughan.com/2012/05/moxy-as-your-jax-rs-json-provider.html

Resources