JAX-RS JAXB Jackson not using #XmlRootElement name - java

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());
}
}

Related

Annotation based XML Marshalling in Spring Boot

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
}
}

Rename fields in response dto. Java, Spring

I have the following question. I want to rename fields in JSON response. In my DTO fields are defined in camal case. In response json a want to use separate words with underscores. I tried to use #JsonPropperty annotation but it is not working. My DTO class is below:
package com.example.demo.dto.response;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
#Data
public class UserResponseDto {
private String id;
private String name;
#JsonProperty("second_name")
private String secondName;
private String email;
private String login;
private String password;
#JsonProperty("date_of_birth")
private Date dateOfBirth;
#JsonProperty("date_of_registration")
private Date dateOfRegistration;
private Set<RoleResponseDto> roles = new HashSet<>();
}
And the response is still in camal case:
{
"id": "d6c0873b-166b-4d6f-90b4-91d9f0dfa0a0",
"name": "Simon",
"secondName": "Wilder",
"email": "myNewEmail#gmail.com",
"login": "QMY",
"password": "$2a$10$Lj3EctARwD34EInzmwsjjuTsKiOzKtI7Gf7pskUfxcPt1PNRDHF1m",
"dateOfBirth": "1991-12-21T00:00:00.000+00:00",
"dateOfRegistration": "2022-04-07T12:52:24.059+00:00",
"roles": [
{
"id": "a9cc27ba-532a-456a-a9b5-ad14052190f8",
"title": "ROLE_USER"
},
{
"id": "684bf3b6-721d-494c-9c66-caeee44933d2",
"title": "ROLE_ADMIN"
}
]
}
Do You have any ideas? Or it is a bug, or what? Everywhere it is written to use #JsonPropperty annotation.
So there are 2 ways to do what you want but first make sure you are using #ResponseBody on all your Controller methods or make sure your controller classes are using #RestController.
if you want the whole application to use the underscore case in the response body then add this to your configs:
Gson Method
#Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(createGsonHttpMessageConverter());
super.addDefaultHttpMessageConverters(converters);
}
#Bean
public Gson createGson() {
return new GsonBuilder().disableHtmlEscaping()
.serializeNulls()
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.create();
}
#Bean
public GsonHttpMessageConverter createGsonHttpMessageConverter() {
GsonHttpMessageConverter gsonConverter = new GsonHttpMessageConverter();
gsonConverter.setGson(createGson());
return gsonConverter;
}
}
Reference
Jackson Method
spring.jackson.property-naming-strategy=SNAKE_CASE
Reference:
if you want it on a single DTO then you can use
#JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
Refercences:

Binding data to RequestBody (415 error)

I have the following in my Controller:
#Controller
public class GreetingController {
#PostMapping("/register")
public String registerUser(#RequestBody UserEntity request) throws ServletException, IOException {
System.out.println(request.getId());
return "register";
}
}
The UserEntity is:
#Entity
#Table(name = "users")
public class UserEntity {
private int id;
private String name;
private String email;
private String password;
I get the following error:
There was an unexpected error (type=Unsupported Media Type, status=415).
Content type 'application/x-www-form-urlencoded;charset=UTF-8' not supported
Note that I have Jackson installed (from this question: Jackson Databind classpath issue).
Additionally, I am able to use the public String registerUser(HttpServletRequest request) fine, but when I try using #RequestBody it just gives me that error.
How would I get the #RequestBody to be the UserEntity?
You are using the header value "application/x-www-form-urlencoded;charset=UTF-8" in the request while you should use "application/json"

RestTemplate: no suitable HttpMessageConverter found exception

I am trying to parse following xml response in spring boot:
Response structure:
<!DOCTYPE RESULT SYSTEM 'http://www.example.com/example/exampleV1.00.dtd'>
<RESULT REQID ='10961549902'>
<MID SUBMITDATE='2017-08-14 17:17:29' ID='1' TAG = 'null' TID = '24180566483'></MID>
</RESULT>
Api call from Service:
RestTemplate restTemplate = new RestTemplate();
restTemplate.getForObject("http://www.exampleUrl.com",ResponseXml.class);
ResponseXml.java
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement(name = "RESULT")
public class ResponseXml implements Serializable{
#XmlAttribute(name="REQID")
private long requestId;
#XmlElement(name = "MID")
private long MID;
public ResponseXml() {
super();
}
public ResponseXml(long requestId) {
super();
this.requestId = requestId;
}
//getter setter
public class MID {
#XmlAttribute(name="SUBMITDATE")
private Date submitDate;
#XmlAttribute(name="ID")
private Long id;
#XmlAttribute(name="TAG")
private Long tag;
#XmlAttribute(name="TID")
private Long tid;
//getter setter
}
}
Exception thrown:
Method threw 'org.springframework.web.client.RestClientException' exception.
Detailed message: Could not extract response: no suitable HttpMessageConverter found
for response type [class com.hk.response.sms.NetcoreResponseXml]
and content type [text/plain;charset=UTF-8]
Please advice a fix, as to how do I parse xml response in JAVA spring boot?
Why am I getting HttpMessageConverter exception despite adding #XmlRootElement annotation?
If it is not possible to fix server then configure message converted in a client application to accept plain/text:
// Create converter which supports text/plain mime type.
MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
List<MediaType> mediaTypes = jsonConverter.getSupportedMediaTypes();
mediaTypes.add(MediaType.TEXT_PLAIN);
jsonConverter.setSupportedMediaTypes(mediaTypes);
// Alternatively get mapper as injected bean:
// #Inject
// private XmlMapper mapper;
XmlMapper mapper = new XmlMapper();
jsonConverter.setObjectMapper(mapper);
RestTemplate restTemplate = new RestTemplate();
// Register converter with RestTemplate
restTemplate.setMessageConverters(Arrays.asList(jsonConverter));

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