Jackson ignores #Ignore annotations - java

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!

Related

Exclude colums (fields) using Jackson CsvMapper to serialize POJO

I have a Java class and i want to serialize it into CSV using jackson.
In addition i want to exclude from the csv a single field base on a external property.
I have tried using all features provides by jackson like Feature.IGNORE_UNKNOWN=true or #JsonIgnoreProperties(ignoreUnknown = true) on my data class, csvMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) but none of them works. I still receive the exception column not found in schema when not all columns are declared in the schema. Code works fine in the other case.
Here i upload my implementation
public class MyClass{
#JsonProperty("A")
private string a;
#JsonProperty("B")
private string b;
#JsonProperty("C")
private string c;
}
CsvMapper mapper = new CsvMapper();
mapper.configure(Feature.IGNORE_UNKNOWN, true);
CsvSchema csvSchema;
if (disable = true) {
schema = CsvSchema.builder()
.addColumn("A")
.addColumn("C")
.build()
} else {
schema = CsvSchema.builder()
.addColumn("A")
.addColumn("B")
.addColumn("C")
.build()
}
ObjectWriter ow = mapper.writer(schema);
String csv = ow.writeValueAsString(list);
use #JsonIgnore on top of property.
Ex:
import com.fasterxml.jackson.annotation.JsonIgnore;
#Validated
#Data
#NoArgsConstructor
#AllArgsConstructor
#Getter
#Setter
#Builder
public class MyResponseModel {
...
#JsonIgnore
private String createBy;
...
}
Using Feature.IGNORE_UNKNOWN=true or #JsonIgnoreProperties(ignoreUnknown = true) will help when you want to deserialize JSON into your Java class.
For example, this will be useful when you're implementing the REST API and in the POST method dedicated to creating new objects, you want to ignore all invalid/incorrect fields sent to you by the user and process only the valid ones instead of returning the 400/500 error.
In your case, you just need to put the #JsonIgnore annotation on the field in your Java class, which you want to exclude during the serialization. This is an example of excluding the «a» property:
public class MyClass {
#JsonIgnore
#JsonProperty("A")
private String a;
#JsonProperty("B")
private String b;
#JsonProperty("C")
private String c;
}
This should also be useful in cases when you want to exclude some private and sensitive information from application logs, like passwords, PII, PHI, etc.
Updated:
This is the working solution for your case:
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
public class MyClass {
#JsonProperty("A")
private String a;
#JsonProperty("B")
private String b;
#JsonProperty("C")
private String c;
}
public String test() throws JsonProcessingException {
boolean disable = false;
CsvMapper mapper = new CsvMapper();
mapper.configure(JsonGenerator.Feature.IGNORE_UNKNOWN, true);
CsvSchema csvSchema;
List<MyClass> list = List.of(MyClass.builder()
.a("a1")
.b("b1")
.c("c1")
.build()
);
if (disable) {
csvSchema = CsvSchema.builder()
.addColumn("A")
.addColumn("C")
.build();
} else {
csvSchema = CsvSchema.builder()
.addColumn("A")
.addColumn("B")
.addColumn("C")
.build();
}
ObjectWriter ow = mapper.writer(csvSchema);
String csv = ow.writeValueAsString(list);
return csv;
}
Dependency, which I've been using for tests and verification of the above code:
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-csv</artifactId>
<version>2.13.3</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>

Jackson does not accept #XlmElement uppercase keys

I try to deserialize some classes for use in OpenAPI.
Like for example:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "MyClass", propOrder = {
"config",
"value"
})
public class MyClass
{
#XmlElement(name = "CONFIG")
protected MyConfig config;
#XmlElement(name = "VALUE")
protected MyValue value;
public MyConfig getCONFIG()
{
return config;
}
public MyValue getVALUE()
{
return value;
}
}
I am using these dependencies:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.8.4</version>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-jersey2-jaxrs</artifactId>
</dependency>
In my Swagger-UI i see that it generated this json:
{
"value": {
[...]
},
"config": {
[...]
}
}
But my REST-Interface only accepts the object if the properties are in UPPERCASE.
This is my object Mapper:
om = new ObjectMapper();
om.registerModule(new JaxbAnnotationModule());
What am i doing wrong here?
You could use MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES.
Feature that will allow for more forgiving deserialization of incoming JSON. If enabled, the bean properties will be matched using their lower-case equivalents, meaning that any case-combination (incoming and matching names are canonicalized by lower-casing) should work.
So:
om.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);

Jackson xml 2.9.0: #JacksonXmlElementWrapper not working with #JsonCreator & #JsonProperty constructor

