JAXB adding attribute to collection element [duplicate] - java

This question already has answers here:
How can I add xml attributes to jaxb annotated class XmlElementWrapper?
(3 answers)
Closed 3 years ago.
I have a class which looks like this
#XmlRootElement(name="root")
public class MyClass {
#XmlElementWrapper(name="list")
#XmlElement(name="item")
private List<String> myList = new ArrayList<String>();
// getters, setters
}
I want to add an attribute a to list element to reach following XML
<root>
<list a="1">
<item>a</item>
<item>b</item>
...
</list>
</root>

You could create another class for this list as following:-
import java.util.*;
import javax.xml.bind.annotation.*;
#XmlRootElement(name="list")
public class RootList {
private String a;
private List<String> someList;
#XmlAttribute(name="a")
public String getA() {
return a;
}
public void setA(String a) {
this.a = a;
}
#XmlElement(name="item")
public List<String> getSomeList() {
return someList;
}
public void setSomeList(List<String> someList) {
this.someList = someList;
}
public RootList(String numValue,List<String> someListValue) {
this();
this.a = numValue;
this.someList = someListValue;
}
public RootList() {
// TODO Auto-generated constructor stub
}
}
To run the above code using JAXB, use the following:
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.*;
public class Test {
public static void main(String[] args) throws Exception {
List<String> arg = new ArrayList<String>();
arg.add("a");
arg.add("b");
RootList root = new RootList("1", arg);
JAXBContext jc = JAXBContext.newInstance(RootList.class);
Marshaller marshaller = jc.createMarshaller();
marshaller.marshal(root, System.out);
}
}
It will generate the following XML as the output:
<list a="1">
<item>a</item>
<item>b</item>
</list>

Related

XmlMapper with same #XmlElement but different #XmlElementWrapper

I hava a class which has some String-Lists that I want to marshal via Jackson. And for better usage I want to have in eacht list the same Element-Name. So I annotate like this:
import java.util.List;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
public class MyClass
{
public String title;
#XmlElementWrapper(name="hints")
#XmlElement(name="text")
public List<String> hints;
#XmlElementWrapper(name="warnings")
#XmlElement(name="text")
public List<String> warnings;
#XmlElementWrapper(name="errors")
#XmlElement(name="text")
public List<String> errors;
}
But at runtime I get an exception Could not write JSON: Multiple fields representing property "text". I've also tried this with no effect:
// mapper instanceof com.fasterxml.jackson.dataformat.xml.XmlMapper
mapper.configure(MapperFeature.USE_WRAPPER_NAME_AS_PROPERTY_NAME, true);
What do I need in addition?
Not a perfect solution, but still a good workaround when I seperate the list itself in a new class and remove wrapping (for it is wrapped by the members using this new type):
public class StringList
{
#JacksonXmlElementWrapper(useWrapping = false)
#XmlElement(name="text")
public final List<String> list = new ArrayList<String>();
public void add( String msg )
{
list.add( msg );
}
}
... so my class will look like:
public class MyClass
{
public String title;
public StringList hints;
public StringList warnings;
public StringList errors;
}

Jackson deserialize inner collection

