I'm looking for a Java JSPON serializer that can handle references according to the JSPON specification.
Are there any available that can do this currently? Or is there any way to modify an existing serializer to handle object refs with the $ref notation?
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB 2 (JSR-222) expert group.
If you are interested in an object-JSON binding approach, below is how it could be done using MOXy. The example below is based on the example one from the JSPON Core Spec:
http://www.jspon.org/JSPON_Core_Spec.htm
Parent
The Parent class is the domain object that corresponds to the root of the JSON message. It has two fields that are of type Child.
package forum9862100;
import javax.xml.bind.annotation.*;
#XmlAccessorType(XmlAccessType.FIELD)
public class Parent {
protected Child field1;
protected Child field2;
}
Child
The Child class may be referenced by its key. We will handle this use case with an XmlAdapter. We link to an XmlAdapter via the #XmlJavaTypeAdapter annotation.
package forum9862100;
import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
#XmlJavaTypeAdapter(ChildAdapter.class)
#XmlAccessorType(XmlAccessType.FIELD)
public class Child {
protected String id;
protected String foo;
protected Integer bar;
}
ChildAdapter
Below is the implementation of the XmlAdapter. This XmlAdapter is stateful which means we will need to set an instance on the Marshaller and Unmarshaller.
package forum9862100;
import java.util.*;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class ChildAdapter extends XmlAdapter<ChildAdapter.AdaptedChild, Child>{
private List<Child> childList = new ArrayList<Child>();
private Map<String, Child> childMap = new HashMap<String, Child>();
public static class AdaptedChild extends Child {
#XmlElement(name="$ref")
public String reference;
}
#Override
public AdaptedChild marshal(Child child) throws Exception {
AdaptedChild adaptedChild = new AdaptedChild();
if(childList.contains(child)) {
adaptedChild.reference = child.id;
} else {
adaptedChild.id = child.id;
adaptedChild.foo = child.foo;
adaptedChild.bar = child.bar;
childList.add(child);
}
return adaptedChild;
}
#Override
public Child unmarshal(AdaptedChild adaptedChild) throws Exception {
Child child = childMap.get(adaptedChild.reference);
if(null == child) {
child = new Child();
child.id = adaptedChild.id;
child.foo = adaptedChild.foo;
child.bar = adaptedChild.bar;
childMap.put(child.id, child);
}
return child;
}
}
Demo
The code below demonstrates how to specify a stateful XmlAdapter on the Marshaller and Unmarshaller:
package forum9862100;
import java.io.File;
import javax.xml.bind.*;
import javax.xml.transform.stream.StreamSource;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Parent.class);
StreamSource json = new StreamSource(new File("src/forum9862100/input.json"));
Unmarshaller unmarshaller = jc.createUnmarshaller();
unmarshaller.setProperty("eclipselink.media-type", "application/json");
unmarshaller.setProperty("eclipselink.json.include-root", false);
unmarshaller.setAdapter(new ChildAdapter());
Parent parent = (Parent) unmarshaller.unmarshal(json, Parent.class).getValue();
System.out.println(parent.field1 == parent.field2);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.setProperty("eclipselink.media-type", "application/json");
marshaller.setProperty("eclipselink.json.include-root", false);
marshaller.setAdapter(new ChildAdapter());
marshaller.marshal(parent, System.out);
}
}
Output
Below is the output from running the demo code. Note how the two instances of Child pass the identity test.
true
{
"field1" : {
"id" : "2",
"foo" : "val",
"bar" : 4
},
"field2" : {
"$ref" : "2"
}}
For More Information
http://blog.bdoughan.com/2011/09/mixing-nesting-and-references-with.html
http://blog.bdoughan.com/2011/08/json-binding-with-eclipselink-moxy.html
I would use one of the many Object to JSon serialization libraries. Many of the libraries are extensible, but I suspect adding references could get complicated unless you make some pragmatic choices as to when to use these.
Related
I'm using MOXy 2.6 (JAXB+JSON).
I want ObjectElement and StringElement to be marshalled the same way, but MOXy creates wrapper object when fields are typed as Object.
ObjectElement.java
public class ObjectElement {
public Object testVar = "testValue";
}
StringElement.java
public class StringElement {
public String testVar = "testValue";
}
Demo.java
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import org.eclipse.persistence.jaxb.JAXBContextFactory;
import org.eclipse.persistence.jaxb.MarshallerProperties;
import org.eclipse.persistence.oxm.MediaType;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContextFactory.createContext(new Class[] { ObjectElement.class, StringElement.class }, null);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(MarshallerProperties.MEDIA_TYPE, MediaType.APPLICATION_JSON);
System.out.println("ObjectElement:");
ObjectElement objectElement = new ObjectElement();
marshaller.marshal(objectElement, System.out);
System.out.println();
System.out.println("StringElement:");
StringElement stringElement = new StringElement();
marshaller.marshal(stringElement, System.out);
System.out.println();
}
}
When launching Demo.java, here's the output ...
ObjectElement:
{"testVar":{"type":"string","value":"testValue"}}
StringElement:
{"testVar":"testValue"}
How to configure MOXy/JaxB to make ObjectElement render as StringElement object ? How to avoid the creation of object wrapper with "type" and "value" properties ?
you can use the Annotation javax.xml.bind.annotation.XmlAttribute. This will render ObjectElement and StringElement to the same output.
See the following example:
import javax.xml.bind.annotation.XmlAttribute;
public class ObjectElement {
#XmlAttribute
public Object testVar = "testValue";
}
I've used the following test class to verify the correct behaviour.
EDIT after the question was updated:
Yes it's possible. Instead of using XmlAttribute like before, I switched to javax.xml.bind.annotation.XmlElement in combination with a type attribute.
The class is now declared as:
public class ObjectElement {
#XmlElement(type = String.class)
public Object testVar = "testValue";
}
To learn how to use #XmlAnyElement, I created the following test service:
#WebService(serviceName = "TestServices")
#Stateless()
public class TestServices {
#WebMethod(operationName = "testMethod")
public ServiceResult testMethod() {
ServiceResult result = new ServiceResult();
result.addObject(new SimpleObj(1, 2));
result.addObject(new SimpleObj(3, 4));
return result;
}
}
SimpleObj is a simple class with 2 int fields. Below is the code for the ServiceResult class:
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
#XmlSeeAlso({SimpleObj.class})
public class ServiceResult {
#XmlAnyElement(lax = true)
private List<Object> body;
public void addObject(Object objToAdd) {
if (this.body == null)
this.body = new ArrayList();
this.body.add(objToAdd);
}
// Getters and Setters
}
To consume the above service, I created an appclient with the following Main class:
public class Main {
#WebServiceRef(wsdlLocation = "META-INF/wsdl/localhost_8080/TestServices/TestServices.wsdl")
private static TestServices_Service service;
private static TestServices port;
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
port = service.getAdminServicesPort();
ServiceResult result = port.testMethod();
for (Object o : result.getAny()) {
System.out.println("TEST: " + o);
}
}
}
Based on the documentation, with #XmlAnyElement, the unmarshaller will eagerly unmarshal this element to a JAXB object. However, what I observed is that JAXB only parsed my object into JAXBElement instead of going all the way into SimpleObj.
I'd be extremely grateful if you could show me how I can get SimpleObj out of the ServiceResult.
UPDATE:
Below is the SimpleObj class:
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class SimpleObj {
private int a;
private int b;
public SimpleObj() {}
public SimpleObj(int a, int b) {
this.a = a;
this.b = b;
}
// Getters and Setters
}
I am unable to reproduce the issue that you are seeing. Below is some demo code that interacts directly with JAXB.
import java.io.*;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(ServiceResult.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
StringReader xml = new StringReader("<serviceResult><simpleObj/><simpleObj/></serviceResult>");
ServiceResult result = (ServiceResult) unmarshaller.unmarshal(xml);
for(Object item : result.getBody()) {
System.out.println(item.getClass());
}
}
}
The output from running the demo code shows that it is instances of SimpleObj in the field annotated with #XmlAnyElement(lax=true).
class forum27871349.SimpleObj
class forum27871349.SimpleObj
UPDATE #1
On the side note, I've read your blog articles on #XmlAnyElement and
I've never seen you had to include #XmlSeeAlso({SimpleObj.class}) in
any of your examples.
I'm not sure why I never leverage #XmlSeeAlso in my examples.
However, in my case, if I don't have this, I would have the error
saying Class *** nor any of its super class is known to this context.
It'd be great if you could also show me if there is a way to make all
of these classes known to the consumer without using #XmlSeeAlso
When you are creating the JAXBContext yourself, you simply need to include anything you would have referenced in an #XmlSeeAlso annotation as part of the classes you used to bootstrap the JAXBContext.
JAXBContext jc = JAXBContext.newInstance(ServiceResult.class, SimpleObj.class);
In a JAX-WS (or JAX-RS) setting where you don't have direct access to the JAXBContext I would recommend using the #XmlSeeAlso annotation like you have done.
UPDATE #2
Regarding the #XmlAnyElement, from the documentation, I thought if the
unmarshaller cannot unmarshal elements into JAXB objects or
JAXBElement objects, I will at least get a DOM node.
When you have a property mapped with #XmlAnyElement(lax=true) the following will happen:
If the element corresponds to the #XmlRootElement of a class, then you will get an instance of that class.
If the element corresponds to the #XmlElementDecl of a class on the ObjectFactory or another class annotated with #XmlRegistry then you will get an instance of that class wrapped in an instance of JAXBElement.
If JAXB does not have an association between the element and a class, then it will convert it to a DOM Element.
I will demonstrate below with an example.
ObjectFactory
import javax.xml.namespace.QName;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.*;
#XmlRegistry
public class ObjectFactory {
#XmlElementDecl(name="simpleObjJAXBElement")
public JAXBElement<SimpleObj> createSimpleObj(SimpleObj simpleObj) {
return new JAXBElement<SimpleObj>(new QName("simpleObjJAXBElement"), SimpleObj.class, simpleObj);
}
}
Demo
import java.io.*;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(ServiceResult.class, ObjectFactory.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
StringReader xml = new StringReader("<serviceResult><simpleObj/><unmapped/><simpleObjJAXBElement/></serviceResult>");
ServiceResult result = (ServiceResult) unmarshaller.unmarshal(xml);
for(Object item : result.getBody()) {
System.out.println(item.getClass());
}
}
}
Output
class forum27871349.SimpleObj
class com.sun.org.apache.xerces.internal.dom.ElementNSImpl
class javax.xml.bind.JAXBElement
I am using Java and JAXB for XML processing.
I have the following class:
public class Characteristic {
private String characteristic;
private String value;
#XmlAttribute
public String getCharacteristic() {
return characteristic;
}
public void setCharacteristic(String characteristic) {
this.characteristic = characteristic;
}
#XmlValue
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
public static void main(String[] args) {
Characteristic c = new Characteristic();
c.setCharacteristic("store_capacity");
c.setValue(40);
Characteristic c2 = new Characteristic();
c2.setCharacteristic("number_of_doors");
c2.setValue(4);
}
This is the result that I get:
<characteristics characteristic="store_capacity">40</characteristics>
<characteristics characteristic="number_of_doors">4</characteristics>
I want to get the following result:
<store_capacity>40</store_capacity>
<number_of_doors>4</number_of_doors>
How can I achieve this?
You can use a combination of #XmlElementRef and JAXBElement to produce dynamic element names.
The idea is:
Make Characteristic a subclass of JAXBElement and override the getName() method to return the name based on the characteristic property.
Annotate characteristics with #XmlElementRef.
Provide the #XmlRegistry (ObjectFactory) with an #XmlElementDecl(name = "characteristic").
Below is a working test.
The test itself (nothing special):
#Test
public void marshallsDynamicElementName() throws JAXBException {
JAXBContext context = JAXBContext.newInstance(ObjectFactory.class);
final Characteristics characteristics = new Characteristics();
final Characteristic characteristic = new Characteristic(
"store_capacity", "40");
characteristics.getCharacteristics().add(characteristic);
context.createMarshaller().marshal(characteristics, System.out);
}
Produces:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<characteristics><store_capacity>40</store_capacity></characteristics>
Let's start with the characteristics root element class. It has a characteristics property which is annotated with #XmlElementRef. This means that the contents should be either JAXBElements or #XmlRootElement-annotated class instances.
#XmlRootElement(name = "characteristics")
public class Characteristics {
private final List<Characteristic> characteristics = new LinkedList<Characteristic>();
#XmlElementRef(name = "characteristic")
public List<Characteristic> getCharacteristics() {
return characteristics;
}
}
In order for this to work you also need an ObjectFactory or something annotated with #XmlRegistry having a corresponding #XmlElementDecl:
#XmlRegistry
public class ObjectFactory {
#XmlElementDecl(name = "characteristic")
public JAXBElement<String> createCharacteristic(String value) {
return new Characteristic(value);
}
}
Recall, the characteristics property must contain either #XmlRootElement-annotated class instances or JAXBElements. #XmlRootElement is not suitable since it's static. But JAXBElement is dynamic. You can subclass JAXBElement and override the getName() method:
public class Characteristic extends JAXBElement<String> {
private static final long serialVersionUID = 1L;
public static final QName NAME = new QName("characteristic");
public Characteristic(String value) {
super(NAME, String.class, value);
}
public Characteristic(String characteristic, String value) {
super(NAME, String.class, value);
this.characteristic = characteristic;
}
#Override
public QName getName() {
final String characteristic = getCharacteristic();
if (characteristic != null) {
return new QName(characteristic);
}
return super.getName();
}
private String characteristic;
#XmlTransient
public String getCharacteristic() {
return characteristic;
}
public void setCharacteristic(String characteristic) {
this.characteristic = characteristic;
}
}
In this case I've overridden the getName() method to dynamically determine the element name. If characteristic property is set, its value will be used as the name, otherwise the method opts to the default characteristic element.
The code of the test is available on GitHub.
If you are using EclipseLink MOXy as your JAXB (JSR-222) provider then you can leverage our #XmlVariableNode extension for this use case (see: http://blog.bdoughan.com/2013/06/moxys-xmlvariablenode-json-schema.html):
Java Model
Characteristics
We will leverage the #XmlVariableNode extension here. This annotation specifies a field/property from the reference class to be used as the element name.
import java.util.List;
import javax.xml.bind.annotation.*;
import org.eclipse.persistence.oxm.annotations.XmlVariableNode;
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Characteristics {
#XmlVariableNode("characteristic")
private List<Characteristic> characteristics;
}
Characteristic
We need to mark the characteristic field/property as #XmlTransient so it won't appear as a child element.
import javax.xml.bind.annotation.*;
#XmlAccessorType(XmlAccessType.FIELD)
public class Characteristic {
#XmlTransient
private String characteristic;
#XmlValue
private String value;
}
jaxb.properties
To specify MOXy as your JAXB provider you need to have EclipseLink on your classpath and include a file called jaxb.properties with the following content in the same package as your domain model (see: http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html).
javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
Demo Code
Demo
Here is some demo code that will read/write the desired XML. Note how the standard JAXB APIs are used.
import java.io.File;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Characteristics.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
File xml = new File("input.xml");
Characteristics characteristics = (Characteristics) unmarshaller.unmarshal(xml);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(characteristics, System.out);
}
}
input.xml/Output
<?xml version="1.0" encoding="UTF-8"?>
<characteristics>
<store_capacity>40</store_capacity>
<number_of_doors>4</number_of_doors>
</characteristics>
The following approach will work with any JAXB (JSR-222) implementation.
Java Model
Characteristics
We will leverage an XmlAnyElement annotation. This annotation gives us alot of flexibility on what type of data can be held, including DOM nodes. We will use an XmlAdapter to convert instances of Characteristic to DOM nodes.
import java.util.List;
import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Characteristics {
#XmlAnyElement
#XmlJavaTypeAdapter(CharacteristicAdapter.class)
private List<Characteristic> characteristics;
}
Characteristic
As far as JAXB is concerned this class is no longer part of our model.
public class Characteristic {
String characteristic;
String value;
}
CharacteristicAdapter
This XmlAdapter converts the Characteristic object to and from a DOM node allowing us to construct it as we like.
import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.*;
public class CharacteristicAdapter extends XmlAdapter<Object, Characteristic> {
private Document doc;
public CharacteristicAdapter() {
try {
doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
} catch(Exception e) {
throw new RuntimeException(e);
}
}
#Override
public Characteristic unmarshal(Object v) throws Exception {
Element element = (Element) v;
Characteristic characteristic = new Characteristic();
characteristic.characteristic = element.getLocalName();
characteristic.value = element.getTextContent();
return characteristic;
}
#Override
public Object marshal(Characteristic v) throws Exception {
Element element = doc.createElement(v.characteristic);
element.setTextContent(v.value);
return element;
}
}
Demo Code
Demo
Below is some code that will read/write the desired XML. Note that the setAdapter call on Marshaller is not required, but is a performance improvement since it will cause the XmlAdapter to be reused.
import java.io.File;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Characteristics.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
File xml = new File("input.xml");
Characteristics characteristics = (Characteristics) unmarshaller.unmarshal(xml);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.setAdapter(new CharacteristicAdapter());
marshaller.marshal(characteristics, System.out);
}
}
input.xml/Output
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<characteristics>
<store_capacity>40</store_capacity>
<number_of_doors>4</number_of_doors>
</characteristics>
you can specify your class as :
#XmlRootElement(name = "Characteristic")
public class Characteristic {
#XmlElement(name = "store_capacity")
protected String storeCapacity;
#XmlElement(name = "number_of_doors")
protected String numberOfDoors;
/** getters and setters of above attributes **/
}
EDIT : If you wants the attributes to be dynamic then you can refer below link
getting Dynamic attribute for element in Jaxb
I have a requirement, to marshall/unmarshall some elements of java pojo depending upon a custom annotation marked on the field.
suppose there are 3 fields in my java pojp
#CustomVersion("v1")
private String field1;
#CustomVersion("v1","v2")
private String field2;
#CustomVersion("v2")
private String field3;
i would like to marshall only the fields with v1 if i pass version="v1" parameter while conversion in jaxb. if i pass v2, all fields with v2 annotation should only be marshalled.
is that even possible using jaxb? i am sure selective marshalling would be supported through some library or way, am not still able to figure it out after quite some searching.
any help or advice or pointers are highly appreciated.
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.
Below is an example of how you could use MOXy's #XmlNamedObjectGraphs extension to map your use case.
Java Model
Foo
The #XmlNamedObjectGraphs extension allows you to specify multiple subsets of mappings identified by a key.
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import org.eclipse.persistence.oxm.annotations.XmlNamedAttributeNode;
import org.eclipse.persistence.oxm.annotations.XmlNamedObjectGraph;
import org.eclipse.persistence.oxm.annotations.XmlNamedObjectGraphs;
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
#XmlNamedObjectGraphs({
#XmlNamedObjectGraph(
name="v1",
attributeNodes = {
#XmlNamedAttributeNode("field1"),
#XmlNamedAttributeNode("field2")}),
#XmlNamedObjectGraph(
name="v2",
attributeNodes = {
#XmlNamedAttributeNode("field2"),
#XmlNamedAttributeNode("field3")})
})
public class Foo {
private String field1 = "ONE";
private String field2 = "TWO";
private String field3 = "THREE";
}
jaxb.properties
To use MOXy as your JAXB provider you need to include a file called jaxb.properties 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 Code
Demo
You can specify the key corresponding to the object graph to have that subset applied to the object you are marshalling.
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import org.eclipse.persistence.jaxb.MarshallerProperties;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Foo.class);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
Foo foo = new Foo();
// Marshal Everything
marshaller.marshal(foo, System.out);
// Marshal "v1" Data
marshaller.setProperty(MarshallerProperties.OBJECT_GRAPH, "v1");
marshaller.marshal(foo, System.out);
// Marshal "v2" Data
marshaller.setProperty(MarshallerProperties.OBJECT_GRAPH, "v2");
marshaller.marshal(foo, System.out);
}
}
Output
<?xml version="1.0" encoding="UTF-8"?>
<foo>
<field1>ONE</field1>
<field2>TWO</field2>
<field3>THREE</field3>
</foo>
<?xml version="1.0" encoding="UTF-8"?>
<foo>
<field1>ONE</field1>
<field2>TWO</field2>
</foo>
<?xml version="1.0" encoding="UTF-8"?>
<foo>
<field2>TWO</field2>
<field3>THREE</field3>
</foo>
For More Information
http://blog.bdoughan.com/2013/03/moxys-object-graphs-inputoutput-partial.html
First of all I would suggest doing such preprocessing before marshalling. It would be much easier. However if it is not possible for some reason then you can create you custom type adapter. Then you can put #XmlJavaTypeAdapter(VersioningAdapter.class) on every type that you want to have versioning enabled.
#XmlJavaTypeAdapter can also be specified on package level, but you have to specify to which types it applies. You cannot use XmlAdapter without specifying somewhere #XmlJavaTypeAdapter.
Drawbacks of such solution:
if you have multiple versioned types then each of them has to be annotated with #XmlJavaTypeAdapter
#XmlJavaTypeAdapter does not work for root element, only on child elements. You have to call adapter manually on root element before marshalling
AFAIK there is no other option for customizing JAXB marshalling. That's why I think that annotation processing should be performed in separate step before marshalling. Unless you can accept mentioned limitations.
Sample adapter (full code can be found here):
public class VersioningAdapter extends XmlAdapter<Object, Object> {
#Override
public Object unmarshal(Object v) throws Exception {
// TODO Auto-generated method stub
return null;
}
#Override
public Object marshal(Object v) throws Exception {
if (v == null) {
return v;
}
Field[] fields = v.getClass().getDeclaredFields();
for (Field field : fields) {
Annotation[] annotations = field.getDeclaredAnnotations();
CustomVersion annotation = findCustomVersion(annotations);
if (annotation != null) {
if (!contains(annotation, Configuration.getVersion())) {
field.setAccessible(true);
field.set(v, null);
}
}
}
return v;
}
private CustomVersion findCustomVersion(Annotation[] annotations) {
for (Annotation annotation : annotations) {
if (annotation instanceof CustomVersion) {
return (CustomVersion) annotation;
}
}
return null;
}
private boolean contains(CustomVersion annotation, String version) {
String[] values = annotation.value();
for (String value : values) {
if (value.equals(version)) {
return true;
}
}
return false;
}
}
I have
xml1:
<abc><name>hello</name></abc>
xml2
<xyz><name>hello</name></xyz>
I have one java class.
#XmlRootElement(name="abc") (this
public class Foo{
#XmlElement
String name;
}
I do not want another class, but would like to accomodate xml2 with the Foo class itself.
I'm okay to intercept or modify it during pre-marshalling/pre-unmarshalling.
Thanks!
}
Depending on exactly what you mean by "I don't want another class", maybe this will work out for you:
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlSeeAlso;
import java.io.StringReader;
public class JaxbBindTwoRootElementsToSameClass {
public static void main(String[] args) throws Exception {
String xml1 = "<abc><name>hello</name></abc>";
String xml2 = "<xyz><name>hello</name></xyz>";
Unmarshaller unmarshaller = JAXBContext.newInstance(Foo.class).createUnmarshaller();
Object o1 = unmarshaller.unmarshal(new StringReader(xml1));
Object o2 = unmarshaller.unmarshal(new StringReader(xml2));
System.out.println(o1);
System.out.println(o2);
}
#XmlSeeAlso({Foo.Foo_1.class, Foo.Foo_2.class})
static class Foo {
#XmlRootElement(name = "abc")
static class Foo_1 extends Foo {}
#XmlRootElement(name = "xyz")
static class Foo_2 extends Foo {}
#XmlElement
String name;
#Override
public String toString() {
return "Foo{name='" + name + '\'' + '}';
}
}
}
Output:
Foo{name='hello'}
Foo{name='hello'}
It has the benefit of using JAXB almost exactly the way you usually would. It's just a slightly unconventional class organization. You even only have to pass Foo.class to the JAXBContext when you create it. No tinkering with JAXB internals needed.
Unmarshalling
You could use the unmarshal methods that take a class parameter. When a class is specified the JAXB implementation does not need to use the root element to determine the class to unmarshal to.
Marshalling
When marshaling you can wrap the root object in a JAXBElement to provide the root element information.