Annotation based XML Marshalling in Spring Boot - java

In my spring boot application, I have below DTO class
#Data
public clsss Feed {
private int id;
private String name;
private String title;
#Builder
#XmlRootElement(name = "feeds")
public static class Feeds {
#XmlElement(name = "feed")
#Singular
private List<Feed> feeds;
}
}
My config class as below
#Component
public class JacksonCustomizer implements Jackson2ObjectMapperBuilderCustomizer {
#Override
public void customize(Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder) {
jacksonObjectMapperBuilder.modulesToInstall(new JaxbAnnotationModule());
}
}
DAO class implementation as below
public Feeds getAll() {
String sqlQuery = "SELECT * FROM feed WHERE trash = 0";
return Feeds.builder().feeds(namedParameterJdbcTemplate.query(sqlQuery, new BeanPropertyRowMapper<>(Feed.class))).build();
}
Using my ReST API, XML response I am receiving as below:
<feeds>
<feed>
<feed>
<id>1</id>
<name>Val1</name>
<title>Title1</title>
</feed>
<feed>
<id>2</id>
<name>Val2</name>
<title>Title2</title>
</feed>
</feed>
</feeds>
I want to remove <feed> which comes as a wrapper element. Desired output is as below:
<feeds>
<feed>
<id>1</id>
<name>Val1</name>
<title>Title1</title>
</feed>
<feed>
<id>2</id>
<name>Val2</name>
<title>Title2</title>
</feed>
</feeds>

Make changes in the config class to set the default wrapper to false.
#Component
public class JacksonCustomizer implements Jackson2ObjectMapperBuilderCustomizer {
#Override
public void customize(Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder) {
jacksonObjectMapperBuilder.modulesToInstall(new JaxbAnnotationModule());
jacksonObjectMapperBuilder.defaultUseWrapper(false); //This was missing before
}
}

Related

Marshall entity without any relationship

Currently, I have a class using JPA to connect to database that could look like this one.
public class Person {
private String name;
private int age;
#OneToMany
private List<Pet> pets;
#OneToOne
private House house;
}
and I am using javax.ws.rs annotations to create xml webservices like this one
#GET
#Path("/load")
#Produces("application/xml")
public Response loadByPetId(#QueryParam("petId") int petId) {
return Response.ok(personBean.loadByPetId(petId)).build();
}
(I am using eclipselink)
#Stateless
public class PersonBean {
#PersistenceContext(unitName = "...")
private EntityManager em;
#Override
protected EntityManager getEntityManager() {
return em;
}
public PersonBean() {
super(Person.class);
}
public List<Person> loadByPetId(int petId) {
return em.createNamedQuery("Person.findByPetId", Person.class).setParameter("petId", petId).getSingleResult();
}
}
that returns an xml that contains each attributes and relationships from the given Person such as
<Person>
<name>toto</name>
<age>18</age>
<Pets>
<Pet>
...
</Pet>
<Pet>
...
</Pet>
...
</Pets>
<House>
...
</House>
</Person>
I want to be able to create an other webservice that load only the attributes of the given Person without relationships such as
<Person>
<name>toto</name>
<age>18</age>
</Person>
But, as I am using Pets to load the correct Person, the Pets list is loaded and i do not know how to "unload it" to prevent it to be in my xml.

JaxB: Not workoing for XML child tag with capital letter

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<InstanceTree xmlns="http://www.testsite.org/Schemas/xyzSchema">
<Instance id="inst0" depth="1">
<UserData type="Instance">
<userValue title="occurrenceUUID" value="abc/>
</UserData>
<UserData type="Part" name="pqr">
<UserValue title="PartNumber" value="MTG_238_ZB_LACKIERUNG" />
</UserData>
</Instance>
</InstanceTree>
#XmlRootElement(name = "InstanceTree")
public class InstanceTree {
}
#XmlRootElement(name = "Instance")
public class Instance {
private List<Userdata> userdata;
#XmlElement
public List<Userdata> getUserdata() {
return userdata;
}
}
#XmlRootElement(name = "UserValue")
public class UserValue {
private List<UserValue> userValue;
#XmlElement
public List<UserValue> getUserValue() {
return userValue;
}
}
#XmlRootElement(name = "UserData")
public class Userdata {
}
In xml giving NullPointerException for upper case Instance, UserData, UserValue.
Giving error for xmlns="http://www.testsite.org/Schemas/abcSchema". working fine after removing url.
Use #XmlElement(name="Userdata") otherwise jaxb uses the attribute name.
#XmlRootElement(name = "Instance")
public class Instance {
private List<Userdata> userdata;
#XmlElement(name = "Userdata")
public List<Userdata> getUserdata() {
return userdata;
}
}
use also package-info.java
#javax.xml.bind.annotation.XmlSchema(
namespace = "http://www.testsite.org/Schemas/abcSchema",
elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED,
xmlns = {
#javax.xml.bind.annotation.XmlNs(prefix = "", namespaceURI = "http://www.testsite.org/Schemas/abcSchema")
}
)
package com.your.package;

