Suppose I am trying to serialize the following with Jackson:
public class Investment implements Serializable{
private String shortName;
private MutualFund mutualFund;
public String getShortName() {
return shortName;
}
public void setShortName(String shortName) {
this.shortName = shortName;
}
public MutualFund getMutualFund() {
return mutualFund;
}
public void setMutualFund(MutualFund mutualFund) {
this.mutualFund = mutualFund;
}
}
That in turn refers to:
public class MutualFund implements Serializable{
private BigDecimal expenseRatio;
private Map<Investment, BigDecimal> underlyingInvestments;
public BigDecimal getExpenseRatio() {
return BigDecimalHelper.guard(expenseRatio);
}
public void setExpenseRatio(BigDecimal expenseRatio) {
this.expenseRatio = expenseRatio;
}
public Map<Investment, BigDecimal> getUnderlyingInvestments() {
return underlyingInvestments;
}
public void setUnderlyingInvestments(Map<Investment, BigDecimal>
underlyingFunds) {
this.underlyingInvestments = underlyingFunds;
}
}
When I try to serialize this with Jackson, everything else is fine except that I end up with an Investment object reference instead of the string with attributes like I was expecting:
"underlyingInvestments":{"com.financial.detail.Investment#5d465e4b":1}}
I've tried to fashion some custom serializers, but without success as I always get an object reference for the nested Investment(s). So, I have a two part question:
Can this problem be addressed simply with Jackson annotations?
If I have to build a custom serializer, could someone kindly point me in the right direction on how to best
approach this issue given the nested nature of this (e.g. an Investment could contain a Mutual Fund, which in turn has an Investment with a Mutual Fund...)
The problem is that you are using the object Investment as keys of a Map, so the question here is, what keys do you expect the json to have? Json keys cand only be strings so the mapper is executing the toString() method of the Investment class. If you know what that key should be, then you can implement the method, like this:
public class Investment implements Serializable {
private String shortName;
private MutualFund mutualFund;
// ...
#Override
public String toString() {
return shortName;
}
}
This will create a json object like this:
{
"expenseRatio": 1,
"underlyingInvestments": {
"shortName": 10
}
}
Also, as #chrylis-cautiouslyoptimistic suggested, another option is to use #JsonValue to indicate which method to use when serializing, like this:
public class Investment implements Serializable{
private String shortName;
private MutualFund mutualFund;
#JsonValue
public String getShortName() {
return shortName;
}
// ...
}
I need to be able to create a Java POJO from a JSON object when I only have an interface that can't be changed. I'm hoping that Mixins can help make this possible. I created a Mixin that hopefully will work but can't get Jackson to use it.
It appears that Jackson is ignoring the Mixin I am defining for both an Interface and an Implementation. The test failures are what I would expect without the Mixin added to the ObjectMapper.
Below is the simplest example that shows the problem. The classes are each in their own package. The real uses case is much more complex, including Lists of interfaces. I am using Jackson 2.10.3.
Any suggestions on what I'm doing wrong?
Timothy
What doesn't work
The interface reader test fails with InvalidDefinitionException: Cannot construct instance of model.Level4 (no Creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
Of secondary importance, the Mixin defines a new label (nameTest) for the name field which should be reflected in the output from writeValueAsString. It outputs the field with the original value for the label (name).
Interface
public interface Level4 {
public Long getId();
public void setId(Long id);
public String getName();
public void setName(String name);
}
Implementation
public class Level4Impl implements Level4 {
private Long id;
private String name;
#Override
public Long getId() {
return id;
}
#Override
public void setId(Long id) {
this.id = id;
}
#Override
public String getName() {
return name;
}
#Override
public void setName(String name) {
this.name = name;
}
}
Mixin
public abstract class Level4Mixin {
public Level4Mixin(
#JsonProperty("id") Long id,
#JsonProperty("nameTest") String name) { }
}
Unit Test
class Level4MixinTest {
private ObjectMapper mapper;
#BeforeEach
void setUp() throws Exception {
mapper = new ObjectMapper();
mapper.addMixIn(Level4.class, Level4Mixin.class);
mapper.addMixIn(Level4Impl.class, Level4Mixin.class);
}
#Test
void test_InterfaceWrite() throws JsonProcessingException {
Level4 lvl4 = new Level4Impl();
lvl4.setId(1L);
lvl4.setName("test");
String json = mapper.writeValueAsString(lvl4);
assertNotNull(json);
assertTrue(json.contains("nameTest"));
}
#Test
void test_InterfaceRead() throws JsonProcessingException {
String json = "{\"id\":1,\"nameTest\":\"test\"}";
assertDoesNotThrow(() -> {
Level4 parsed = mapper.readValue(json, Level4.class);
assertNotNull(parsed);
});
}
#Test
void test_ImplWrite() throws JsonProcessingException {
Level4Impl lvl4 = new Level4Impl();
lvl4.setId(1L);
lvl4.setName("test");
String json = mapper.writeValueAsString(lvl4);
assertNotNull(json);
assertTrue(json.contains("nameTest"));
}
#Test
void test_ImplRead() {
String json = "{\"id\":1,\"nameTest\":\"test\"}";
assertDoesNotThrow(() -> {
Level4Impl parsed = mapper.readValue(json, Level4Impl.class);
assertNotNull(parsed);
});
}
}
First of all you have to let Jackson know which subclass of your interface it should instantiate. You do it by adding #JsonTypeInfo and/or #JsonSubTypes annotations to your mix-in class. For single subclass the following would suffice:
#JsonTypeInfo(use = Id.NAME, defaultImpl = Level4Impl.class)
public abstract class Level4Mixin {
}
For multiple sub-classes it will a bit more complex and will require additional field in JSON payload that will identify concrete type. See Jackson Polymorphic Deserialization for details. Also worth mentioning that adding type info will cause type ID field to be written to JSON. JFYI.
Adding new label would be as trivial as adding a pair of getter and setter for desired property. Obviously original name field will be written to JSON too in this case. To change that you may want to place #JsonIgnore on getter in subclass or in mix-in. In latter case name will be ignored for all sub-classes.
Last note: in this case you should register your mix-in with super-type only.
Here are the changes to your classes that satisfy your tests:
Level4Impl
public class Level4Impl implements Level4 {
private Long id;
private String name;
#Override
public Long getId() {
return id;
}
#Override
public void setId(Long id) {
this.id = id;
}
#Override
public String getName() {
return name;
}
#Override
public void setName(String name) {
this.name = name;
}
public String getNameTest() {
return name;
}
public void setNameTest(String name) {
this.name = name;
}
}
Mixin
#JsonTypeInfo(use = Id.NAME, defaultImpl = Level4Impl.class)
public interface Level4Mixin {
#JsonIgnore
String getName();
}
Level4MixinTest change
#BeforeEach
void setUp() throws Exception {
mapper = new ObjectMapper();
mapper.addMixIn(Level4.class, Level4Mixin.class);
// remove
//mapper.addMixIn(Level4Impl.class, Level4Mixin.class);
}
For adding properties to an object when that object is serialized you can use #JsonAppend. For example:
#JsonAppend(attrs = {#JsonAppend.Attr(value = "nameTest")})
public class Level4Mixin {}
And the test:
#BeforeEach
void setUp() throws Exception {
mapper = new ObjectMapper()
.addMixIn(Level4Impl.class, Level4Mixin.class);
}
#Test
void test_ImplWrite() throws JsonProcessingException {
Level4Impl lvl4 = new Level4Impl();
lvl4.setId(1L);
lvl4.setName("test");
String json = mapper.writerFor(Level4Impl.class)
.withAttribute("nameTest", "myValue")
.writeValueAsString(lvl4);
assertNotNull(json);
assertTrue(json.contains("nameTest"));
assertTrue(json.contains("myValue"));
}
The same works for test_InterfaceWrite.
The tests to deserialize a json into an object are not clear:
#Test
void test_ImplRead() {
String json = "{\"id\":1,\"nameTest\":\"test\"}";
assertDoesNotThrow(() -> {
Level4Impl parsed = mapper.readValue(json, Level4Impl.class);
assertNotNull(parsed);
});
}
The class Level4Impl does not have the property nameTest so the deserialization fails. If you don't want to throw the exception you can configure the ObjectMapper to don't fail on unknown properties. For example:
#Test
void test_ImplRead() {
String json = "{\"id\":1,\"nameTest\":\"test\"}";
assertDoesNotThrow(() -> {
Level4Impl parsed = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.readValue(json, Level4Impl.class);
assertNotNull(parsed);
});
}
In case you can't make it work by default (which was my case), try to modify existing MappingJackson2HttpMessageConverter converter, e.g. do it this way:
#Configuration
public class WebMvcConfig implements WebMvcConfigurer {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.stream()
.filter(c -> c instanceof MappingJackson2HttpMessageConverter)
.forEach(c -> {
MappingJackson2HttpMessageConverter converter = (MappingJackson2HttpMessageConverter) c;
converter.getObjectMapper().addMixIn(APIResponse.class, MixInAPIResponse.class);
});
}
Where MixInAPIResponse is your configured MixIn class for target class ApiResponse.
I am trying to see if I can replace my existing Pojos with the new Record classes in Java 14. But unable to do so. Getting following error:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot
construct instance of com.a.a.Post (no Creators, like default
construct, exist): cannot deserialize from Object value (no delegate-
or property-based Creator)
I get that the error is saying the record has no constructors, but from what I see the record class takes care of it in the background and relevant getters are also set in the background (not getters exactly but id() title() and so on without the get prefix). Is it cos Spring has not adopted the latest Java 14 record yet? Please advice. Thanks.
I am doing this in Spring Boot version 2.2.6 and using Java 14.
The following works using the usual POJOs.
PostClass
public class PostClass {
private int userId;
private int id;
private String title;
private String body;
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
}
Method to call rest service which works now as I am using the above POJO.
public PostClass[] getPosts() throws URISyntaxException {
String url = "https://jsonplaceholder.typicode.com/posts";
return template.getForEntity(new URI(url), PostClass[].class).getBody();
}
But if I switch to following where I am using record instead, I am getting the above error.
The new record class.
public record Post(int userId, int id, String title, String body) {
}
Changing the method to use the record instead which fails.
public Post[] getPosts() throws URISyntaxException {
String url = "https://jsonplaceholder.typicode.com/posts";
return template.getForEntity(new URI(url), Post[].class).getBody();
}
EDIT:
Tried adding constructors as follows to the record Post and same error:
public record Post(int userId, int id, String title, String body) {
public Post {
}
}
or
public record Post(int userId, int id, String title, String body) {
public Post(int userId, int id, String title, String body) {
this.userId = userId;
this.id = id;
this.title = title;
this.body = body;
}
}
It is possible with some Jackson Annotations, which cause Jackson to use fields instead of getters. Still far less verbose than a pre-Java 14 class (without Lombok or similar solutions).
record Foo(#JsonProperty("a") int a, #JsonProperty("b") int b){
}
This probably works because according to https://openjdk.java.net/jeps/359:
Declaration annotations are permitted on record components if they are
applicable to record components, parameters, fields, or methods.
Declaration annotations that are applicable to any of these targets
are propagated to implicit declarations of any mandated members.
See also: When is the #JsonProperty property used and what is it used for?
It is also possible to make use #JsonAutoDetect
#JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
record Bar(int a, int b){
}
If configuring the Objectmapper to use field Visibility globally, this annotation on class level is not needed.
See also: How to specify jackson to only use fields - preferably globally
Example:
public class Test {
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper om = new ObjectMapper();
System.out.println(om.writeValueAsString(new Foo(1, 2))); //{"a":1,"b":2}
System.out.println(om.writeValueAsString(new Bar(3, 4))); //{"a":3,"b":4}
}
record Foo(#JsonProperty("a") int a, #JsonProperty("b") int b){
}
#JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
record Bar(int a, int b){
}
}
There is also a Github issue for that feature: https://github.com/FasterXML/jackson-future-ideas/issues/46
This is slated for jackson 2.12
https://github.com/FasterXML/jackson-future-ideas/issues/46
The compiler generates the constructor and other accessor method for a Record.
In your case,
public final class Post extends java.lang.Record {
public Post(int, int java.lang.String, java.lang.String);
public java.lang.String toString();
public final int hashCode();
public final boolean equals(java.lang.Object);
public int userId();
public int id();
public java.lang.String title();
public java.lang.String body();
}
Here you can see that there is not default constructor which is needed got Jackson. The constructor you used is a compact constructor,
public Post {
}
You can define a default/no args constructor as,
public record Post(int userId, int id, String title, String body) {
public Post() {
this(0,0, null, null);
}
}
But Jackson uses Getter and Setters to set values. So in short, you can not use Record for mapping the response.
EDIT as PSA: Jackson can properly serialize and deserialize records as of 2.12 which has been released.
Use the parameter names module for jackson, https://github.com/FasterXML/jackson-modules-java8/tree/master/parameter-names (make sure the compiler sets -parameters) or add `#JsonProperty("name") to each field in the record
add #JsonCreator to the constructor. I can't tell if the inheritance will work properly, so you might have to explicitly declare the constructor and annotate it.
If a public accessor method or (non-compact) canonical constructor is declared explicitly, then it only has the annotations which appear on it directly; nothing is propagated from the corresponding record component to these members.
From https://openjdk.java.net/jeps/384
So add
new ObjectMapper().registerModules(new ParameterNamesModule())
and try
#JsonCreator record Value(String x);
or something like
record Value(String x) {
#JsonCreator
public Value(String x) {
this.x = x;
}
}
or all the way to
record Value(#JsonProperty("x") String x) {
#JsonCreator
public Value(#JsonProperty("x") String x) {
this.x = x;
}
}
This is how I get immutable pojos with lombok and jackson to work, and I don't see why records wouldn't work under the same format. My setup is Jackson parameter names module, -parameters compiler flag for java 8 (I don't think this is required for like jdk9+), #JsonCreator on the constructor. Example of a real class working with this setup.
#Value
#AllArgsConstructor(onConstructor_ = #JsonCreator)
public final class Address {
private final String line1;
private final String line2;
private final String city;
private final String region;
private final String postalCode;
private final CountryCode country;
}
Initial Question
Is it possible to have multiple #JsonCreator methods, and for jackson to detect which one it should use depending on the method definiton?
#JsonCreator
public static StateOfComm factory(String id) {
return StateOfComm.valueOf(id);
}
#JsonCreator
public static StateOfComm factory(CustomType value) {
return StateOfComm.valueOf(value.getId());
}
Update
The JSON that fails (because id=null), is the following:
{"comment":null, "processes":[{"stateOfComm":{"id":"CA"}}]}
The following works:
{"comment":null, "processes":[{"stateOfComm":"CA"}]}
I was able to parse both JSON examples in your question by:
using jackson-modules-java8 version 2.9.1 dependency
invoking the java 8 compiler with -parameters argument
introducing all argument constructors for all classes involved
avoiding #JsonProperty on static creation methods and constructors
defining a class:
class CustomType {
private final String id;
}
My understanding is that Jackson couldn't discern between multiple creators in older version. E.g. see answer here and github issue here. It seems that the option to have parameter names in compiled code in java 8 helps in this case.
I solved this problem by getting rid of the #JsonCreator annotations, and using a custom StdDeserializer that did the trick.
Here is an example:
#JsonIgnoreProperties(
ignoreUnknown = true
)
#JsonDeserialize(
using = IdTextEntry.Deserializer.class
)
#Data
public class IdTextEntry implements IdAndText {
String id;
String text;
public IdTextEntry(Enum<?> val) {
if (val != null) {
this.id = val.name();
this.text = val.toString();
}
}
public static class Deserializer extends StdDeserializer<IdTextEntry> {
public Deserializer() {
this((Class)null);
}
Deserializer(Class<?> vc) {
super(vc);
}
public IdTextEntry deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
JsonNode node = (JsonNode)jp.getCodec().readTree(jp);
String id;
if (node.has("id") && node.has("text")) {
id = node.get("id").asText();
String text = node.get("text").asText();
return new IdTextEntry(id, text);
} else if (node.has("id")) {
id = node.get("id").asText();
return new IdTextEntry(id, id);
} else {
id = node.asText();
return new IdTextEntry(id, id);
}
}
}
}
You can have multiple #JsonCreator methods but it requires to use #JsonProperty for specifying which property you are initializing.
#JsonCreator
public static StateOfComm factory(#JsonProperty("id") String id) {
return StateOfComm.valueOf(id);
}
#JsonCreator
public static StateOfComm factory(#JsonProperty("value") CustomType value) {
return StateOfComm.valueOf(value.getId());
}
I'm using JAVA 1.6 and Jackson 1.9.9 I've got an enum
public enum Event {
FORGOT_PASSWORD("forgot password");
private final String value;
private Event(final String description) {
this.value = description;
}
#JsonValue
final String value() {
return this.value;
}
}
I've added a #JsonValue, this seems to do the job it serializes the object into:
{"event":"forgot password"}
but when I try to deserialize I get a
Caused by: org.codehaus.jackson.map.JsonMappingException: Can not construct instance of com.globalrelay.gas.appsjson.authportal.Event from String value 'forgot password': value not one of declared Enum instance names
What am I missing here?
The serializer / deserializer solution pointed out by #xbakesx is an excellent one if you wish to completely decouple your enum class from its JSON representation.
Alternatively, if you prefer a self-contained solution, an implementation based on #JsonCreator and #JsonValue annotations would be more convenient.
So leveraging on the example by #Stanley the following is a complete self-contained solution (Java 6, Jackson 1.9):
public enum DeviceScheduleFormat {
Weekday,
EvenOdd,
Interval;
private static Map<String, DeviceScheduleFormat> namesMap = new HashMap<String, DeviceScheduleFormat>(3);
static {
namesMap.put("weekday", Weekday);
namesMap.put("even-odd", EvenOdd);
namesMap.put("interval", Interval);
}
#JsonCreator
public static DeviceScheduleFormat forValue(String value) {
return namesMap.get(StringUtils.lowerCase(value));
}
#JsonValue
public String toValue() {
for (Entry<String, DeviceScheduleFormat> entry : namesMap.entrySet()) {
if (entry.getValue() == this)
return entry.getKey();
}
return null; // or fail
}
}
Note that as of this commit in June 2015 (Jackson 2.6.2 and above) you can now simply write:
public enum Event {
#JsonProperty("forgot password")
FORGOT_PASSWORD;
}
The behavior is documented here: https://fasterxml.github.io/jackson-annotations/javadoc/2.11/com/fasterxml/jackson/annotation/JsonProperty.html
Starting with Jackson 2.6 this annotation may also be used to change serialization of Enum like so:
public enum MyEnum {
#JsonProperty("theFirstValue") THE_FIRST_VALUE,
#JsonProperty("another_value") ANOTHER_VALUE;
}
as an alternative to using JsonValue annotation.
You should create a static factory method which takes single argument and annotate it with #JsonCreator (available since Jackson 1.2)
#JsonCreator
public static Event forValue(String value) { ... }
Read more about JsonCreator annotation here.
Actual Answer:
The default deserializer for enums uses .name() to deserialize, so it's not using the #JsonValue. So as #OldCurmudgeon pointed out, you'd need to pass in {"event": "FORGOT_PASSWORD"} to match the .name() value.
An other option (assuming you want the write and read json values to be the same)...
More Info:
There is (yet) another way to manage the serialization and deserialization process with Jackson. You can specify these annotations to use your own custom serializer and deserializer:
#JsonSerialize(using = MySerializer.class)
#JsonDeserialize(using = MyDeserializer.class)
public final class MyClass {
...
}
Then you have to write MySerializer and MyDeserializer which look like this:
MySerializer
public final class MySerializer extends JsonSerializer<MyClass>
{
#Override
public void serialize(final MyClass yourClassHere, final JsonGenerator gen, final SerializerProvider serializer) throws IOException, JsonProcessingException
{
// here you'd write data to the stream with gen.write...() methods
}
}
MyDeserializer
public final class MyDeserializer extends org.codehaus.jackson.map.JsonDeserializer<MyClass>
{
#Override
public MyClass deserialize(final JsonParser parser, final DeserializationContext context) throws IOException, JsonProcessingException
{
// then you'd do something like parser.getInt() or whatever to pull data off the parser
return null;
}
}
Last little bit, particularly for doing this to an enum JsonEnum that serializes with the method getYourValue(), your serializer and deserializer might look like this:
public void serialize(final JsonEnum enumValue, final JsonGenerator gen, final SerializerProvider serializer) throws IOException, JsonProcessingException
{
gen.writeString(enumValue.getYourValue());
}
public JsonEnum deserialize(final JsonParser parser, final DeserializationContext context) throws IOException, JsonProcessingException
{
final String jsonValue = parser.getText();
for (final JsonEnum enumValue : JsonEnum.values())
{
if (enumValue.getYourValue().equals(jsonValue))
{
return enumValue;
}
}
return null;
}
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. So you only need to write toString() method in your enum.
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
Try this.
public enum Event {
FORGOT_PASSWORD("forgot password");
private final String value;
private Event(final String description) {
this.value = description;
}
private Event() {
this.value = this.name();
}
#JsonValue
final String value() {
return this.value;
}
}
I like the accepted answer. However, I would improve it a little (considering that there is now Java higher than version 6 available).
Example:
public enum Operation {
EQUAL("eq"),
NOT_EQUAL("ne"),
LESS_THAN("lt"),
GREATER_THAN("gt");
private final String value;
Operation(String value) {
this.value = value;
}
#JsonValue
public String getValue() {
return value;
}
#JsonCreator
public static Operation forValue(String value) {
return Arrays.stream(Operation.values())
.filter(op -> op.getValue().equals(value))
.findFirst()
.orElseThrow(); // depending on requirements: can be .orElse(null);
}
}
You can customize the deserialization for any attribute.
Declare your deserialize class using the annotationJsonDeserialize (import com.fasterxml.jackson.databind.annotation.JsonDeserialize) for the attribute that will be processed. If this is an Enum:
#JsonDeserialize(using = MyEnumDeserialize.class)
private MyEnum myEnum;
This way your class will be used to deserialize the attribute. This is a full example:
public class MyEnumDeserialize extends JsonDeserializer<MyEnum> {
#Override
public MyEnum deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
JsonNode node = jsonParser.getCodec().readTree(jsonParser);
MyEnum type = null;
try{
if(node.get("attr") != null){
type = MyEnum.get(Long.parseLong(node.get("attr").asText()));
if (type != null) {
return type;
}
}
}catch(Exception e){
type = null;
}
return type;
}
}
Here is another example that uses string values instead of a map.
public enum Operator {
EQUAL(new String[]{"=","==","==="}),
NOT_EQUAL(new String[]{"!=","<>"}),
LESS_THAN(new String[]{"<"}),
LESS_THAN_EQUAL(new String[]{"<="}),
GREATER_THAN(new String[]{">"}),
GREATER_THAN_EQUAL(new String[]{">="}),
EXISTS(new String[]{"not null", "exists"}),
NOT_EXISTS(new String[]{"is null", "not exists"}),
MATCH(new String[]{"match"});
private String[] value;
Operator(String[] value) {
this.value = value;
}
#JsonValue
public String toStringOperator(){
return value[0];
}
#JsonCreator
public static Operator fromStringOperator(String stringOperator) {
if(stringOperator != null) {
for(Operator operator : Operator.values()) {
for(String operatorString : operator.value) {
if (stringOperator.equalsIgnoreCase(operatorString)) {
return operator;
}
}
}
}
return null;
}
}
There are various approaches that you can take to accomplish deserialization of a JSON object to an enum. My favorite style is to make an inner class:
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.hibernate.validator.constraints.NotEmpty;
import java.util.Arrays;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import static com.fasterxml.jackson.annotation.JsonFormat.Shape.OBJECT;
#JsonFormat(shape = OBJECT)
public enum FinancialAccountSubAccountType {
MAIN("Main"),
MAIN_DISCOUNT("Main Discount");
private final static Map<String, FinancialAccountSubAccountType> ENUM_NAME_MAP;
static {
ENUM_NAME_MAP = Arrays.stream(FinancialAccountSubAccountType.values())
.collect(Collectors.toMap(
Enum::name,
Function.identity()));
}
private final String displayName;
FinancialAccountSubAccountType(String displayName) {
this.displayName = displayName;
}
#JsonCreator
public static FinancialAccountSubAccountType fromJson(Request request) {
return ENUM_NAME_MAP.get(request.getCode());
}
#JsonProperty("name")
public String getDisplayName() {
return displayName;
}
private static class Request {
#NotEmpty(message = "Financial account sub-account type code is required")
private final String code;
private final String displayName;
#JsonCreator
private Request(#JsonProperty("code") String code,
#JsonProperty("name") String displayName) {
this.code = code;
this.displayName = displayName;
}
public String getCode() {
return code;
}
#JsonProperty("name")
public String getDisplayName() {
return displayName;
}
}
}
In the context of an enum, using #JsonValue now (since 2.0) works for serialization and deserialization.
According to the jackson-annotations javadoc for #JsonValue:
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 having the Event enum annotated just as above works (for both serialization and deserialization) with jackson 2.0+.
Besides using #JsonSerialize #JsonDeserialize, you can also use SerializationFeature and DeserializationFeature (jackson binding) in the object mapper.
Such as DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE, which give default enum type if the one provided is not defined in the enum class.
In my case, this is what resolved:
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
#JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum PeriodEnum {
DAILY(1),
WEEKLY(2),
;
private final int id;
PeriodEnum(int id) {
this.id = id;
}
public int getId() {
return id;
}
public String getName() {
return this.name();
}
#JsonCreator
public static PeriodEnum fromJson(#JsonProperty("name") String name) {
return valueOf(name);
}
}
Serializes and deserializes the following json:
{
"id": 2,
"name": "WEEKLY"
}
I hope it helps!
#JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum LoginOptionType {
PHONE(1, "Phone"), MAIL(2, "mail"), PERSONAL_EMAIL(3, "Personal email");
private static List<LoginOptionType> all;
static {
all = new ArrayList<LoginOptionType>() {
{
add(LoginOptionType.PHONE);
add(LoginOptionType.MAIL);
add(LoginOptionType.PERSONAL_EMAIL);
}
};
}
private final Integer viewValue;
private final String name;
LoginOptionType(Integer viewValue, String name) {
this.viewValue = viewValue;
this.name = name;
}
public Integer getViewValue() {
return viewValue;
}
public String getName() {
return name;
}
public static List<LoginOptionType> getAll() {
return all;
}
}
Response
[
{
"viewValue": 1,
"name": "Phone"
},
{
"viewValue": 2,
"name": "mail"
},
{
"viewValue": 3,
"name": "Personal email"
}
]
Here, 'value' acts as a deserialiser and 'namespace' acts as a serialiser. Hence, you can pass in value "Student Absent" to API while saving, and in DB it will be saved as "STUDENT_ABSENT". On the other hand, while retrieving data in your class, your API will return "Student Absent"
import com.fasterxml.jackson.annotation.JsonProperty;
public enum AttendanceEnums {
STUDENT_PRESENT,
#JsonProperty(value = "Student Absent", namespace = "Student Absent")
STUDENT_ABSENT;
}
I had been looking for a solution to enum serialization and I finally made a solution.
https://github.com/sirgilligan/EnumerationSerialization
https://digerati-illuminatus.blogspot.com/2022/10/java-enum-generic-serializer-and.html
It uses a new annotation and two new classes, EnumerationSerializer and EnumerationDeserializer. You can subclass the EnumerationDeserializer and make a class that sets the enum Class (typical approach) or you can annotate the enum and you don't have to have a subclass of EnumerationDeserializer.
#JsonSerialize(using = EnumerationSerializer.class)
#JsonDeserialize(using = EnumerationDeserializer.class)
#EnumJson(serializeProjection = Projection.NAME, deserializationClass = RGB.class)
enum RGB {
RED,
GREEN,
BLUE
}
Notice how the implementation of ContextualDeserializer pulls the class from the annotation.
https://github.com/sirgilligan/EnumerationSerialization/blob/main/src/main/java/org/example/EnumerationDeserializer.java
There is a lot of good code in this that might give insights.
For your specific question you could do this:
#JsonSerialize(using = EnumerationSerializer.class)
#JsonDeserialize(using = EnumerationDeserializer.class)
#EnumJson(serializeProjection = Projection.NAME, deserializationClass = Event.class)
public enum Event {
FORGOT_PASSWORD("forgot password");
//This annotation is optional because the code looks for value or alias.
#EnumJson(serializeProjection = Projection.VALUE)
private final String value;
private Event(final String description) {
this.value = description;
}
}
Or you could do this:
#JsonSerialize(using = EnumerationSerializer.class)
#JsonDeserialize(using = EnumerationDeserializer.class)
#EnumJson(serializeProjection = Projection.NAME, deserializationClass = Event.class)
public enum Event {
FORGOT_PASSWORD("forgot password");
private final String value;
private Event(final String description) {
this.value = description;
}
}
That's all you have to do.
Then if you have a class that "has a" event you can annotate each occurance to serialize the way you want.
class EventHolder {
#EnumJson(serializeProjection = Projection.NAME)
Event someEvent;
#EnumJson(serializeProjection = Projection.ORDINAL)
Event someOtherEvent;
#EnumJson(serializeProjection = Projection.VALUE)
Event yetAnotherEvent;
}
The simplest way I found is using #JsonFormat.Shape.OBJECT annotation for the enum.
#JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum MyEnum{
....
}
I did it like this :
// Your JSON
{"event":"forgot password"}
// Your class to map
public class LoggingDto {
#JsonProperty(value = "event")
private FooEnum logType;
}
//Your enum
public enum FooEnum {
DATA_LOG ("Dummy 1"),
DATA2_LOG ("Dummy 2"),
DATA3_LOG ("forgot password"),
DATA4_LOG ("Dummy 4"),
DATA5_LOG ("Dummy 5"),
UNKNOWN ("");
private String fullName;
FooEnum(String fullName) {
this.fullName = fullName;
}
public String getFullName() {
return fullName;
}
#JsonCreator
public static FooEnum getLogTypeFromFullName(String fullName) {
for (FooEnum logType : FooEnum.values()) {
if (logType.fullName.equals(fullName)) {
return logType;
}
}
return UNKNOWN;
}
}
So the value of the property "logType" for class LoggingDto will be DATA3_LOG
This post is old, but if it can help someone, use JsonFormat.Shape.STRING
#JsonFormat(shape = JsonFormat.Shape.STRING)
public enum SomeEnum{
#JsonProperty("SOME_PROPERTY")
someProperty,
...
}
Code results is like this
{"someenum":"SOME_PROPERTY"}