We are using Spring for a project, and I need to marshall and unmarshall lists of objects
from and to XML.
The business objects themselves do not have #XmlRootElement annotations on them for various reasons, which cannot change.
I can do this easily with javax.xml by using a wrapper class. I'm using a simple testObject with a numeric ID and a description field:
/**
* testObject for xml convert
*
*/
package xmlconvert;
import java.io.File;
import java.io.IOException;
import java.util.List;
//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.XmlRootElement;
//import javax.xml.bind.annotation.XmlType;
public class testObject
{
private static final long serialVersionUID = 1L;
public testObject()
{
setObjectid( 0L );
setDescription( " " );
}
public testObject( Long oid, String desc )
{
setObjectid( oid );
setDescription( desc );
}
private Long objectid;
public Long getObjectid()
{
return this.objectid;
}
public void setObjectid( Long oid )
{
this.objectid = oid;
}
private String description;
public String getDescription()
{
return this.description;
}
public void setDescription( String d )
{
this.description = d;
}
}
My wrapper class looks like this:
/**
* JAXBToList
*
* An object that holds the list of objects to convert.
*
*/
package xmlconvert;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElements;
import javax.xml.bind.annotation.XmlRootElement;
import xmlconvert.testObject;
#XmlRootElement(name="testObjects")
public class JAXBToList
{
protected List<testObject> list;
public JAXBToList()
{
list = new ArrayList<testObject>();
}
public JAXBToList( List<testObject> list )
{
this.list = list;
}
#XmlElement(name="testObject")
public List<testObject> getList()
{
return (List<testObject>) this.list;
}
}
When I convert an ArrayList of these objects to XML, the code looks like this:
#XmlElements({
#XmlElement(name="testObject",
type=testObject.class)
})
public static void convertListToXML( JAXBToList objectList,
String xmlFilePath )
throws IOException, JAXBException {
JAXBContext jc = JAXBContext.newInstance( JAXBToList.class );
marshaller = jc.createMarshaller();
marshaller.setProperty( Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE );
marshaller.marshal( objectList,
new StreamResult( new FileOutputStream(xmlFilePath) ) );
}
I would like to use Spring marshaller and unmarshaller to do this, but there seems to be no way to set the context to use the JAXBToList class in the oxm class Jaxb2Marshaller, or the other varieties of marshallers.
I know there is a set Jaxb Context Properties method, but there are no instructions on what I would need to set, or if using this would do the same thing as setting the jaxb Context as shown above.
Does anyone have a link that illustrates how oxm may be used for a list of objects? A google search does not turn up much.
Related
Unbelievable how many questions there are already about this subject. Still, none of them helps me because what I understood is that it is a common error and I believe my code is correct because it is exactly the same as the one of another project my colleague passed me.
But.. I have this error
unable to marshal type "modules.CollaborationInfo" as an element because it is missing an #XmlRootElement annotation
Given this class CollaborationInfo:
package modules;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElementRef;
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "CollaborationInfo", propOrder = {
"AgreementRef",
"ConversationId"
})
public class CollaborationInfo {
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement(name="AgreementRef")
public static class AgreementRef {
#XmlAttribute
private String pmode;
public String getPmode() {
return pmode;
}
public void setPmode(String pmode) {
this.pmode = pmode;
}
}
#XmlElementRef(name = "AgreementRef")
protected AgreementRef AgreementRef = new AgreementRef();
#XmlElement(name="ConversationId")
protected String ConversationId;
public String getPmode() {
return AgreementRef.getPmode();
}
public void setPmode(String value) {
this.AgreementRef.setPmode(value);
}
public String getConversationId() {
return ConversationId;
}
public void setConversationId(String value) {
this.ConversationId = value;
}
}
And as main():
public static void main(String[] args) throws Exception{
JAXBContext contextObj = JAXBContext.newInstance(CollaborationInfo.class);
Marshaller marshallerObj = contextObj.createMarshaller();
marshallerObj.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
CollaborationInfo CI = new CollaborationInfo();
CI.setPmode("String1");
CI.setConversationId("String2");
marshallerObj.marshal(CI, new FileOutputStream("C:\\OUT.xml"));
}
I should be able to have as output (OUT.xml):
<?xml version="1.0" encoding="UTF-8"?>
<CollaborationInfo>
<AgreementRef pmode="String1"/>
<ConversationId>String2</ConversationId>
</CollaborationInfo>
But I just can't.
Can someone tell me where I am wrong?
(Of course the real XML is much more long and complex, but probably if I'm able to have this part working I'm able to continue the rest)
As the errors says, you need to add the annotation #XmlRootElement in front of the class CollaborationInfo. Your #XmlRootElement is for the static class AgreementRef.
package modules;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElementRef;
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "CollaborationInfo", propOrder = {
"AgreementRef",
"ConversationId"
})
public class CollaborationInfo {
#XmlAccessorType(XmlAccessType.FIELD)
public static class AgreementRef {
#XmlAttribute
private String pmode;
public String getPmode() {
return pmode;
}
public void setPmode(String pmode) {
this.pmode = pmode;
}
}
#XmlElement(name = "AgreementRef")
protected AgreementRef AgreementRef = new AgreementRef();
#XmlElement(name="ConversationId")
protected String ConversationId;
public String getPmode() {
return AgreementRef.getPmode();
}
public void setPmode(String value) {
this.AgreementRef.setPmode(value);
}
public String getConversationId() {
return ConversationId;
}
public void setConversationId(String value) {
this.ConversationId = value;
}
}
This is old but just in case someone runs into it, it was answered here:
https://stackoverflow.com/a/59249216/5835746
Basically, you should not use the auto-generated classes directly but use the methods from ObjectFactory, which was also auto-generated by the plugin, to create instances of those classes.
Editing the auto-generated classes, to add the #XmlRootElement annotation is probably not a good idea.
I've got a web service which manages Parada objects. What I want to achieve seems straightforward: return lists of these objects:
List<Parada> list
This list is returned using a Service class which uses another DAO class, just commenting it out.
Besides, my common practise is that every web method return a Response using ResponseBuilder, as in here:
return Response.ok(obj, MediaType.APPLICATION_JSON).build();
This is an example of one of my web methods:
#GET
#Consumes(value = MediaType.TEXT_PLAIN)
#Produces(MediaType.APPLICATION_JSON)
#Path("{idParadaGtfs}")
public Response getParadasPorIdGtfs(
#PathParam(value = "idParadaGtfs") Integer pCodigoParadaEnGtfs
){
try{
getServiceIfNecessary();
List<Parada> paradas = service.getParadas(pCodigoParadaEnGtfs);
return Response.ok(paradas, MediaType.APPLICATION_JSON).build();
}catch(HibernateException e){
String msg = "Error HibernateException: " + e.getMessage();
LogHelper.logError(logger, msg, true);
e.printStackTrace();
return Response.serverError().tag(msg).build();
}catch(Exception e){
String msg = "Error Exception: " + e.getMessage();
LogHelper.logError(logger, msg, true);
e.printStackTrace();
return Response.serverError().tag(msg).build();
}
}
Unfortunately, I'm not receiving any object and I get the following error everytime I execute the web method described above:
nov 26, 2015 2:20:16 PM org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor aroundWriteTo
GRAVE: MessageBodyWriter not found for media type=application/json, type=class java.util.ArrayList, genericType=java.util.List<model.Parada>.
What do I have to implement to let my web methods build Responses using Lists?
Thank you!
EDIT:
I've been able to make it work by making some changes and additions, which I'll describe now.
First of all, I've added a Parada container class, ParadaContainer:
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlRootElement;
import com.ingartek.ws.paradasasociadasws.model.Parada;
#XmlRootElement
public class ParadaContainer implements Serializable {
private static final long serialVersionUID = 6535386309072039406L;
private List<Parada> paradas;
public ParadaContainer(ArrayList<Parada> pParadas) {
this.setParadas(pParadas);
}
public List<Parada> getParadas() {
return paradas;
}
public void setParadas(List<Parada> paradas) {
this.paradas = paradas;
}
#Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("ParadaContainer [");
if (paradas != null) {
builder.append("paradas=");
for(Parada p : paradas){
builder.append(p.toString());
}
}
builder.append("]");
return builder.toString();
}
}
Now, I'm not returning a List of Parada objects, instead I return a single ParadaContainer object:
ParadaContainer paradas = new ParadaContainer(new ArrayList<Parada>(service.getParadas()));
return Response
.ok(paradas)
.type(MediaType.APPLICATION_JSON)
.build();
I don't know whether they are mandatory or not, but I've created another class (MyObjectMapperProvider)...
import javax.ws.rs.ext.ContextResolver;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
public class MyObjectMapperProvider implements ContextResolver<ObjectMapper> {
final ObjectMapper defaultObjectMapper;
public MyObjectMapperProvider() {
defaultObjectMapper = createDefaultMapper();
}
#Override
public ObjectMapper getContext(Class<?> type) {
return defaultObjectMapper;
}
private static ObjectMapper createDefaultMapper() {
final ObjectMapper result = new ObjectMapper();
result.configure(SerializationFeature.INDENT_OUTPUT, true);
return result;
}
}
...and edited my Application class and added some lines (see as of *Jackson * comment until Clases de Servicios comment):
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import javax.ws.rs.core.Application;
import org.glassfish.jersey.jackson.JacksonFeature;
import com.ingartek.ws.paradasasociadasws.ws.ParadasWS;
public class App extends Application {
private final Set<Class<?>> classes;
public App() {
HashSet<Class<?>> c = new HashSet<Class<?>>();
// Filtro CORS:
c.add(CORSFilter.class);
// Jackson
c.add(MyObjectMapperProvider.class);
c.add(JacksonFeature.class);
// Clases de Servicios:
c.add(ParadasWS.class);
classes = Collections.unmodifiableSet(c);
}
#Override
public Set<Class<?>> getClasses() {
return classes;
}
}
Afterwards, I've edited my class model by adding some annotations to them (#XmlRootElement and #JsonProperty; removed irrelevant getters, setters, hashCode, equals and toString methods):
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.xml.bind.annotation.XmlRootElement;
import com.fasterxml.jackson.annotation.JsonProperty;
#XmlRootElement(name = "grupo")
#Entity
#Table(name = "grupos_cercania_exacta")
public class Grupo implements Serializable {
#Transient
private static final long serialVersionUID = -5679016396196675191L;
#JsonProperty("id")
#Id
#Column(name = "id_grupo")
private Integer id;
...
}
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.xml.bind.annotation.XmlRootElement;
import com.fasterxml.jackson.annotation.JsonProperty;
#XmlRootElement(name = "operador")
#Entity
#Table(name = "operadores_asociados")
public class Operador implements Serializable {
#Transient
private static final long serialVersionUID = -7557099187432476588L;
/*
Atributos
*/
#JsonProperty("codigo")
#Id
#Column(name = "codigo_operador", insertable = false, updatable = false)
private Integer codigo;
#JsonProperty("nombre")
#Column(name = "descripcion_corta", insertable = false, updatable = false)
private String nombre;
#JsonProperty("descripcion")
#Column(name = "descripcion_larga", insertable = false, updatable = false)
private String descripcion;
#JsonProperty("web")
#Column(name = "direccion_web", insertable = false, updatable = false)
private String web;
#JsonProperty("telefono")
#Column(name = "telefono", insertable = false, updatable = false)
private String telefono;
...
}
import java.io.Serializable;
import java.util.UUID;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.xml.bind.annotation.XmlRootElement;
import com.fasterxml.jackson.annotation.JsonProperty;
#XmlRootElement(name = "parada")
#Entity
#Table(name = "paradas_asociadas")
public class Parada implements Serializable {
#Transient
private static final long serialVersionUID = -3594254497389126197L;
#JsonProperty("id")
#Id
#Column(name = "id")
private UUID id;
#JsonProperty("codigoMunicipio")
#Column(name = "codigo_municipio")
private Integer codigoMunicipio;
#JsonProperty("nombre")
#Column(name = "nombre")
private String nombre;
#JsonProperty("descripcion")
#Column(name = "descripcion")
private String descripcion;
#JsonProperty("idGtfs")
#Column(name = "id_gtfs")
private Integer idGtfs;
#JsonProperty("idWs")
#Column(name = "id_ws")
private Integer idWs;
#JsonProperty("latitud")
#Column(name = "latitud")
private Double latitud;
#JsonProperty("longitud")
#Column(name = "longitud")
private Double longitud;
#JsonProperty("utmX")
#Column(name = "utm_x")
private Double utmX;
#JsonProperty("utmY")
#Column(name = "utm_y")
private Double utmY;
#JsonProperty("grupo")
#ManyToOne
#JoinColumn(name = "grupo_cercania_exacta_id")
private Grupo grupo;
#JsonProperty("operador")
#ManyToOne
#JoinColumn(name = "operador")
private Operador operador;
...
}
I've to admit that I've had some problems just after these changes. Sharp people could've realised that there is a missing attribute regarding the previous Parada class: the lack of Point attribute. This attribute was causing me some problems, this is, the absence of a Serializer and a Serializer was preventing me from creating a successful JSON. So I googled it out and found three options:
Remove the Point item. This was my ultimate choice, as Point was superfluous due to the existence of Latitude and Longitude elements and because it just could bother or confuse the final user.
Creating a custom Serializer and Deserializer. Fortunately I found the following link, which describes the process of creating them. The following is described in here:
Add these annotations to our coordinates field:
#JsonSerialize(using = PointToJsonSerializer.class)
#JsonDeserialize(using = JsonToPointDeserializer.class)
Create such serializer:
import java.io.IOException;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.vividsolutions.jts.geom.Point;
public class PointToJsonSerializer extends JsonSerializer<Point> {
#Override
public void serialize(Point value, JsonGenerator jgen,
SerializerProvider provider) throws IOException,
JsonProcessingException {
String jsonValue = "null";
try
{
if(value != null) {
double lat = value.getY();
double lon = value.getX();
jsonValue = String.format("POINT (%s %s)", lat, lon);
}
}
catch(Exception e) {}
jgen.writeString(jsonValue);
}
}
Create such deserializer:
import java.io.IOException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.PrecisionModel;
public class JsonToPointDeserializer extends JsonDeserializer<Point> {
private final static GeometryFactory geometryFactory = new GeometryFactory(new PrecisionModel(), 26910);
#Override
public Point deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
try {
String text = jp.getText();
if(text == null || text.length() <= 0)
return null;
String[] coordinates = text.replaceFirst("POINT ?\\(", "").replaceFirst("\\)", "").split(" ");
double lat = Double.parseDouble(coordinates[0]);
double lon = Double.parseDouble(coordinates[1]);
Point point = geometryFactory.createPoint(new Coordinate(lat, lon));
return point;
}
catch(Exception e){
return null;
}
}
}
The last option is to use Jackson Datatype JTS library, whose github repository lays here.
I lasted some hours so that I could find these solutions, but finally I got them. Hope it helps to someone. Thank you!
Either you don't have a JSON provider (I am guessing you do) or you are using MOXy. Under the latter assumption, with MOXy, it needs to know the type information in order to be able to serialize. When you return Response, you are wrapping the object, which takes away type information (because of type erasure), as opposed to if you were doing
#GET
public List<Parada> get() {}
Here the type information is known. But doing
#GET
public Response get() {
List<Parada> list..
return Response.ok(list)...
}
The type is hidden and erased by the time the entity reaches the serialization phase of the processing.
To get around this, GenericEntity was introduced
Normally type erasure removes generic type information such that a Response instance that contains, e.g., an entity of type List<String> appears to contain a raw List<?> at runtime. When the generic type is required to select a suitable MessageBodyWriter, this class may be used to wrap the entity and capture its generic type.
So you can do
List<Parada> paradas = ...
GenericEntity<List<Parada>> entity = new GenericEntity<List<Parada>>(paradas){};
return Response.ok(entity, ...)...
Second option, is to instead of using MOXy, use Jackson instead. With Jackson, the type info is not needed (in most cases), as the serializer just introspects and the bean bean properties to get the data.
It is not allowed to send a List back. Probably because List has no #XmlRootElement notation. You can create your own container:
#XmlRootElement
public class ParadaContainer implements Serializable {
private List<Parada> list;
public List<Parada> getList() {
return list;
}
public void setList(List<Parada> list) {
this.list = list;
}
}
You part will look like:
try{
getServiceIfNecessary();
List<Parada> paradas = service.getParadas(pCodigoParadaEnGtfs);
ParadaContainer paradaContainer = new ParadaContainer();
paradaContainer.setList(paradas);
return Response.ok(paradaContainer, MediaType.APPLICATION_JSON).build();
}
I need to wrap some arbitrary JSON content into a POJO that is then serialized with MOXy/JAXB to JSON, but could not figure out how to bind a JsonObject with JAXB. I only need to marshall the JsonObject, unmarshalling is not required.
i.e. having the POJO:
#XmlRootElement
public class MsgPOJO {
public String type;
public Object content;
}
how to put an arbitrary JSON content in 'MsgPOJO.content', and serialize it:
String jsonDoc = "{\"prop\":\"value\"}";
MsgPOJO msg = new MsgPOJO();
msg.type = "whatever";
msg.content = jsonDoc;
so that this would be the output:
{
"type": "whatever",
"content": {
"prop": "value"
}
}
I was thinking about annotating the MsgPOJO.content with a #XmlJavaTypeAdapter, but this does not seem to get me anywhere, since the JSON content could be arbitrary.
It would be nice if moxy could marshal JsonObject or JsonStructure, so I could just define the POJO like:
#XmlRootElement
public class MsgPOJO {
public String type;
public JsonObject content;
}
Is there a way to make this work? Or is it a limitation in MOXy/JAXB?
MOXy doesn't support marshal/unmarshal of JSON-P structures by default, you need to implement XmlJavaTypeAdapter. Below is example for JsonObject adapter.
MsgPOJO.java
package org.eclipse.persistence.testing.jsonp;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
/**
* Created by mvojtek on 24/02/15.
*/
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class MsgPOJO {
public String type;
public JsonObjectWrapper content;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public JsonObjectWrapper getContent() {
return content;
}
public void setContent(JsonObjectWrapper content) {
this.content = content;
}
#Override
public String toString() {
return "MsgPOJO{" +
"type='" + type + '\'' +
", content=" + content +
'}';
}
}
JsonObjectWrapper.java
package org.eclipse.persistence.testing.jsonp;
import javax.json.JsonObject;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
/**
* Created by mvojtek on 24/02/15.
*/
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class JsonObjectWrapper {
#XmlJavaTypeAdapter(JsonObjectAdapter.class)
private JsonObject jsonObject;
public JsonObject getJsonObject() {
return jsonObject;
}
public void setJsonObject(JsonObject jsonObject) {
this.jsonObject = jsonObject;
}
#Override
public String toString() {
return "JsonObjectWrapper{" +
"jsonObject=" + jsonObject +
'}';
}
}
JsonObjectAdapter.java
package org.eclipse.persistence.testing.jsonp;
import javax.json.Json;
import javax.json.JsonObject;
import javax.json.JsonReader;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import java.io.StringReader;
/**
* Created by mvojtek on 24/02/15.
*/
public final class JsonObjectAdapter extends XmlAdapter<String,JsonObject> {
#Override
public String marshal(JsonObject v) throws Exception {
if (null == v) {
return null;
}
return v.toString();
}
#Override
public JsonObject unmarshal(String v) throws Exception {
if (null == v) {
return null;
}
JsonReader jsonReader = Json.createReader(new StringReader(v));
return jsonReader.readObject();
}
}
Test.java
package org.eclipse.persistence.testing.jsonp;
import org.eclipse.persistence.jaxb.JAXBContextFactory;
import org.eclipse.persistence.jaxb.JAXBContextProperties;
import org.eclipse.persistence.oxm.MediaType;
import javax.json.Json;
import javax.json.JsonReader;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import java.io.StringReader;
import java.io.StringWriter;
public class Test {
public static void main(String[] args) throws Exception {
//marshal
JAXBContext jaxbContext = JAXBContextFactory.createContext(new Class[]{MsgPOJO.class}, null);
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(JAXBContextProperties.MEDIA_TYPE, MediaType.APPLICATION_JSON);
MsgPOJO msgPOJO = new MsgPOJO();
msgPOJO.setType("myType");
JsonReader jsonReader = Json.createReader(new StringReader("{\"prop\":\"value\"}"));
JsonObjectWrapper wrapper = new JsonObjectWrapper();
wrapper.setJsonObject(jsonReader.readObject());
msgPOJO.setContent(wrapper);
StringWriter marshallerOutput = new StringWriter();
marshaller.marshal(msgPOJO, marshallerOutput);
String result = marshallerOutput.toString();
System.out.println("marshal result = "+result);
//unmarshal
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
unmarshaller.setProperty(JAXBContextProperties.MEDIA_TYPE, MediaType.APPLICATION_JSON);
unmarshaller.setProperty(JAXBContextProperties.JSON_INCLUDE_ROOT, true);
MsgPOJO msgPOJO2 = (MsgPOJO)unmarshaller.unmarshal(new StringReader(result));
System.out.println("msgPOJO2="+msgPOJO2);
}
}
If you don't want String, you can write general structure with the help of MyList and MyMap structures. After that, you can write XmlJavaTypeAdapter, which will marshal JsonObject to this new type. The result will be json, not reallly the same as string representation of the input, but legal json.
https://github.com/eclipse/eclipselink.runtime/blob/master/moxy/eclipselink.moxy.test/src/org/eclipse/persistence/testing/jaxb/rs/model/MyList.java
https://github.com/eclipse/eclipselink.runtime/blob/master/moxy/eclipselink.moxy.test/src/org/eclipse/persistence/testing/jaxb/rs/model/MyMap.java
I found a lot of articles that describe how to unmarshal a sequence of XML elements to a HashMap as long as they are within a "parent" element. However, I do not get this to work with the children directly under the root element!
Option 1 - Works:
<?xml version="1.0" encoding="UTF-8"?>
<checks>
<checks>
<check key="check1"/>
<check key="check2"/>
...
</checks>
</checks>
Option 2 - Does not work:
<?xml version="1.0" encoding="UTF-8"?>
<checks>
<check key="check1"/>
<check key="check2"/>
...
</checks>
Checks:
package com.foo.conf;
import java.util.Map;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
#XmlRootElement(name="checks")
public class Checks {
#XmlJavaTypeAdapter(ChecksAdapter.class)
#XmlElement(name="checks")
public Map<String, Check> checkMap;
}
Check:
package com.foo.conf;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlValue;
public class Check {
#XmlAttribute public String key;
#XmlValue public String description;
public Check() { }
public Check(String key) {
this.key = key;
}
public String getCheckKey() {
return this.key;
}
}
CheckMapType:
package com.foo.conf;
import java.util.List;
import javax.xml.bind.annotation.XmlElement;
class CheckMapType {
#XmlElement(name="check")
public List<Check> checkList; // = new ArrayList<Check>();
}
ChecksAdapter:
package com.foo.conf;
import java.util.HashMap;
import java.util.Map;
import javax.xml.bind.annotation.adapters.XmlAdapter;
final class ChecksAdapter extends XmlAdapter<CheckMapType, Map<String, Check>> {
#Override
public CheckMapType marshal(Map<String, Check> arg0) throws Exception {
return null;
}
#Override
public Map<String, Check> unmarshal(CheckMapType arg0) throws Exception {
System.out.println("u: " + arg0.checkList.size());
Map<String, Check> map = new HashMap<String, Check>();
for (Check check : arg0.checkList) {
System.out.println(check);
map.put(check.key, check);
}
return map;
}
}
This is (some dummy test lineS) how I generate the classes/invoke the unmarshalling:
JAXBContext jc = JAXBContext.newInstance(Checks.class);
Unmarshaller u = jc.createUnmarshaller();
Checks c = (Checks) u.unmarshal(new File("checks.xml"));
System.out.println(c.checkMap.size());
Any idea on how to get option #2 to work? It works when using a List instead of the Map but I need the HashMap as I have to access the objects by the given keys...
Any hints much appreciated!
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.
JAXB will treat each object relationship with a nesting relationship. Map is treated like an Object instead of a Collection so this is why you are getting the behaviour that you are seeing.
MOXy has an XPath based mapping extension called #XmlPath that could be used for this use case.
package com.foo.conf;
import java.util.Map;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import org.eclipse.persistence.oxm.annotations.XmlPath;
#XmlRootElement(name="checks")
public class Checks {
#XmlJavaTypeAdapter(ChecksAdapter.class)
#XmlPath(".")
public Map<String, Check> checkMap;
}
For More Information
http://blog.bdoughan.com/2010/07/xpath-based-mapping.html
http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html
How are you generaing the JAXB classes? I am not sure what exactly are you trying to do but the below very simple code works for me ..
JAXBContext jc = JAXBContext.newInstance(ChecksType.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
ChecksType chksType = (ChecksType) unmarshaller.unmarshal(new File("/path/to/xml"));
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(chksType, System.out);
System.err.println(chksType.getCheck().get(0).getKey());
for (CheckType checkType : chksType.getCheck()) {
System.out.println("key = " + checkType.getKey() + ", " + checkType);
}
and here is my JAXB generated classes ..
ChecksType (Root element)
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "checksType", propOrder = { "check" })
#XmlRootElement(name = "checks")
public class ChecksType {
#XmlElement(required = true)
protected List<CheckType> check;
public List<CheckType> getCheck() {
if (check == null) {
check = new ArrayList<CheckType>();
}
return this.check;
}
}
And checkType (the child)
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "checkType")
public class CheckType {
#XmlAttribute(name = "key")
protected String key;
public String getKey() {
return key;
}
public void setKey(String value) {
this.key = value;
}
}
I need to serialize this:
List<Event>
where the Event class is:
public class Event {
public int id;
public String foo;
public String bar;
}
into JSON of this form:
{
"123":{"foo":"...","bar":"..."},
"345":{"foo":"...","bar":"..."}
}
Taking the "id" property out of Event and storing a Map would do the trick, but I need to support duplicate IDs.
Is there an annotation I can put on the "id" property to cause Jackson to treat it as a key, with the rest of the object as the associated value?
With your current structure of ID as the key, I'm not sure having duplicate IDs is possible in the JSON spec. Maybe if you had arrays with the IDs. I think you need to re-evaluate your desired JSON output.
You could use IdentityHashMap, so you could use different instances of string containing same value and have this result:
{"1":{"foo":"foo1","bar":"bar"},"2":{"foo":"foo2.1","bar":"bar"},"3":{"foo":"foo2","bar":"baz"},"2":{"foo":"foo2","bar":"baz"}}
that you can have executing this:
import java.io.IOException;
import java.util.Arrays;
import java.util.IdentityHashMap;
import java.util.List;
import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
public class JacksonTest {
public static void main(final String[] args) throws JsonGenerationException, JsonMappingException, IOException {
ObjectMapper om = new ObjectMapper();
IdentityHashMap<String, Event> ihm = new IdentityHashMap<String, Event>();
List<Event> list = Arrays.asList( //
new Event(1, "foo1", "bar"), //
new Event(2, "foo2", "baz"), //
new Event(2, "foo2.1", "bar"), //
new Event(3, "foo2", "baz") //
);
for (Event e : list) {
ihm.put(String.valueOf(e.id), e);
}
System.out.println(om.writeValueAsString(ihm));
}
#JsonIgnoreProperties({ "id" })
public static class Event {
public int id;
public String foo;
public String bar;
public Event(final int id, final String foo, final String bar) {
super();
this.id = id;
this.foo = foo;
this.bar = bar;
}
}
}