JAX-RS JAXB Jackson not using #XmlRootElement name

I am developing a restful application with JAX-RS and JAXB. I want to send following Entity as JSON to my client:
#XmlRootElement(name = "user")
#XmlAccessorType(XmlAccessType.FIELD)
public class UserDTO implements Serializable
{
private static final long serialVersionUID = 1L;
private Long id;
private String username;
private String firstname;
private String lastname;
// getter & setter
}
The method in my WebService is defined as follows:
#POST
#Path("users/{id}")
#Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
public UserAccountDTO login(#PathParam("id") Long id)
{
UserAccountDTO userAccount = loadUserAccount(id);
return userAccount;
}
First problem was, that the root node was not send via JSON. Therefore I have added following Class:
#Provider
#Produces(MediaType.APPLICATION_JSON)
public class SkedFlexContextResolver implements ContextResolver<ObjectMapper>
{
private ObjectMapper objectMapper;
public SkedFlexContextResolver() throws Exception
{
this.objectMapper = new ObjectMapper().configure(SerializationFeature.WRAP_ROOT_VALUE, true);
}
#Override
public ObjectMapper getContext(Class<?> objectType)
{
return objectMapper;
}
}
Now, the root node is send with the data. In case of XML everything is fine (root node is equal to name of #XmlRootElement). See following XML response:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<user>
<id>10</id>
<username>Admin</username>
<firstname>Administrator</firstname>
</user>
But in case of JSON the root node is the Classname of the POJO:
{
"UserAccountDTO":
{
"id": 10,
"username": "Admin",
"firstname": "Administrator",
"lastname": null
}
}
Why differs the output between XML and JSON? What do I need to change to get the specified name in the #XmlRootElement-Annotation
I had to register Jaxb module to the xml mapper like this, otherwise the #XmlRootElement(name = "myname") was igonerd.
JaxbAnnotationModule module = new JaxbAnnotationModule();
xmlMapper.registerModule(module);
Changing .configure(SerializationFeature.WRAP_ROOT_VALUE, true) to .configure(SerializationFeature.WRAP_ROOT_VALUE, false) should help.
According to javadoc:
Feature that can be enabled to make root value <..> wrapped within a single property JSON object, where key as the "root name"
Maybe it can help
#Configuration
public class SpringConfig implements WebMvcConfigurer {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new Jaxb2RootElementHttpMessageConverter());
}
}

How to ignore a field from binding to XML with JAXB

I have a simple class that looks like this
import nl.hu.pf.coproco.model.Observeable;
#XmlRootElement (name = "Purpose")
#XmlAccessorType (XmlAccessType.FIELD)
public class Purpose implements Observeable{
private String name;
private ArrayList<Observer> observers;
#XmlElement (name = "subPurpose")
private ArrayList<Purpose> subPurposes;
//methods
}
But Observable is an interface so I get an exception because JAXB can'ty handle interfaces. I tried figuring out how to ignore the field with the ArrayList<Observer> so it wont be exported to xml.
I tried using the #XmlTransient annotation but the I get the following eception javax.xml.bind.UnmarshalException: unexpected element (uri:"", local:"purpose"). Expected elements are <{}Purpose>
This happens when Unmarshalling but I want to Marshall that class too
Sample xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<purpose>
<name>All Purposes</name>
<subPurpose>
<name>Creational</name>
</subPurpose>
<subPurpose>
<name>Structural</name>
</subPurpose>
<subPurpose>
<name>Behavioral</name>
<subPurpose>
<name>Behavioral 1</name>
</subPurpose>
<subPurpose>
<name>Behavioral 2</name>
</subPurpose>
</subPurpose>
</purpose>
The interface Observable looks like this:
public interface Observeable {
public void registerObserver(Observer o);
public void removeObserver(Observer o);
public void notifyObservers(Object o, String arg);
}
Your xml contains purpose not Purpose so change #XmlRootElement(name = "Purpose") to
#XmlRootElement(name = "purpose")
And add #XmlTransient on observers like this
#XmlTransient
private ArrayList<Observer> observers;

Unmarshalling generic list with JAXB