I have an issue with deserialization of inner collection:
Imagine there are two classes:
// RootClass.java
package somepackage;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import java.util.ArrayList;
import java.util.List;
class RootClass {
public List getItems() {
return items;
}
public void setItems(List items) {
this.items = items;
}
#JsonSerialize(contentAs = Item.class)
List<Item> items = new ArrayList<>();
}
//Item.java
package somepackage;
class Item {
String name;
public Item() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Item(String cat) {
name = cat;
}
}
// main class
package somepackage;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
public class SampleCase {
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
RootClass root = new RootClass();
root.items.add(new Item("cat"));
String json = mapper.writeValueAsString(root);
RootClass root2 = mapper.readValue(json, RootClass.class);
Item item = (Item) root2.items.get(0);
}
}
I get an exception:
Exception in thread "main" java.lang.ClassCastException: class java.util.LinkedHashMap cannot be cast to class somepackage.Item (java.util.LinkedHashMap is in module java.base of loader 'bootstrap'; somepackage.Item is in unnamed module of loader 'app')
According to javadoc #JsonSerialize(contentAs = Item.class) on the collection would have help me, but it wouldn't. The question is: What am i missing?
If it is not about this annotation i suspect there is a standard way to deal with the problem (i do not want to create custom deserializers).
Most questions on collection deserialization are about situation when root object is a collection itself, but this is not the case for me.
jackson 2.9.8
java 11.0.2 OpenJDK x64
There's nothing wrong with the basic form of your code. What you're trying to do will work. You just have problems with your code, starting with the fact that it won't even compile since you call a constructor on Item that takes a String, and yet you define no such constructor. You also need getters for Jackson to work with.
Here's a version of your code that works:
class RootClass {
List<Item> items = new ArrayList<>();
public List<Item> getItems() {
return items;
}
}
class Item {
String name;
Item() {}
Item(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
public static void main(String[] argsx) throws IOException{
ObjectMapper mapper = new ObjectMapper();
RootClass root = new RootClass();
root.items.add(new Item("cat"));
RootClass root2 = null;
String json = mapper.writeValueAsString(root);
root2 = mapper.readValue(json, RootClass.class);
Item item = root2.items.get(0);
System.out.println(item.getName());
}
Output:
cat

JAXB Unmarshalling derived types

Based on the following program, it prints out what we expect it to correctly.
Is it possible for such a program to unmarshall correctly if the classes ClassA and ClassB used
the same XmlRootElement name? For example, if they were both defined as "typeA"...? Would it be
possible to do something like that with JAXB?
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.*;
import java.io.ByteArrayOutputStream;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.Date;
public class JaxbABCTest {
public static void main(String[] args) throws JAXBException, UnsupportedEncodingException {
final JAXBContext context = JAXBContext.newInstance(ABC.class);
final Marshaller marshaller = context.createMarshaller();
final Unmarshaller unmarshaller = context.createUnmarshaller();
ABC class1 = new ClassA();
ABC class2 = new ClassB();
final ByteArrayOutputStream baosA = new ByteArrayOutputStream();
final ByteArrayOutputStream baosB = new ByteArrayOutputStream();
// Marshall to XML
marshaller.marshal(class1, baosA);
marshaller.marshal(class2, baosB);
String xmlA = baosA.toString(Charset.defaultCharset().name());
String xmlB = baosB.toString(Charset.defaultCharset().name());
System.out.println(xmlA);
System.out.println(xmlB);
// Now attempt the reverse.
Object unmarshalA = unmarshaller.unmarshal(new StringReader(xmlA));
Object unmarshalB = unmarshaller.unmarshal(new StringReader(xmlB));
System.out.println(unmarshalA.getClass());
System.out.println(unmarshalB.getClass());
}
}
#XmlTransient
#XmlSeeAlso({
ClassA.class,
ClassB.class
})
abstract class ABC {
private int a;
#XmlAttribute
public int getA() {
return a;
}
public void setA(int a) {
this.a = a;
}
}
#XmlRootElement(name = "typeA")
class ClassA extends ABC {
private String b;
private Date c;
#XmlAttribute
public String getB() {
return b;
}
public void setB(String b) {
this.b = b;
}
#XmlAttribute
public Date getC() {
return c;
}
public void setC(Date c) {
this.c = c;
}
}
#XmlRootElement(name = "typeB")
class ClassB extends ABC {
private String b;
private Date c;
private boolean d;
private float e;
#XmlAttribute
public String getB() {
return b;
}
public void setB(String b) {
this.b = b;
}
#XmlAttribute
public Date getC() {
return c;
}
public void setC(Date c) {
this.c = c;
}
#XmlAttribute
public boolean isD() {
return d;
}
public void setD(boolean d) {
this.d = d;
}
#XmlAttribute
public float getE() {
return e;
}
public void setE(float e) {
this.e = e;
}
}
For straightforward JAXB unmarshalling, (root) one element name refers to an element with a specific structure which is mapped to one certain Java class.
You can, of course, define a class containing the union of the fields of A and B - as long as fields occuring in both classes match. You'll have to have some attribute that lets you decide which of the two "subclasses" it really is. (This is similar in spirit to using an XPath expression that includes a test for the presence (or even value) of an attribute or element.)
You may also pursue a more elaborate approach leaving this element unmarshalled after reading the XML, investigate the DOM tree and create a JAXBContext according to what has been detected. This will let you use the same element name with Java types exactly matching the XML content. Of course, an unambiguous criterion must be available, and youÄll have to write the code for analysing based on te raw DOM tree data.