I would like that my ParentClass has final fields, 'brokenChildList' list is wrapped xml element and list items have different tag than the list (<brokenChildList><brokenChild/></brokenChildList>).
Here is a snippet of code to reproduce my issues (imports are partially truncated, setters and getters omitted)
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
public class Main {
public static void main(String... args) throws IOException {
ObjectMapper xmlMapper = new XmlMapper();
String xmlString = "<ParentClass><childClass name=\"name1\" value=\"val1\"/><brokenChildList><brokenChild name=\"bc1\" reason=\"bc-val1\"/><brokenChild name=\"bc2\" reason=\"bc-val2\"/></brokenChildList></ParentClass>";
ParentClass parentClass = xmlMapper.readValue(xmlString, ParentClass.class);
StringWriter stringWriter = new StringWriter();
xmlMapper.writeValue(stringWriter, parentClass);
String serialised = stringWriter.toString();
System.out.println(serialised);
System.out.println(xmlString.equals(serialised));
}
public static class ChildClass {
#JacksonXmlProperty(isAttribute = true)
private String name;
#JacksonXmlProperty(isAttribute = true)
private String value;
//getters & setters
}
public static class BrokenChild {
#JacksonXmlProperty(isAttribute = true)
private String name;
#JacksonXmlProperty(isAttribute = true)
private String reason;
//getters & setters
}
public static class ParentClass {
private final ChildClass childClass;
private final List<BrokenChild> brokenChildList;
#JsonCreator
public ParentClass(
#JsonProperty("childClass") ChildClass childClass,
#JsonProperty("brokenChildList") List<BrokenChild> brokenChildList
) {
this.childClass = childClass;
this.brokenChildList = brokenChildList;
}
#JacksonXmlProperty(localName = "childClass")
public ChildClass getChildClass() {
return childClass;
}
#JacksonXmlElementWrapper(localName = "brokenChildList")
#JacksonXmlProperty(localName = "brokenChild")
public List<BrokenChild> getBrokenChildList() {
return brokenChildList;
}
}
}
The above code gives output with Jackson version 2.8.10:
<ParentClass><childClass name="name1" value="val1"/><brokenChildList><brokenChild name="bc1" reason="bc-val1"/><brokenChild name="bc2" reason="bc-val2"/></brokenChildList></ParentClass>
true
With Jackson version 2.9.0 it gives:
Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: Duplicate property 'brokenChildList' for [simple type, class org.test.Main$ParentClass]
at [Source: (StringReader); line: 1, column: 1]
I would like to find a solution (and any version after 2.9.0) that will give same output with the attached code.
My failed attempts include:
Replacing #JacksonXmlElementWrapper(localName = "brokenChildList") with #JacksonXmlElementWrapper will rename wrapper element as 'brokenChild' which is undesirable.
Removing #JacksonXmlElementWrapper(localName = "brokenChildList") will rename wrapper element as 'brokenChild' which is undesirable.
This problem is really tricky because Jackson collects metadata from different places: fields, getters, setters, constructor parameters. Also, you can use MixIn but in your case it does not appear.
#JacksonXmlElementWrapper annotation can be attached to FIELD and METHOD type elements and this forces you to declare it on getter. Because ParentClass is immutable and you want to build it with constructor we need to annotate constructor parameters as well. And this is where collision appears: you have a constructor parameter with #JsonProperty("brokenChildList") annotation and getter with #JacksonXmlElementWrapper(localName = "brokenChildList") which reuses the same name. If you would changed localName to #JacksonXmlElementWrapper(localName = "brokenChildListXYZ") (added XYZ) everything would be deserialised and serialised but output would be different then input.
To solve this problem, we can use com.fasterxml.jackson.databind.deser.BeanDeserializerModifier class which allows to filter out fields we do not want to use for deserialisation and which creates collision. Example usage:
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import java.io.IOException;
import java.io.StringWriter;
import java.util.List;
import java.util.stream.Collectors;
public class XmlMapperApp {
public static void main(String... args) throws IOException {
SimpleModule module = new SimpleModule();
module.setDeserializerModifier(new BeanDeserializerModifier() {
#Override
public List<BeanPropertyDefinition> updateProperties(DeserializationConfig config, BeanDescription beanDesc, List<BeanPropertyDefinition> propDefs) {
if (beanDesc.getBeanClass() == ParentClass.class) {
return propDefs.stream().filter(p -> p.getConstructorParameter() != null).collect(Collectors.toList());
}
return super.updateProperties(config, beanDesc, propDefs);
}
});
XmlMapper xmlMapper = XmlMapper.xmlBuilder()
.addModule(module)
.build();
//yours code
}
}
To create this example I used version 2.10.0.
See also:
Jackson 2.10 features
Jackson Release 2.10

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