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.
Related
I'm wondering how, calling an external API service to get a list of object mapped by my class.
This is what the XML looks like
<ArrayOfObject>
<Object>
<id></id>
<name></name>
<var1></var1>
<var2></var2>
<var3></var3>
</Object>
<Object>
<id></id>
<name></name>
<var1></var1>
<var2></var2>
<var3></var3>
</Object>
...
</ArrayOfObject>
I'm trying to get only the id and name so i've tried to map in my class only theese two fields with #JsonIgnoreProperties annotation:
#JsonIgnoreProperties
public class Object{
public String id, name;
public Object() {}
public Object(String id, String name) {
this.id = id;
this.name= name;
}
public String getId() {return id;}
public void setId(String id) {
this.id = id;
}
public String getName() {return name;}
public void setName(String name) {
this.name = name;
}
and wrap it into
public class ArrayOfObject {
#JacksonXmlElementWrapper(useWrapping = false)
private List<Object> object;
public ArrayOfObject () {
object = new ArrayList<>();
}
public List<Object> getObjects() {return objects;}
public void setObjects(List<Object> objects) {
this.object = objects;
}
}
So now this is what my controller looks like
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
#RestController
#RequestMapping
public class RestCall {
#GetMapping("/objects")
public List<Object> getObject(){
final String uri = "";
RestTemplate rt = new RestTemplate();
ArrayOfObject result = rt.getForObject(uri, ArrayOfObject.class);
return result.getObjects();
}
}
that's my pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>it.me</groupId>
<artifactId>Reading-test</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>Reading-test</name>
<description>Service</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
I'm wondering why it gives back an empty list, am i missing something in the serialization or could be something else?
It would be easier to use a for loop and adding those object to a list?
If it would how can i implement something like that?
This is part of a previous question but not a duplicate.
Don't know if anybody cares but i've solved adding a few annotations and now looks like:
package it.me.model;
import java.io.Serializable;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
#JacksonXmlRootElement(localName = "Object")
public class Object implements Serializable{
private static final long serialVersionUID = 1L;
#JacksonXmlProperty
private String id;
#JacksonXmlProperty
private String name;
#JacksonXmlProperty
private String var1;
#JacksonXmlProperty
private String var2;
#JacksonXmlProperty
private String var3;
public Object() {}
public Object(String id, String name, String var1, String var2, String var3) {
this.id = id;
this.name = name;
this.var1= var1;
this.var2= var2;
this.var3= var3;
}
//getters n setters
public static long getSerialversionuid() {return serialVersionUID;}
//tostring, equals, hashcode overrides
}
That's the wrapper class:
package it.me.model;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
#JacksonXmlRootElement
public class ArrayOfObject implements Serializable{
private static final long serialVersionUID = 1L;
#JacksonXmlProperty(localName = "Object")
#JacksonXmlElementWrapper(useWrapping = false)
private List<Object> ArrayOfObject = new ArrayList<>();
public ArrayOfObject() {}
public ArrayOfObject(List<Object> arrayOfObject) {
ArrayOfObject = arrayOfObject;
}
public List<Object> getArrayOfObject() {return ArrayOfObject;}
public void setArrayOfObject(List<Object> arrayOfObject) {
ArrayOfObject = arrayOfObject;
}
public static long getSerialversionuid() {return serialVersionUID;}
}
In needed in my pom.xml thoose: (not sure about the 2nd one)
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
</dependency>
Finally that's the controller:
package it.me.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import it.me.model.ArrayOfObject;
#RestController
#RequestMapping
public class Controller {
#GetMapping("/objects")
public ArrayOfObject getObjects(){
final String uri = ""
RestTemplate rt = new RestTemplate();
ArrayOfObject result = rt.getForObject(uri, ArrayOfObject.class);
return result;
}
}
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.
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.
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.
I am trying to let Jackson ignore some properties of a DTO, however I can't seem to be able to.
I have a fairly big project with many dependencies (Lombok, Spring, GWT, Gemfire and many more),
and I can't change these dependencies (maybe I can change versions, but it's not my call).
I have prepared a test case, here it is:
this is my test dto class, it has a map that is only useful
server side. A copy of this dto is serialized to be sent to gwt
to display (the implementation is not complete, only the relevant parts
are shown).
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreType;
import lombok.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
#Getter
#Setter
#Builder
#NoArgsConstructor
#AllArgsConstructor
#EqualsAndHashCode(of = "id", callSuper = true)
public class MyClass extends MyAbstractClass {
#Getter
#Setter
#Builder
public static class AValueClass {
int someInt;
String SomeString;
}
#Data
#AllArgsConstructor
#NoArgsConstructor
#JsonIgnoreType
public static class MyJsonIgnoreKeyClass {
protected Integer anInt;
protected String aString;
}
#JsonIgnore
#Getter(AccessLevel.NONE) #Setter(AccessLevel.NONE)
private transient Map<MyJsonIgnoreKeyClass, List<AValueClass>> aMapThatJacksonShouldIgnore = new HashMap<>();
public void addToMap(MyJsonIgnoreKeyClass key, AValueClass value) {
List<AValueClass> valueList = aMapThatJacksonShouldIgnore.get(key);
if(valueList == null) {
valueList = new ArrayList<>();
}
valueList.add(value);
aMapThatJacksonShouldIgnore.put(key,valueList);
}
public boolean noMap() {
return aMapThatJacksonShouldIgnore == null || aMapThatJacksonShouldIgnore.keySet().isEmpty();
}
public void nullifyMap() {
aMapThatJacksonShouldIgnore = null;
}
// other methods operating on maps omitted
}
the test model inherits some fields from a superclass
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import java.util.Date;
#Setter
#Getter
#EqualsAndHashCode(of = "id")
public class MyAbstractClass {
protected String id;
protected Date aDay;
}
here are the unit tests I've prepared
public class MyClassJacksonTest {
ObjectMapper om;
#Before
public void setUp() throws Exception {
om = new ObjectMapper().registerModule(new Jdk8Module());
om.setSerializationInclusion(JsonInclude.Include.NON_NULL);
om.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
}
#Test
public void testWithMapValues() throws Exception {
MyClass testClass = new MyClass();
testClass.setADay(new Date());
testClass.setId(UUID.randomUUID().toString());
testClass.addToMap(
new MyClass.MyJsonIgnoreKeyClass(1,"test"),
new MyClass.AValueClass(1,"test"));
StringWriter writer = new StringWriter();
om.writeValue(writer,testClass);
writer.flush();
String there = writer.toString();
MyClass andBackAgain = om.readValue(there, MyClass.class);
assertTrue(andBackAgain.noMap());
}
#Test
public void testWithEmptyMaps() throws Exception {
MyClass testClass = new MyClass();
testClass.setADay(new Date());
testClass.setId(UUID.randomUUID().toString());
StringWriter writer = new StringWriter();
om.writeValue(writer,testClass);
writer.flush();
String there = writer.toString();
MyClass andBackAgain = om.readValue(there, MyClass.class);
assertTrue(andBackAgain.noMap());
}
#Test
public void testWithNullMaps() throws Exception {
MyClass testClass = new MyClass();
testClass.setADay(new Date());
testClass.setId(UUID.randomUUID().toString());
testClass.nullifyMap();
StringWriter writer = new StringWriter();
om.writeValue(writer,testClass);
writer.flush();
String there = writer.toString();
MyClass andBackAgain = om.readValue(there, MyClass.class);
assertTrue(andBackAgain.noMap());
}
}
All of the tests are failing with
com.fasterxml.jackson.databind.JsonMappingException: Can not find a (Map) Key deserializer for type [simple type, class MyClass$MyJsonIgnoreKeyClass]
So the questions are :
Why Jackson tries to find a deserializer for the keys of a map that can't be accessed (since there are no getter and setter) and that is annotated with #JsonIgnore?
More importantly, how can I tell it not to search for the deserializers?
These are the relevant dependencies on my pom, if it can be of any help :
<properties>
<!-- ... -->
<jackson.version>2.7.4</jackson.version>
</properties>
<dependencies>
<!-- other dependencies omitted -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-jsonSchema</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jdk8</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr353</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>${jackson.version}</version>
<exclusions>
<exclusion>
<groupId>org.codehaus.woodstox</groupId>
<artifactId>stax2-api</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
It turns out that this is a case of bad interaction between Lombok and Jackson.
The Lombok annotation #AllArgsConstructor generates a constructor that is annotated with #ConstructorProperties, which in turn lists all the properties that are declared in the class.
This is then used by Jackson when the default deserializer is to be used.
In this case, the absence of setters and getters and the presence of #JsonIgnore annotations is not taken into account.
The solution is simply to specify the #AllArgsConstructor with the attribute suppressConstructorProperties set to true :
#Getter
#Setter
#Builder
#NoArgsConstructor
#AllArgsConstructor(suppressConstructorProperties = true)
#EqualsAndHashCode(of = "id", callSuper = true)
public class MyClass extends MyAbstractClass {
// everything else is unchanged
Tricky one, indeed. What I think is happening is that you are generating a public all arguments constructor with Lombok. When deserializing, that is the one that Jackson will try to use. If you change your annotation for MyClass to
#AllArgsConstructor(access = AccessLevel.PRIVATE)
... it should work fine. Good luck!