Jackson deserialize string to default enum [duplicate] - java

I am using REST web service/Apache Wink with Jackson 1.6.2. How do I annotate an enum field so that Jackson deserializes it?
Inner class
public enum BooleanField
{
BOOLEAN_TRUE { public String value() { return "1";} },
BOOLEAN_FALSE { public String value() { return "0";} },
Java Bean/Request object
BooleanField locked;
public BooleanField getLocked() {return locked;}
The Jackson docs state that it can do this via #JsonValue/#JsonCreator but provides no examples.
Anyone willing to spill the (java)beans, as it were?

If you are using Jackson 1.9, serialization would be done by:
public enum BooleanField {
BOOLEAN_TRUE("1")
;
// either add #JsonValue here (if you don't need getter)
private final String value;
private BooleanField(String value) { this.value = value; }
// or here
#JsonValue public String value() { return value; }
so change you need is to add method to Enum type itself, so all values have it. Not sure if it would work on subtype.
For #JsonCreator, having a static factory method would do it; so adding something like:
#JsonCreator
public static BooleanField forValue(String v) { ... }
Jackson 2.0 will actually support use of just #JsonValue for both, including deserialization.

With Jackson 2.6 or newer, the #JsonProperty annotation can be applied directly to the enum constant to change its serialization:
public enum BooleanField
{
#JsonProperty("1")
BOOLEAN_TRUE,
#JsonProperty("0")
BOOLEAN_FALSE
}

don't annotate them, just configure your ObjectMapper instance:
private ObjectMapper createObjectMapper() {
final ObjectMapper mapper = new ObjectMapper();
// enable toString method of enums to return the value to be mapped
mapper.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING);
mapper.enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING);
return mapper;
}
and in your enum override the toString() method:
public enum SectionType {
START("start"),
MORE("more");
// the value which is used for matching
// the json node value with this enum
private final String value;
SectionType(final String type) {
value = type;
}
#Override
public String toString() {
return value;
}
}
You don't need any annotations or custom deserializers.

Actually, according to the docs for JsonValue (Jackson 2.3.3):
NOTE: when use for Java enums, one additional feature is
* that value returned by annotated method is also considered to be the
* value to deserialize from, not just JSON String to serialize as.
* This is possible since set of Enum values is constant and it is possible
* to define mapping, but can not be done in general for POJO types; as such,
* this is not used for POJO deserialization.
So for enums, your deserialization will not work using JsonCreator because JsonValue will be used for both serialization and deserialization.
One way to do this for enums is using JsonSetter and JsonGetter.

public enum BooleanField
{
BOOLEAN_TRUE("1"),
BOOLEAN_FALSE("0");
private final String value;
BooleanField( int value ) { this.value = value; }
}
Deserializer
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.ObjectCodec;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
public class BooleanFieldDeserializer extends Json Deserializer<BooleanField> {
public BooleanField deserialize( JsonParser p, DeserializationContext ctx )
throws IOException
{
// boilerplate code for every deserializer
ObjectCodec objectCodec = p.getCodec();
JsonNode node = objectCodec.readTree(p);
// customizable part for your impl
String booleanFieldString = node.asText();
return valueOf( booleanFieldString ); <- Enum-supplied method
}
Then, in your JavaBean...
#JsonDeserialize(using = BooleanFieldDeserializer.class)
BooleanField locked;

The following may work if the enumeration is an array or not. (Only for deserialization)
package com.stack.model;
import java.util.HashMap;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import lombok.Data;
#Data
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonPropertyOrder({ "success", "my-enums" })
public class MyObjectJSON {
#JsonProperty("sucess")
private boolean success;
#JsonProperty("my-enums")
private MyEnum[] myEnums;
static enum MyEnum {
Enum1, Enum2, Enum3, Enum4, EnumN;
private static Map<String, MyEnum> myEnumsMap = new HashMap<String, MyEnum>(5);
static {
myEnumsMap.put("enum1-val", Enum1);
myEnumsMap.put("enum2-val", Enum2);
myEnumsMap.put("enum3-val", Enum3);
myEnumsMap.put("enum4-val", Enum4);
myEnumsMap.put("enumn-val", EnumN);
}
#JsonCreator
public static MyEnum forValue(String value) {
return myEnumsMap.get(value.toLowerCase());
}
}
}
To consider:
The #Data annotation generates setters, getters, toString, etc.
#JsonProperty("my-enums") private MyEnum[] myEnums, this is the way to annotate with jackson the field that is of type Enum (
It works if it is an array or not).
MyEnum is the enumeration of the values ​​to be mapped of the JSON object, suppose the following object:
{
"sucess": true,
"my-enums": ["enum1-val", "enum3-val"]
}
The forValue function allows mapping the string values ​​of the array to Enum, it is annotated with #JsonCreator to indicate a construction factory used in deserialization.

