How to fix MessageBodyProviderNotFoundException with Jersey Client using Jackson? - java

I have set up a REST client consuming JSON with Jersey. Firstly according to Baeldung's tutorial with MOXy and secondly according to Vaadin's tutorial with Jackson.
The JSON response from the REST service looks as follows:
{
"DisplayName": "Sixpack, Joe",
"UserID": "joe.sixpack",
"StdLog": "Some text"
}
So I have set up a simple JAXB bean ...
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class DisplayName {
private String displayName;
private String userID;
private String stdLog;
public DisplayName() {
}
public DisplayName(String DisplayName, String UserID, String StdLog) {
this.displayName = DisplayName;
this.userID = UserID;
this.stdLog = StdLog;
}
public String getDisplayName() {
return displayName;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
public String getUserID() {
return userID;
}
public void setUserID(String userID) {
this.userID = userID;
}
public String getStdLog() {
return stdLog;
}
public void setStdLog(String stdLog) {
this.stdLog = stdLog;
}
}
... added the Maven dependencies ...
<dependency>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-client</artifactId>
<version>2.22.2</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-moxy</artifactId>
<version>2.13</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.5</version>
</dependency>
... and implemented a REST client:
Client client = ClientBuilder.newClient();
URI uri = UriBuilder.fromUri("http://server:2000").path("/path/to/service/" + UriComponent.encode(input_parameter, UriComponent.Type.QUERY_PARAM_SPACE_ENCODED)).build();
WebTarget target = client.target(uri);
DisplayName responseDN = target.request(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON).get(DisplayName.class);
However, all the POJO fields are always null when using the MOXy media converter regardless of the annotations and constructors (e.g. no-args constructor) used according to similar issues (e.g. JAX-RS Response Object displaying Object fields as NULL values, Java REST service accepts POJO, but fields are always null).
So I want to use Jackson and adapted the JAXB class ...
import com.fasterxml.jackson.annotation.*;
#JsonInclude(JsonInclude.Include.ALWAYS)
#JsonPropertyOrder({"DisplayName","UserID","StdLog"})
public class DisplayName {
#JsonProperty("DisplayName")
private String displayName;
#JsonProperty("UserID")
private String userID;
#JsonProperty("StdLog")
private String stdLog;
public DisplayName() {
}
#JsonCreator
public DisplayName(#JsonProperty("DisplayName") String DisplayName, #JsonProperty("UserID") String UserID, #JsonProperty("StdLog") String StdLog) {
this.displayName = DisplayName;
this.userID = UserID;
this.stdLog = StdLog;
}
public String getDisplayName() {
return displayName;
}
#JsonProperty("DisplayName")
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
public String getUserID() {
return userID;
}
#JsonProperty("UserID")
public void setUserID(String userID) {
this.userID = userID;
}
public String getStdLog() {
return stdLog;
}
#JsonProperty("StdLog")
public void setStdLog(String stdLog) {
this.stdLog = stdLog;
}
}
... and removed the MOXy dependency and added a dependency for JAXB:
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>2.25.1</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
However, now I run into the error
org.glassfish.jersey.message.internal.MessageBodyProviderNotFoundException: MessageBodyReader not found for media type=application/json
and none of the solutions I found did help (e.g. MessageBodyReader not found for media type=application/json, Glassfish :MessageBodyProviderNotFoundException in Jersy Client).
Why? And what is the solution?

Shortly after I had posted the question, I found the explanation and solution in the Jersey User Guide which says:
In order to use Jackson as your JSON (JAXB/POJO) provider you need to register JacksonFeature and a ContextResolver for ObjectMapper, if needed, in your Configurable (client/server).
Since MOXy is the default media library and registered automatically with the WebClient, it will be used unless another media library is registered. Hence, one has to register the Jackson media converter ...
Client client = ClientBuilder.newClient().register(JacksonFeature.class);
... and add the respective Maven dependency:
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>2.25.1</version>
</dependency>
I'm just surprised that neither the tutorials nor the other answers to similar questions mentioned it.

Related

How to print the attributes from request in response in java using RestFull webservice?

I have been following a tutorial of RestFull webservices, but i am not able to understand some concepts.
Here is my PersonServiceImpl class.
#Path("/person")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public class PersonServiceImpl implements PersonService{
private static Map<Integer,Person> person = new HashMap<Integer,Person>();
#Override
#Path("/add")
#POST
public Response addPerson(Person p) {
Response response = new Response();
if(person.get(p.getId())!=null) {
response.setStatus(false);
response.setMessage("Person already exists");
}
person.put(p.getId(),p);
response.setStatus(true);
response.setMessage("Person added sucessfully ");
return response;
}
#Override
public Response deletePerson(int id) {
// TODO Auto-generated method stub
return null;
}
#Override
public Person getPerson(int id) {
// TODO Auto-generated method stub
return null;
}
#Override
public Person[] getAllPerson() {
// TODO Auto-generated method stub
return null;
}
}
I have made some changes in the code to produce and consume json file.
This is my person class
#XmlRootElement(name = "person")
public class Person {
private String name;
private int age;
private int id;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
And this is my Response class
#XmlRootElement
public class Response {
private boolean status;
private String message;
public boolean isStatus() {
return status;
}
public void setStatus(boolean status) {
this.status = status;
}
public String getMessage() {
return "" + message;
}
public void setMessage(String message) {
this.message= message;
}
}
And here is my pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>JAXRS-EXAMPLE</groupId>
<artifactId>JAXRS-EXAMPLE</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<dependencies>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-server</artifactId>
<version>1.19</version>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-json</artifactId>
<version>1.18.1</version>
</dependency>
<dependency>
<groupId>com.owlike</groupId>
<artifactId>genson</artifactId>
<version>0.99</version>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-servlet</artifactId>
<version>1.19</version>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-client</artifactId>
<version>1.19</version>
</dependency>
</dependencies>
<build>
<sourceDirectory>src</sourceDirectory>
<plugins>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>3.0.0</version>
<configuration>
<warSourceDirectory>WebContent</warSourceDirectory>
</configuration>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.3</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
I want to know :
How to print the values of person class in the response?
How the json values are mapped to the java attributes and where?
3.If i enter a extra value in the json request what happens to that value?
The link to the tutorial: https://www.journaldev.com/9170/restful-web-services-tutorial-java
First of all, it is not a good idea to implement your own Response class. The class javax.ws.rs.core.Response exists and should be used for a generic response of a JAX-RS method.
Regarding 1: If by 'print' you mean return the JSON representation of the Person instance, just do this:
#Override
public Person getPerson(int id) {
Person personForId = person.get(id);
if (personForId == null) {
thrown new NotFoundException();
}
return personForId;
}
The Person instance will be automatically serialized to a JSON representation.
Regarding 2: Since Person follows the Java Bean convention of getters and setters, JSON elements will be mapped by name to bean properties. This JSON
{
"status": true,
"message": "some message"
}
will be mapped to a Person instance with status = true and message = "some message".
There are plenty of annotations you can use on the class, on its fields or getters or setters that let you tweak this behaviour.
Regarding 3: This behaviour depends on the JSON serialization library you use. The widely used Jackson library provides the #JsonIgnoreProperties annotation. Other libraries may have different annotations for this purpose.

JPA Cannot Deserialize Java 8 LocalDateTime

I'm using Spring Boot 1.5.1 and getting an exception anytime I hit my API when there is a LocalDateTime field in my Entity class.
The MySQL dt column is TIMESTAMP
Is JPA not able to natively deserialize LocalDateTime?
Console output when performing GET request
2017-03-02 22:00:18.797 ERROR 13736 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.orm.jpa.JpaSystemException: could not deserialize; nested exception is org.hibernate.type.SerializationException: could not deserialize] with root cause
java.io.StreamCorruptedException: invalid stream header: 20323031
Reservation.class
package com.example.springboot.reservation;
import java.time.LocalDateTime;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
// Model class
#Entity
#Table(name="reservation")
public class Reservation {
#Id
private Long id;
#Column
private LocalDateTime dt;
#Column(name="user_id")
private Long userId;
// Hibernate will convert camel case column names to snake case!!!
// Don't use camelcase columns in DB
#Column(name="party_size")
private int partySize;
public Reservation() {}
public Reservation(Long id, Long userId, int partySize) {
this.id = id;
this.userId = userId;
this.partySize = partySize;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public LocalDateTime getDt() {
return dt;
}
public void setDt(LocalDateTime dt) {
this.dt = dt;
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public int getPartySize() {
return partySize;
}
public void setPartySize(int partySize) {
this.partySize = partySize;
}
}
pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.1.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<properties>
<java.version>1.8</java.version>
</properties>
#Converter
public class LocalDateTimeConverter implements AttributeConverter<java.time.LocalDateTime, java.sql.Timestamp> {
#Override
public java.sql.Timestamp convertToDatabaseColumn(java.time.LocalDateTime entityValue) {
return entityValue == null ? null : java.sql.Timestamp.valueOf(entityValue)
}
#Override
public java.time.LocalDateTime convertToEntityAttribute(java.sql.Timestamp dbValue) {
return dbValue == null ? null : dbValue.toLocalDateTime();
}
}
Make sure that this converter class is added to the package scanned by hibernate. Add this converter to the column declaration
#Column
#Convert(converter = LocalDateTimeConverter.class)
private LocalDateTime dt;
If you are not using JPA 2.0, This answer would help you use #Temporal annotation for LocalDateTime.
You don't need a converter if you use the newer hibernate-java8 which has LocalDateTime support.
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-java8</artifactId>
<version>${hibernate.version}</version>
</dependency>
You can write converter as described below:
#Converter(autoApply = true)
public class MyLocalDateConverter implements AttributeConverter<java.time.LocalDate, java.sql.Date> {
#Override
public java.sql.Date convertToDatabaseColumn(java.time.LocalDate attribute) {
return attribute == null ? null : java.sql.Date.valueOf(attribute);
}
#Override
public java.time.LocalDate convertToEntityAttribute(java.sql.Date dbData) {
return dbData == null ? null : dbData.toLocalDate();
}
}
You can find some already baked converters in spring package:
org.springframework.data.jpa.convert.threeten.Jsr310JpaConverters
JDocs:
JPA 2.1 converters to turn JSR-310 types into legacy Dates. To activate these converters make sure your persistence provider detects them by including this class in the list of mapped classes. In Spring environments, you can simply register the package of this class (i.e. org.springframework.data.jpa.convert.threeten) as package to be scanned on e.g. the LocalContainerEntityManagerFactoryBean.

Spring Boot Automatic JSON to Object at Controller

I have SpringBoot application with that dependencies:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jersey</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
I have a method at my controller as follows:
#RequestMapping(value = "/liamo", method = RequestMethod.POST)
#ResponseBody
public XResponse liamo(XRequest xRequest) {
...
return something;
}
I send a JSON object from my HTML via AJAX with some fields of XRequest type object (it is a plain POJO without any annotations). However my JSON is not constructed into object at my controller method and its fields are null.
What I miss for an automatic deserialisation at my controller?
Spring boot comes with Jackson out-of-the-box which will take care of un-marshaling JSON request body to Java objects
You can use #RequestBody Spring MVC annotation to deserialize/un-marshall JSON string to Java object... For example.
Example
#RestController
public class CustomerController {
//#Autowired CustomerService customerService;
#RequestMapping(path="/customers", method= RequestMethod.POST)
#ResponseStatus(HttpStatus.CREATED)
public Customer postCustomer(#RequestBody Customer customer){
//return customerService.createCustomer(customer);
}
}
Annotate your entities member elements with #JsonProperty with corresponding json field names.
public class Customer {
#JsonProperty("customer_id")
private long customerId;
#JsonProperty("first_name")
private String firstName;
#JsonProperty("last_name")
private String lastName;
#JsonProperty("town")
private String town;
}
SpringBoot by default comes with this functionality. You just have to use #RequestBody annotation in parameter declaration of your controller method but in contrast to #so-random-dude's answer you don't have to annotate fields with #JsonProperty, that is not required.
You just have to provide getters and setters for your custom XML object class. I am posting an example below for simplicity.
Example:
Controller method declaration:-
#PostMapping("/create")
public ResponseEntity<ApplicationResponse> createNewPost(#RequestBody CreatePostRequestDto createPostRequest){
//do stuff
return response;
}
Your custom XML object class:-
public class CreatePostRequestDto {
String postPath;
String postTitle;
public String getPostPath() {
return postPath;
}
public void setPostPath(String postPath) {
this.postPath = postPath;
}
public String getPostTitle() {
return postTitle;
}
public void setPostTitle(String postTitle) {
this.postTitle = postTitle;
}
}

How do I read a complex POJO from a flattened XML structure using Jackson?

I have some in-memory data structures that I load from an XML file, and want to expose a simplified API without tying the XML or any users of the data structures to the implementation details. I'm using Jackson for the XML unmarshalling.
Data Structures
As you can see in the code, the Profile class contains an instance of User, but directly exposes the getters/setters for User's fields. I don't want to have a setUser(User)/getUser() because I want that implementation detail to be hidden from the public API.
Profile.java:
package com.example.data;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement(name = "profile")
public class Profile {
private User user;
private String email;
#XmlElement(name = "userID")
public String getUserID() {
return user.getUserID();
}
#XmlElement(name = "userID")
public void setUserID(String userID) {
user.setUserID(userID);
}
#XmlElement(name = "password")
public String getPassword() {
return user.getPassword();
}
#XmlElement(name = "password")
public void setPassword(String password) {
user.setPassword(password);
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
User.java:
package com.example.data;
public class User {
public String userID;
public String password;
public String getUserID() {
return userID;
}
public void setUserID(String userID) {
this.userID = userID;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
As you can see, I've tried adding #XmlElement JAXB annotations on the getters and setters without any success. I also tried #JacksonXmlProperty but didn't have any luck with that, either.
Data
I also don't want to have to wrap the <userID> and <password> in a <user> tag; I want the XMl to be flat as shown below.
profile.xml:
<?xml version="1.0" encoding="UTF-8"?>
<profile>
<userID>bob</userID>
<password>letmein</password>
<email>bob#example.com</email>
</profile>
Application
JacksonXMLExample.java:
package com.example;
import java.io.File;
import java.io.IOException;
import com.example.data.Profile;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.module.jaxb.JaxbAnnotationModule;
public class JacksonXMLExample {
public static void main(String[] args) throws JsonParseException, JsonMappingException, IOException {
File file = new File("src/main/resources/profile.xml");
ObjectMapper mapper = new XmlMapper();
JaxbAnnotationModule module = new JaxbAnnotationModule();
mapper.registerModule(module);
Profile profile = mapper.readValue(file, Profile.class);
System.out.println(profile);
}
}
Maven
pom.xml:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>JacksonXMLExample</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>javax.xml</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.7.4</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.7.4</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.7.4</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-xml-provider</artifactId>
<version>2.7.4</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-jaxb-annotations</artifactId>
<version>2.7.4</version>
</dependency>
</dependencies>
</project>
The Problem
When I try executing the above code, I get the following exception:
Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: N/A
at [Source: src\main\resources\profile.xml; line: 3, column: 13] (through reference chain: com.example.data.Profile["userID"])
at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:262)
at com.fasterxml.jackson.databind.deser.SettableBeanProperty._throwAsIOE(SettableBeanProperty.java:537)
at com.fasterxml.jackson.databind.deser.SettableBeanProperty._throwAsIOE(SettableBeanProperty.java:518)
at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:99)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:260)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:125)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3807)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2691)
at com.example.JacksonXMLExample.main(JacksonXMLExample.java:21)
Caused by: java.lang.NullPointerException
at com.example.data.Profile.setUserID(Profile.java:18)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:97)
... 5 more
How do I use a complex POJO composed of other POJOs without having to mirror that structure in the corresponding XML or expose implementation details to user of the class?
As I was ready to post my question, I had a facepalm moment when I realized that all I needed was to initialize the user field in Profile.java:
private User user = new User();
This is necessary because, of course, you cannot delegate method calls to a member that has not been initialized.