Resolving cyclic references in jaxb

I'm dealing with some cyclic references while implementing my project's web service layer. I'm using jaxb (latest version, 2.2.7) and even I had a look to some tips as here and here I can't get it working. That's a basic SSCCE about my problem:
/*
* The service interface
*/
#WebService
public interface IMyWS {
#WebMethod
public List<Class1> cyclicTest();
}
/*
* Interface implementation
*/
#WebService(endpointInterface = "com.mycompany.ws.interfaces.IMyWS")
public class MyWS implements IMyWS {
#XmlRootElement
public static class Class1 {
#XmlTransient
private Class2 class2;
public Class1() {
}
public Class1(Class2 refClass) {
class2 = refClass;
}
public Class2 getClass2() {
return class2;
}
public void setClass2(Class2 class2) {
this.class2 = class2;
}
#Override
public String toString() {
return this.getClass().getSimpleName();
}
}
#XmlRootElement
public static class Class2 {
private Class1 class1;
public Class2() {
}
public Class1 getClass1() {
return class1;
}
public void setClass1(Class1 class1) {
this.class1 = class1;
}
#Override
public String toString() {
return this.getClass().getSimpleName();
}
}
#Override
public List<Class1> cyclicTest() {
//I create an instance of each class, having them a cyclic reference to the other instance
Class2 class2 = new Class2();
Class1 class1 = new Class1(class2);
class2.setClass1(class1);
return Arrays.asList(class1);
}
}
And the exception I'm actually dealing with when calling cyclicTest():
Caused by: javax.xml.bind.MarshalException
- with linked exception:
[com.sun.istack.SAXException2: Se ha detectado un ciclo en el gráfico de objeto. Esto provocará un XML con profundidad infinita: Class1 -> Class2 -> Class1]
at com.sun.xml.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:326)
at com.sun.xml.bind.v2.runtime.MarshallerImpl.marshal(MarshallerImpl.java:178)
at org.apache.cxf.jaxb.JAXBEncoderDecoder.writeObject(JAXBEncoderDecoder.java:537)
at org.apache.cxf.jaxb.JAXBEncoderDecoder.marshall(JAXBEncoderDecoder.java:233)
... 50 more
Caused by: com.sun.istack.SAXException2: Se ha detectado un ciclo en el gráfico de objeto. Esto provocará un XML con profundidad infinita: Class1 -> Class2 -> Class1
at com.sun.xml.bind.v2.runtime.XMLSerializer.reportError(XMLSerializer.java:249)
at com.sun.xml.bind.v2.runtime.XMLSerializer.pushObject(XMLSerializer.java:537)
at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:631)
at com.sun.xml.bind.v2.runtime.property.SingleElementNodeProperty.serializeBody(SingleElementNodeProperty.java:158)
at com.sun.xml.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:361)
at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:696)
at com.sun.xml.bind.v2.runtime.property.SingleElementNodeProperty.serializeBody(SingleElementNodeProperty.java:158)
at com.sun.xml.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:361)
at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:696)
at com.sun.xml.bind.v2.runtime.property.ArrayElementNodeProperty.serializeItem(ArrayElementNodeProperty.java:69)
at com.sun.xml.bind.v2.runtime.property.ArrayElementProperty.serializeListBody(ArrayElementProperty.java:172)
at com.sun.xml.bind.v2.runtime.property.ArrayERProperty.serializeBody(ArrayERProperty.java:159)
at com.sun.xml.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:361)
at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:696)
at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl$1.serializeBody(ElementBeanInfoImpl.java:156)
at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl$1.serializeBody(ElementBeanInfoImpl.java:131)
at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl.serializeBody(ElementBeanInfoImpl.java:333)
at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl.serializeRoot(ElementBeanInfoImpl.java:340)
at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl.serializeRoot(ElementBeanInfoImpl.java:76)
at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsRoot(XMLSerializer.java:494)
at com.sun.xml.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:323)
... 53 more
I think I have the proper annotations set. What am I actually missing?
Using #XmlID and #XmlIDREF should solve your problem.
Here's what I did:
I added a String field to Class 1 to be used as an id, and annotated it with #XmlID.
Then, I annotated the setClass1() method with #XmlIDREF. My test is in the main method below.
/*
* Interface implementation
*/
#WebService(endpointInterface = "com.mycompany.ws.interfaces.IMyWS")
public class MyWS implements IMyWS {
#XmlRootElement
public static class Class1 {
#XmlID
private String id;
private Class2 class2;
public Class1() {
this.id = UUID.randomUUID().toString();
}
public Class1(Class2 refClass) {
this();
class2 = refClass;
}
public Class2 getClass2() {
return class2;
}
public void setClass2(Class2 class2) {
this.class2 = class2;
}
#Override
public String toString() {
return this.getClass().getSimpleName();
}
}
#XmlRootElement
public static class Class2 {
private Class1 class1;
public Class2() {
}
public Class1 getClass1() {
return class1;
}
#XmlIDREF
public void setClass1(Class1 class1) {
this.class1 = class1;
}
#Override
public String toString() {
return this.getClass().getSimpleName();
}
}
#Override
public List<Class1> cyclicTest() {
//I create an instance of each class, having them a cyclic reference to the other instance
Class2 class2 = new Class2();
Class1 class1 = new Class1(class2);
class2.setClass1(class1);
return Arrays.asList(class1);
}
public static void main(String[] args) throws JAXBException {
JAXBContext ctx = JAXBContext.newInstance(Class1.class);
Marshaller m = ctx.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
List<Class1> class1s = new MyWS().cyclicTest();
for (Class1 c1 : class1s){
m.marshal(c1, System.out);
}
}
}
Output:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<class1>
<id>e9e53e73-9a96-4c7c-b919-3ed3d7aa4c5e</id>
<class2>
<class1>e9e53e73-9a96-4c7c-b919-3ed3d7aa4c5e</class1>
</class2>
</class1>
As you add #XmlTransient on a private property, you should change your XmlAccessType (default to XmlAccessType.PUBLIC_MEMBER) to XmlAccessType.PROPERTY
PUBLIC_MEMBER is the default access type in JAXB. It means that a JAXB implementation will generate bindings for: public fields, annotated fields, properties
code copied from W A :
#WebService(endpointInterface = "com.mycompany.ws.interfaces.IMyWS")
public class MyWS implements IMyWS {
#XmlRootElement
#XmlAccessorType(XmlAccessType.PROPERTY)
public static class Class1 {
private Class2 class2;
public Class1() {
}
public Class1(Class2 refClass) {
class2 = refClass;
}
#XmlTransient
public Class2 getClass2() {
return class2;
}
public void setClass2(Class2 class2) {
this.class2 = class2;
}
#Override
public String toString() {
return this.getClass().getSimpleName();
}
}
#XmlRootElement
public static class Class2 {
private Class1 class1;
public Class2() {
}
public Class1 getClass1() {
return class1;
}
public void setClass1(Class1 class1) {
this.class1 = class1;
}
#Override
public String toString() {
return this.getClass().getSimpleName();
}
}
public List<Class1> cyclicTest() {
//I create an instance of each class, having them a cyclic reference to the other instance
Class2 class2 = new Class2();
Class1 class1 = new Class1(class2);
class2.setClass1(class1);
return Arrays.asList(class1);
}
public static void main(String[] args) throws JAXBException {
JAXBContext ctx = JAXBContext.newInstance(Class1.class);
Marshaller m = ctx.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
List<Class1> class1s = new MyWs().cyclicTest();
for (Class1 c1 : class1s){
m.marshal(c1, System.out);
}
}
Adding to the answer by W Almir, to make generation of unique IDs for objects simpler (no need to add code to constructors), annotate a method that returns the id. For example:
#XmlID
public String getId()
{
return Integer.toString(System.identityHashCode(this));
}
This seems to work not only for converting cyclical graphs of Java objects to XML but also for converting XML to cyclical graphs of Java objects.

EclipseLink and Spring OXM Inheritance issues

I am stuck with the inheritance problem while trying to unmarshal to object. Here is my class
A
#XmlRootElement(name="A")
public abstract class A{
}
B
#XmlRootElement(name="B")
public class B extends A{
String bField;
#XmlAttribute(name="b")
public String getBField(){
return bField;
}
public void setBField(String value){
this.bField = value;
}
}
C
#XmlRootElement(name="C")
public class C extends A{
String cField;
#XmlAttribute(name="c")
public String getCField(){
return cField;
}
public void setCField(String value){
this.cField = value;
}
}
Container
#XmlRootElement(name="container")
public class Container{
ArrayList<B> listB;
ArrayList<C> listC;
public ArrayList<B> getListB(){
return listB;
}
#XmlElementWrapper(name="list-B")
#XmlElement(name="b")
public ArrayList<B> getListB(){
return listB;
}
#XmlElementWrapper(name="list-C")
#XmlElement(name="c")
public ArrayList<C> getListC(){
return listC;
}
public ArrayList<C> getListC(){
return listC;
}
}
Then the input XML file
<container>
<list-B>
<b b="BFied"/>
</list-B>
<list-C>
<c c="CField"/>
</list-C>
</container>
I used EclipseLink JAXB integrated with Spring OXM. When i unmarshal xml file to an instance of Container, every thing is duplicated. In list B i have 2 B instances which duplicated ( the same thing with list C).
Please let me know where did i do wrong? Thank you!
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.
I haven't been able to reproduce the issue that you are seeing. I am using the EclipseLink 2.4.0 which can be obtained from the following location:
http://www.eclipse.org/eclipselink/downloads/
Below is my complete code based on your question:
A
package forum11642669;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement(name="A")
public abstract class A{
}
B
package forum11642669;
import javax.xml.bind.annotation.*;
#XmlRootElement(name = "B")
public class B extends A {
String bField;
#XmlAttribute(name = "b")
public String getBField() {
return bField;
}
public void setBField(String value) {
this.bField = value;
}
}
C
package forum11642669;
import javax.xml.bind.annotation.*;
#XmlRootElement(name = "C")
public class C extends A {
String cField;
#XmlAttribute(name = "c")
public String getCField() {
return cField;
}
public void setCField(String value) {
this.cField = value;
}
}
Container
The version of the Container class you had in your question wouldn't compile, so I've modified it below:
package forum11642669;
import java.util.ArrayList;
import javax.xml.bind.annotation.*;
#XmlRootElement(name = "container")
public class Container {
ArrayList<B> listB;
ArrayList<C> listC;
#XmlElementWrapper(name = "list-B")
#XmlElement(name = "b")
public ArrayList<B> getListB() {
return listB;
}
public void setListB(ArrayList<B> listB) {
this.listB = listB;
}
#XmlElementWrapper(name = "list-C")
#XmlElement(name = "c")
public ArrayList<C> getListC() {
return listC;
}
public void setListC(ArrayList<C> listC) {
this.listC = listC;
}
}
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
package forum11642669;
import java.io.File;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Container.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
File xml = new File("src/forum11642669/input.xml");
Container container = (Container) unmarshaller.unmarshal(xml);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(container, System.out);
}
}
input.xml/Output
<?xml version="1.0" encoding="UTF-8"?>
<container>
<list-B>
<b b="BFied"/>
</list-B>
<list-C>
<c c="CField"/>
</list-C>
</container>

Categories