I have a REST API created with Jersey in Java.
For one request I'd like to return in JSON a list of tuples of pair of coordinates.
For this, I have a class that's a wrapper for an ArrayList
, a Tuple2
class and a Coords
class.
I use the javax.xml.bind.annotations
for automatically generating the XML/JSON of my classes.
But for a reason that I don't understand my Coords
class can't be me mapped to XML.
I have tried different types of attributes (Integers
instead of int
), having the @XmlAttribute
at different places (before the attributes and before the getters) and different XmlAccessType
(PROPERTY
instead of NONE
) but the results were the same.
Here is my Coords class :
package model;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlAttribute;
import static javax.xml.bind.annotation.XmlAccessType.NONE;
@XmlRootElement
@XmlAccessorType(NONE)
public class Coords {
@XmlAttribute private int x;
@XmlAttribute private int y;
public Coords(final int x, final int y) {
this.x = x;
this.y = y;
}
public Coords() {
this.x = 0;
this.y = 0;
}
public int getX() {
return this.x;
}
public int getY() {
return this.y;
}
}
And here is how it is present in my Tuple2
package model;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlAttribute;
import static javax.xml.bind.annotation.XmlAccessType.NONE;
@XmlRootElement
@XmlAccessorType(NONE)
public class Tuple2 {
private Coords c1;
private Coords c2;
// ...
@XmlAttribute
public Coords getFirst() {
return this.c1;
}
@XmlAttribute
public Coords getSecond() {
return this.c2;
}
// ...
}
Here is the error message:
[EL Warning]: moxy: 2019-10-27 15:01:08.586--javax.xml.bind.JAXBException:
Exception Description: The @XmlAttribute property first in type model.Tuple2 must reference a type that maps to text in XML. model.Coords cannot be mapped to a text value.
- with linked exception:
[Exception [EclipseLink-50096] (Eclipse Persistence Services - 2.7.4.v20190115-ad5b7c6b2a): org.eclipse.persistence.exceptions.JAXBException
Exception Description: The @XmlAttribute property first in type model.Tuple2 must reference a type that maps to text in XML. model.Coords cannot be mapped to a text value.]
oct. 27, 2019 3:01:08 PM org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor aroundWriteTo
GRAVE: MessageBodyWriter not found for media type=application/json, type=class model.ActionList, genericType=class model.ActionList.
Thank you for your help.
Your problem is coming from the misuse of the xml annotations. You are defining a Tuple2
to be an xml root element by annotating it with @XmlRootElement
, and its fields to be xml attributes by annotating the get methods with @XmlAttribute
. Which translates to:
<tuple2 first="first_attributes_vale" second="second_attributes_value" />
Now, both fields are of type Coords
, which is declared to be another xml element by annotating the Coords
class with @XmlRootElement
, and its fields to be xml attributes. When Coords
gets serialised to xml, it will be:
<coords x="value" y="value" />
The problem occurs when serialising Tuple2
. Its fields are supposed to be xml attributes, but Coords
is another xml element. Xml attributes cannot contain nested elements, but only values.
Depending on what you want, you can solve this in two different ways. Although, I wouldn't recommend the second approach, because it's odd (even though it works) and will incur extra effort on the client side (see explanations below).
Annotate the getFirst()
and getSecond()
methods with the @XmlElement
annotation.
package model;
import static javax.xml.bind.annotation.XmlAccessType.NONE;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement
@XmlAccessorType(NONE)
public class Tuple2 {
private Coords c1;
private Coords c2;
public Tuple2(Coords c1, Coords c2) {
this.c1 = c1;
this.c2 = c2;
}
public Tuple2() {
c1 = new Coords(0, 0);
c2 = new Coords(0, 0);
}
@XmlElement
public Coords getFirst() {
return this.c1;
}
@XmlElement
public Coords getSecond() {
return this.c2;
}
}
This will produce a result that looks like this:
<tuple2>
<first x="2" y="4"/>
<second x="12" y="12"/>
</tuple2>
This is the odd way of solving it. It works, but it incurs extra effort on the client side, because the values of Coords
are encoded as string values and will require parsing on the receiving side.
Change the return type of getFirst()
and getSecond()
methods to be String
and override the toString()
method of Coords
.
package model;
import static javax.xml.bind.annotation.XmlAccessType.NONE;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement
@XmlAccessorType(NONE)
public class Tuple2 {
private Coords c1;
private Coords c2;
public Tuple2(Coords c1, Coords c2) {
this.c1 = c1;
this.c2 = c2;
}
public Tuple2() {
c1 = new Coords(0, 0);
c2 = new Coords(0, 0);
}
@XmlAttribute
public String getFirst() {
return this.c1.toString();
}
@XmlAttribute
public String getSecond() {
return this.c2.toString();
}
}
Override the toString()
method of Coords
:
package model;
import static javax.xml.bind.annotation.XmlAccessType.NONE;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement
@XmlAccessorType(NONE)
public class Coords {
@XmlAttribute private int x;
@XmlAttribute private int y;
public Coords(final int x, final int y) {
this.x = x;
this.y = y;
}
public Coords() {
this.x = 0;
this.y = 0;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("Coords [x=");
builder.append(x);
builder.append(", y=");
builder.append(y);
builder.append("]");
return builder.toString();
}
}
This will produce a result similar to this:
<tuple2 first="Coords [x=2, y=4]" second="Coords [x=12, y=12]"/>
The values of the attributes first
and second
will be whatever the toString()
method of Coords
returns.