Related

Java Spring custom Enum to String conversion in JSON Serialization

I'm trying to convert an enum value into a custom string as part of a JSON response in a Java Spring application. I've attempted to override the enum's toString method and create a Spring converter but both attempts don't seem to work.
Sample Controller
#RequestMapping(value = "/test/endpoint", produces = APPLICATION_JSON_VALUE)
#RestController
public class RecommenderController {
...
#GetMapping("test")
public List<MyEnum> test() {
return new ArrayList<>() {{
this.add(MyEnum.SAMPLE);
}};
}
}
Enum
public enum MyEnum {
SAMPLE("sample"), OTHER_SAMPLE("other sample");
private final String name;
public MyEnum(String name) {
this.name = name;
}
public String toString() {
return this.name;
}
}
This code returns the response ["SAMPLE"] although I want it to return ["sample"]. Is there a way to implement this in Spring?
Assuming you are using the default MappingJackson2HttpMessageConverter, then behind the scenes you are using Jackson's ObjectMapper to perform all the JSON serialization and deserialization. So it's a matter of configuring Jackson for your protocol objects.
In this case, it's probably most straightforward tell Jackson that it can make a single JSON value for your instance of MyEnum with the #JsonValue annotation.
public enum MyEnum {
SAMPLE("sample"), OTHER_SAMPLE("other sample");
private final String name;
public MyEnum(String name) {
this.name = name;
}
#JsonValue
public String getValue() {
return this.name;
}
}
#JsonValue has a bonus, as described in its Javadoc:
NOTE: when use for Java enums, one additional feature is that value returned by annotated method is also considered to be the value to deserialize from, not just JSON String to serialize as. This is possible since set of Enum values is constant and it is possible to define mapping, but can not be done in general for POJO types; as such, this is not used for POJO deserialization.
So if you have the same Enum definition in your application that receives the list, it will deserialize the human readable value back into your Enum.
This can be done by using the #JsonValue annotation in the enum definition:
public enum MyEnum {
...
#JsonValue
public String getName() {
return this.name;
}
}

How to solve conflicting getter definitions for property in jackson without access to source

I'm getting this error:
HTTP Status 500 - Could not write JSON: Conflicting getter definitions for property "oid"
The problem is that the class has two similar methods:
getOID (deprecated) and getOid
But I cannot modify the class as it's just a dependency.
Is there a way to fix this issue?
If you can not modify POJO you can implement custom serialiser or use MixIn feature.
Assume, your class looks like below:
class Id {
private int oid;
#Deprecated
public int getOID() {
return oid;
}
public int getOid() {
return oid;
}
public void setOid(int oid) {
this.oid = oid;
}
#Override
public String toString() {
return "oid=" + oid;
}
}
You need to create an interface with extra configuration:
interface IdIgnoreConflictMixIn {
#JsonIgnore
int getOID();
#JsonProperty
int getOid();
}
Now, you need to register this interface:
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
public class JsonMixInApp {
public static void main(String[] args) throws IOException {
Id id = new Id();
id.setOid(1);
ObjectMapper mapper = new ObjectMapper();
mapper.addMixIn(Id.class, IdIgnoreConflictMixIn.class);
mapper.writeValue(System.out, id);
}
}
Above code prints:
{"oid":1}
See also:
Custom Jackson Serializer for a specific type in a particular class
What is equivalent code settings for #JSonIgnore annotation?
Jackson parse json with unwraping root, but without ability to set #JsonRootName
Make Jackson serializer override specific ignored fields