How to custom serialize & deserialize enums using FasterXML? [duplicate]

I have an Enum desrcibed below:
public enum OrderType {
UNKNOWN(0, "Undefined"),
TYPEA(1, "Type A"),
TYPEB(2, "Type B"),
TYPEC(3, "Type C");
private Integer id;
private String name;
private WorkOrderType(Integer id, String name) {
this.id = id;
this.name = name;
}
//Setters, getters....
}
I return enum array with my controller ( new OrderType[] {UNKNOWN,TYPEA,TYPEB,TYPEC};), and Spring serializes it into the following json string:
["UNKNOWN", "TYPEA", "TYPEB", "TYPEC"]
What is the best approach to force Jackson to serialize enums just like POJOs? E.g.:
[
{"id": 1, "name": "Undefined"},
{"id": 2, "name": "Type A"},
{"id": 3, "name": "Type B"},
{"id": 4, "name": "Type C"}
]
I played with different annotations but couldn't manage to get such result.
Finally I found solution myself.
I had to annotate enum with #JsonSerialize(using = OrderTypeSerializer.class) and implement custom serializer:
public class OrderTypeSerializer extends JsonSerializer<OrderType> {
#Override
public void serialize(OrderType value, JsonGenerator generator,
SerializerProvider provider) throws IOException,
JsonProcessingException {
generator.writeStartObject();
generator.writeFieldName("id");
generator.writeNumber(value.getId());
generator.writeFieldName("name");
generator.writeString(value.getName());
generator.writeEndObject();
}
}
#JsonFormat(shape= JsonFormat.Shape.OBJECT)
public enum SomeEnum
available since https://github.com/FasterXML/jackson-databind/issues/24
just tested it works with version 2.1.2
answer to TheZuck:
I tried your example, got Json:
{"events":[{"type":"ADMIN"}]}
My code:
#RequestMapping(value = "/getEvent") #ResponseBody
public EventContainer getEvent() {
EventContainer cont = new EventContainer();
cont.setEvents(Event.values());
return cont;
}
class EventContainer implements Serializable {
private Event[] events;
public Event[] getEvents() {
return events;
}
public void setEvents(Event[] events) {
this.events = events;
}
}
and dependencies are:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
<exclusions>
<exclusion>
<artifactId>jackson-annotations</artifactId>
<groupId>com.fasterxml.jackson.core</groupId>
</exclusion>
<exclusion>
<artifactId>jackson-core</artifactId>
<groupId>com.fasterxml.jackson.core</groupId>
</exclusion>
</exclusions>
</dependency>
<jackson.version>2.1.2</jackson.version>
I've found a very nice and concise solution, especially useful when you cannot modify enum classes as it was in my case. Then you should provide a custom ObjectMapper with a certain feature enabled. Those features are available since Jackson 1.6.
public class CustomObjectMapper extends ObjectMapper {
#PostConstruct
public void customConfiguration() {
// Uses Enum.toString() for serialization of an Enum
this.enable(WRITE_ENUMS_USING_TO_STRING);
// Uses Enum.toString() for deserialization of an Enum
this.enable(READ_ENUMS_USING_TO_STRING);
}
}
There are more enum-related features available, see here:
https://github.com/FasterXML/jackson-databind/wiki/Serialization-features
https://github.com/FasterXML/jackson-databind/wiki/Deserialization-Features
Here is my solution. I want transform enum to {id: ..., name: ...} form.
With Jackson 1.x:
pom.xml:
<properties>
<jackson.version>1.9.13</jackson.version>
</properties>
<dependencies>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-core-asl</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>${jackson.version}</version>
</dependency>
</dependencies>
Rule.java:
import org.codehaus.jackson.map.annotate.JsonSerialize;
import my.NamedEnumJsonSerializer;
import my.NamedEnum;
#Entity
#Table(name = "RULE")
public class Rule {
#Column(name = "STATUS", nullable = false, updatable = true)
#Enumerated(EnumType.STRING)
#JsonSerialize(using = NamedEnumJsonSerializer.class)
private Status status;
public Status getStatus() { return status; }
public void setStatus(Status status) { this.status = status; }
public static enum Status implements NamedEnum {
OPEN("open rule"),
CLOSED("closed rule"),
WORKING("rule in work");
private String name;
Status(String name) { this.name = name; }
public String getName() { return this.name; }
};
}
NamedEnum.java:
package my;
public interface NamedEnum {
String name();
String getName();
}
NamedEnumJsonSerializer.java:
package my;
import my.NamedEnum;
import java.io.IOException;
import java.util.*;
import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.JsonProcessingException;
import org.codehaus.jackson.map.JsonSerializer;
import org.codehaus.jackson.map.SerializerProvider;
public class NamedEnumJsonSerializer extends JsonSerializer<NamedEnum> {
#Override
public void serialize(NamedEnum value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
Map<String, String> map = new HashMap<>();
map.put("id", value.name());
map.put("name", value.getName());
jgen.writeObject(map);
}
}
With Jackson 2.x:
pom.xml:
<properties>
<jackson.version>2.3.3</jackson.version>
</properties>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
</dependencies>
Rule.java:
import com.fasterxml.jackson.annotation.JsonFormat;
#Entity
#Table(name = "RULE")
public class Rule {
#Column(name = "STATUS", nullable = false, updatable = true)
#Enumerated(EnumType.STRING)
private Status status;
public Status getStatus() { return status; }
public void setStatus(Status status) { this.status = status; }
#JsonFormat(shape = JsonFormat.Shape.OBJECT)
public static enum Status {
OPEN("open rule"),
CLOSED("closed rule"),
WORKING("rule in work");
private String name;
Status(String name) { this.name = name; }
public String getName() { return this.name; }
public String getId() { return this.name(); }
};
}
Rule.Status.CLOSED translated to {id: "CLOSED", name: "closed rule"}.
An easy way to serialize Enum is using #JsonFormat annotation. #JsonFormat can configure the serialization of a Enum in three ways.
#JsonFormat.Shape.STRING
public Enum OrderType {...}
uses OrderType::name as the serialization method. Serialization of OrderType.TypeA is “TYPEA”
#JsonFormat.Shape.NUMBER
Public Enum OrderTYpe{...}
uses OrderType::ordinal as the serialization method. Serialization of OrderType.TypeA is 1
#JsonFormat.Shape.OBJECT
Public Enum OrderType{...}
treats OrderType as a POJO. Serialization of OrderType.TypeA is {"id":1,"name":"Type A"}
JsonFormat.Shape.OBJECT is what you need in your case.
A little more complicated way is your solution, specifying a serializer for the Enum.
Check out this reference:
https://fasterxml.github.io/jackson-annotations/javadoc/2.2.0/com/fasterxml/jackson/annotation/JsonFormat.html
Use #JsonCreator annotation, create method getType(), is serialize with toString or object working
{"ATIVO"}
or
{"type": "ATIVO", "descricao": "Ativo"}
...
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.JsonNodeType;
#JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum SituacaoUsuario {
ATIVO("Ativo"),
PENDENTE_VALIDACAO("Pendente de Validação"),
INATIVO("Inativo"),
BLOQUEADO("Bloqueado"),
/**
* Usuarios cadastrados pelos clientes que não possuem acesso a aplicacao,
* caso venham a se cadastrar este status deve ser alterado
*/
NAO_REGISTRADO("Não Registrado");
private SituacaoUsuario(String descricao) {
this.descricao = descricao;
}
private String descricao;
public String getDescricao() {
return descricao;
}
// TODO - Adicionar metodos dinamicamente
public String getType() {
return this.toString();
}
public String getPropertieKey() {
StringBuilder sb = new StringBuilder("enum.");
sb.append(this.getClass().getName()).append(".");
sb.append(toString());
return sb.toString().toLowerCase();
}
#JsonCreator
public static SituacaoUsuario fromObject(JsonNode node) {
String type = null;
if (node.getNodeType().equals(JsonNodeType.STRING)) {
type = node.asText();
} else {
if (!node.has("type")) {
throw new IllegalArgumentException();
}
type = node.get("type").asText();
}
return valueOf(type);
}
}
In Spring Boot 2, the easiest way is to declare in your application.properties:
spring.jackson.serialization.WRITE_ENUMS_USING_TO_STRING=true
spring.jackson.deserialization.READ_ENUMS_USING_TO_STRING=true
and define the toString() method of your enums.

Categories