I've a service that returns this XML:
<?xml version="1.0" encoding="UTF-8"?>
<response>
<status>success</status>
<result>
<project>
<id>id1</id>
<owner>owner1</owner>
</project>
<project>
<id>id2</id>
<owner>owner2</owner>
</project>
</result>
or
<?xml version="1.0" encoding="UTF-8"?>
<response>
<status>success</status>
<result>
<user>
<id>id1</id>
<name>name1</name>
</user>
<user>
<id>id2</id>
<name>name2</name>
</user>
</result>
I want to unmarshall the retrieved XML using these classes:
Result:
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Response<T> {
#XmlElement
protected String status;
#XmlElementWrapper(name = "result")
#XmlElement
protected List<T> result;
}
Project:
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Project {
#XmlElement
public String id;
#XmlElement
public String owner;
}
User:
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class User {
#XmlElement
public String id;
#XmlElement
public String name;
}
First not working solution
JAXBContext context = JAXBContext.newInstance(Response.class, Project.class, User.class);
Unmarshaller unmarshaller = context.createUnmarshaller();
StreamSource source = new StreamSource(new File("responseProject.xml"));
Response<Project> responseProject = (Response<Project>)unmarshaller.unmarshal(source);
System.out.println(responseProject.getStatus());
for (Project project:responseProject.getResult()) System.out.println(project);
source = new StreamSource(new File("responseUser.xml"));
Response<User> responseUser = (Response<User>)unmarshaller.unmarshal(source);
System.out.println(responseUser.getStatus());
for (User user:responseUser.getResult()) System.out.println(user);
I get an empty list.
Second not working solution
Inspired by this article http://blog.bdoughan.com/2012/11/creating-generic-list-wrapper-in-jaxb.html I've modified the Response class:
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Response<T> {
#XmlElement
protected String status;
#XmlAnyElement(lax=true)
protected List<T> result;
}
And then tested it with this code:
Response<Project> responseProject = unmarshal(unmarshaller, Project.class, "responseProject.xml");
System.out.println(responseProject.getStatus());
for (Project project:responseProject.getResult()) System.out.println(project);
private static <T> Response<T> unmarshal(Unmarshaller unmarshaller, Class<T> clazz, String xmlLocation) throws JAXBException {
StreamSource xml = new StreamSource(xmlLocation);
#SuppressWarnings("unchecked")
Response<T> wrapper = (Response<T>) unmarshaller.unmarshal(xml, Response.class).getValue();
return wrapper;
}
And I get this exception reading the response list:
Exception in thread "main" java.lang.ClassCastException: com.sun.org.apache.xerces.internal.dom.ElementNSImpl cannot be cast to org.test.Project
Note: I can't modify the original XML. There are more types other than Project and User.
Thanks to Blaise Doughan and his article I've found the solution.
First we need the Wrapper class provided in the article:
#XmlRootElement
public class Wrapper<T> {
private List<T> items;
public Wrapper() {
items = new ArrayList<T>();
}
public Wrapper(List<T> items) {
this.items = items;
}
#XmlAnyElement(lax=true)
public List<T> getItems() {
return items;
}
}
Then I've modified the Response class in order to use it:
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Response<T> {
#XmlElement
protected String status;
#XmlElement
protected Wrapper<T> result;
...
public Response(String status, List<T> result) {
this.status = status;
this.result = new Wrapper<>(result);
}
...
public List<T> getResult() {
return result.getItems();
}
...
}
Finally the unmarshalling code:
JAXBContext context = JAXBContext.newInstance(Response.class, Project.class, User.class, Wrapper.class);
Unmarshaller unmarshaller = context.createUnmarshaller();
StreamSource source = new StreamSource(new File("responseProject.xml"));
Response<Project> responseProject = (Response<Project>)unmarshaller.unmarshal(source);
System.out.println(responseProject.getStatus());
for (Project project:responseProject.getResult()) System.out.println(project);
source = new StreamSource(new File("responseUser.xml"));
Response<User> responseUser = (Response<User>)unmarshaller.unmarshal(source);
System.out.println(responseUser.getStatus());
for (User user:responseUser.getResult()) System.out.println(user);
I've added the Wrapper class to the context class list.
Alternatively you can add this annotation to the Response class:
#XmlSeeAlso({Project.class, User.class})
Using #XmlSeeAlso({Project.class, User.class}) on Response classes has the drawback of generating some garbage information on each entity in the list: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="userAccount"
<resources>
<links>
<link>
<rel>self</rel>
<uri>http://localhost:8080/salonea-1.0/rest/user-accounts?offset=0&limit=2</uri>
</link>
<link>
<rel>prev</rel>
<uri></uri>
</link>
<link>
<rel>next</rel>
<uri>http://localhost:8080/salonea-1.0/rest/user-accounts?offset=2&limit=2</uri>
</link>
</links>
<collection>
<user-account
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="userAccount">
<accountType>user</accountType>
<activationCode>638f502a0e409348ccc2e36c24907f0</activationCode>
<email>michzio#hotmail.com</email>
<login>michzio</login>
<password>sAmPL3#e</password>
<registrationDate>2015-09-03T17:30:03+02:00</registrationDate>
<userId>1</userId>
</user-account>
<user-account
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="userAccount">
<accountType>user</accountType>
<activationCode>334bc79d142a291894bd71881e38a719</activationCode>
<email>alicja#krainaczarow.com</email>
<login>alicja</login>
<password>zAczka!00</password>
<registrationDate>2015-09-03T17:30:03+02:00</registrationDate>
<userId>2</userId>
</user-account>
</collection>
</resources>

Categories