JSON deserialization to Enum with Java 8, Spring Boot Starter 2.1.9 and Lombok 1.18.10

I'm trying to deserialize to a enumeration but the JSON values (lowercase) differs from the enumeration constants (uppercase).
This is the enumeration:
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Getter;
#Getter
#AllArgsConstructor
public enum ContractTypes {
#JsonProperty("product")
PRODUCT("product"),
#JsonProperty("service")
SERVICE("service");
private String value;
}
As you can see, I have annotated the elements with the #JsonPropertyannotation to try to map the provided value to the suitable constant.
I've also tryied to annotated the attribute value with a #JsonValue annotation. In both cases I obtain the same result:
Field error in object 'createContractRequestDto' on field 'contractType': rejected value [product]; codes [typeMismatch.createContractRequestDto.contractType,typeMismatch.contractType,typeMismatch.enm.ContractTypes,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [createContractRequestDto.contractType,contractType]; arguments []; default message [contractType]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'enm.ContractTypes' for property 'contractType'; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [enm.ContractTypes] for value 'product'; nested exception is java.lang.IllegalArgumentException: No enum constant enm.ContractTypes.product]]
Why is not working the #JsonProperty and #JsonValue annotations? How must I code the solution to map the JSON value to the suitable enumeration element?
We can also create a custom converter.
public class ContractTypesConverter implements Converter<String, ContractTypes> {
#Override
public ContractTypes convert(String source) {
return ContractTypes.valueOf(source.toUpperCase());
}
}
this can be further written as like this (Thanks to lambda)
Converter<String, ContractTypes> converter = source -> ContractTypes.valueOf(source.toUpperCase());
And Register it with WebMvcConfigurer like this
#Configuration
public class WebConfiguration implements WebMvcConfigurer {
// ... other configurations
#Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new ContractTypesConverter());
}
}
This worked for me. Not sure what you have missed in your code.
#Getter
public enum ContractTypes {
PRODUCT("product"),
SERVICE("service");
private String value;
ContractTypes(String value){
this.value = value;
}
#JsonValue
public String getValue() {
return value;
}
}
#NoArgsConstructor
#Setter
#Getter
static class Holder {
private ContractTypes contractTypes;
}
#Test
public void test() throws IOException {
Holder holder = new ObjectMapper().readValue("{\"contractTypes\":\"product\"}", Holder.class);
assertEquals(ContractTypes.PRODUCT, holder.contractTypes);
}
Those annotations work on the property not on enum type.
You can use enum directly using like PRODUCT or write a Custom Deserializer and use that for deserialization annotating on property.
#JsonDeserialize(using = ContractTypesDeserializer.class)
ContractTypes contractTypes;
Customer Deserializer implementation
public class ContractTypesDeserializer extends StdDeserializer<ContractTypes> {
private static final long serialVersionUID = -4714891596189L;
public ContractTypesDeserializer() {
super ContractTypes.class);
}
protected ContractTypesDeserializer(Class ContractTypes> type) {super(type);}
#Override
public ContractTypes deserialize(JsonParser parser, DeserializationContext context)
throws IOException, JsonProcessingException {
return ContractTypes.valueOf(parser.getText().toUpperCase());
}
}

Writing custom deserializer for polymorphic type hierarchy Jackson

I'm experimenting with Jackson deserialization for inheritance in Java.
I've a base class:
#Getter //Lombok #Getter
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", include = JsonTypeInfo.As.PROPERTY)
#JsonSubTypes(value = {
#JsonSubTypes.Type(value=ClassA.class, name = "classA")
})
public abstract class BaseClass {
private List<String> fields;
#JsonCreator
public BaseClass(#JsonProperty("fields") final List<String> fields) {
this.fields = fields;
}
}
ClassA is also abstract
#Getter
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "typeA", include = JsonTypeInfo.As.PROPERTY)
#JsonSubTypes(value = {
#JsonSubTypes.Type(value=SubClassA.class, name = "subclassA")
})
public abstract class ClassA extends BaseClass{
private String mode;
#JsonCreator
public ClassA(#JsonProperty("fields") final List<String> fields, #JsonProperty("mode") String mode) {
super(fields);
this.mode = mode;
}
}
My subClassA:
public class SubClassA extends ClassA {
private String dummyField;
public SubClassA(#JsonProperty("fields") final List<String> fields, #JsonProperty("mode") String mode,
#JsonProperty("dummyField") String dummyField) {
super(fields, mode);
this.dummyField = dummyField;
}
}
If I pass in a JSON of in the following form:
{
"type": "classA",
"typeA": "subclassA",
"mode": "testingMode",
"fields": ["1", "2"],
"dummyField": "dummy"
}
I get an error Cannot construct instance of ClassA (no Creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
I came across this https://github.com/FasterXML/jackson-databind/issues/374 which says this is a known issue with Jackson.
How do I go about writing a customDeserializer for this.
In classA I tried doing this:
#JsonDeserialize(using = ClassADeserializer.class)
and ClassADeserializer is:
public class ClassADeserializer extends StdDeserializer<ClassA> {
private final JsonDeserializer<?> defaultDeserializer;
public ClassADeserializer(JsonDeserializer<?> defaultDeserializer) {
super(ClassA.class);
this.defaultDeserializer = defaultDeserializer;
}
#Override public ClassA deserialize(final JsonParser jsonParser, final DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
return (ClassA) defaultDeserializer.deserialize(jsonParser, deserializationContext);
}
which obviously doesn't work. How do I go about writing a custom deserializer for this?
Problem:
You pass in json "type": "classA",... That means jackson first try to create instance of ClassA..During deserialization jackson search #JsonCreator constructor first..If #JsonCreator missing or can not call #JsonCreator constructor then jackson create object with default constructor and call setter method... In your ClassA #JsonCreator constructor with 2 arguments but jackson call with 3 arguments.. So its fail. then jackson call default constructor to create instance. but default constructor also missing.. thats why u get this error: Cannot construct instance of ClassA (no Creators, like default construct, exist)..
Solution:
As you want to deserialize to SubClassA... You need to use #JsonCreator in SubClassA...Then you need to use #JsonIgnoreProperties to ignore properties type so that jackson create instance of SubClassA instead of ClassA....
Try with below SubClassA:
#Getter
#JsonIgnoreProperties(ignoreUnknown = true)
public class SubClassA extends ClassA {
private String dummyField;
#JsonCreator
public SubClassA(#JsonProperty("fields") final List<String> fields, #JsonProperty("mode") String mode,
#JsonProperty("dummyField") String dummyField) {
super(fields, mode);
this.dummyField = dummyField;
}
}

How to annotate enum fields for deserialization using Jackson json

I am using REST web service/Apache Wink with Jackson 1.6.2. How do I annotate an enum field so that Jackson deserializes it?
Inner class
public enum BooleanField
{
BOOLEAN_TRUE { public String value() { return "1";} },
BOOLEAN_FALSE { public String value() { return "0";} },
Java Bean/Request object
BooleanField locked;
public BooleanField getLocked() {return locked;}
The Jackson docs state that it can do this via #JsonValue/#JsonCreator but provides no examples.
Anyone willing to spill the (java)beans, as it were?
If you are using Jackson 1.9, serialization would be done by:
public enum BooleanField {
BOOLEAN_TRUE("1")
;
// either add #JsonValue here (if you don't need getter)
private final String value;
private BooleanField(String value) { this.value = value; }
// or here
#JsonValue public String value() { return value; }
so change you need is to add method to Enum type itself, so all values have it. Not sure if it would work on subtype.
For #JsonCreator, having a static factory method would do it; so adding something like:
#JsonCreator
public static BooleanField forValue(String v) { ... }
Jackson 2.0 will actually support use of just #JsonValue for both, including deserialization.
With Jackson 2.6 or newer, the #JsonProperty annotation can be applied directly to the enum constant to change its serialization:
public enum BooleanField
{
#JsonProperty("1")
BOOLEAN_TRUE,
#JsonProperty("0")
BOOLEAN_FALSE
}
don't annotate them, just configure your ObjectMapper instance:
private ObjectMapper createObjectMapper() {
final ObjectMapper mapper = new ObjectMapper();
// enable toString method of enums to return the value to be mapped
mapper.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING);
mapper.enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING);
return mapper;
}
and in your enum override the toString() method:
public enum SectionType {
START("start"),
MORE("more");
// the value which is used for matching
// the json node value with this enum
private final String value;
SectionType(final String type) {
value = type;
}
#Override
public String toString() {
return value;
}
}
You don't need any annotations or custom deserializers.
Actually, according to the docs for JsonValue (Jackson 2.3.3):
NOTE: when use for Java enums, one additional feature is
* that value returned by annotated method is also considered to be the
* value to deserialize from, not just JSON String to serialize as.
* This is possible since set of Enum values is constant and it is possible
* to define mapping, but can not be done in general for POJO types; as such,
* this is not used for POJO deserialization.
So for enums, your deserialization will not work using JsonCreator because JsonValue will be used for both serialization and deserialization.
One way to do this for enums is using JsonSetter and JsonGetter.
public enum BooleanField
{
BOOLEAN_TRUE("1"),
BOOLEAN_FALSE("0");
private final String value;
BooleanField( int value ) { this.value = value; }
}
Deserializer
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.ObjectCodec;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
public class BooleanFieldDeserializer extends Json Deserializer<BooleanField> {
public BooleanField deserialize( JsonParser p, DeserializationContext ctx )
throws IOException
{
// boilerplate code for every deserializer
ObjectCodec objectCodec = p.getCodec();
JsonNode node = objectCodec.readTree(p);
// customizable part for your impl
String booleanFieldString = node.asText();
return valueOf( booleanFieldString ); <- Enum-supplied method
}
Then, in your JavaBean...
#JsonDeserialize(using = BooleanFieldDeserializer.class)
BooleanField locked;
The following may work if the enumeration is an array or not. (Only for deserialization)
package com.stack.model;
import java.util.HashMap;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import lombok.Data;
#Data
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonPropertyOrder({ "success", "my-enums" })
public class MyObjectJSON {
#JsonProperty("sucess")
private boolean success;
#JsonProperty("my-enums")
private MyEnum[] myEnums;
static enum MyEnum {
Enum1, Enum2, Enum3, Enum4, EnumN;
private static Map<String, MyEnum> myEnumsMap = new HashMap<String, MyEnum>(5);
static {
myEnumsMap.put("enum1-val", Enum1);
myEnumsMap.put("enum2-val", Enum2);
myEnumsMap.put("enum3-val", Enum3);
myEnumsMap.put("enum4-val", Enum4);
myEnumsMap.put("enumn-val", EnumN);
}
#JsonCreator
public static MyEnum forValue(String value) {
return myEnumsMap.get(value.toLowerCase());
}
}
}
To consider:
The #Data annotation generates setters, getters, toString, etc.
#JsonProperty("my-enums") private MyEnum[] myEnums, this is the way to annotate with jackson the field that is of type Enum (
It works if it is an array or not).
MyEnum is the enumeration of the values ​​to be mapped of the JSON object, suppose the following object:
{
"sucess": true,
"my-enums": ["enum1-val", "enum3-val"]
}
The forValue function allows mapping the string values ​​of the array to Enum, it is annotated with #JsonCreator to indicate a construction factory used in deserialization.

Categories