Enum, How to rename during (de)serialization? - java

Hi is it possible to change name of ENUM in Java?
For example, I have enum -
public enum CloudType {
AZURE, OPENSTACK
}
And a class -
public class Env{
private CloudType cloudType;
}
And during JACKSON parsing, if I give -
{
"cloudType":"AZURE"
}
Or
{
"cloudType":"azure"
}
It will give me an Env object with cloudType=AZURE ?
One thread is there (Jackson databind enum case insensitive), but really we need to do this much?
Or
#XmlEnumValue("azure")
AZURE("azure"),
will be enough?

You can decouple the Java names of the enum instances from their JSON representations.
You should add a #JsonValue-annotated method to your enum CloudType to tell Jackson which value to use when reading/writing JSON.
For that you also need a constructor to initialize this value.
Like this:
public enum CloudType {
AZURE("azure"),
OPENSTACK("openstack");
private final String value;
private CloudType(String value) {
this.value = value;
}
#JsonValue
public String getValue() {
return value;
}
}
Then Jackson will serialize CloudType.AZURE to "azure",
and deserialize "azure" to CloudType.AZURE.

Related

keep getting unrecognized field not marked as ignorable error while paring a json string [duplicate]

This bean 'State' :
public class State {
private boolean isSet;
#JsonProperty("isSet")
public boolean isSet() {
return isSet;
}
#JsonProperty("isSet")
public void setSet(boolean isSet) {
this.isSet = isSet;
}
}
is sent over the wire using the ajax ' success' callback :
success : function(response) {
if(response.State.isSet){
alert('success called successfully)
}
Is the annotation #JsonProperty required here ? What is the advantage of using it ?
I think I can remove this annotation without causing any side effects.
Reading about this annotion on https://github.com/FasterXML/jackson-annotations/wiki/Jackson-Annotations I don't know when this is required to be used ?
Here's a good example. I use it to rename the variable because the JSON is coming from a .Net environment where properties start with an upper-case letter.
public class Parameter {
#JsonProperty("Name")
public String name;
#JsonProperty("Value")
public String value;
}
This correctly parses to/from the JSON:
"Parameter":{
"Name":"Parameter-Name",
"Value":"Parameter-Value"
}
I think OldCurmudgeon and StaxMan are both correct but here is one sentence answer with simple example for you.
#JsonProperty(name), tells Jackson ObjectMapper to map the JSON property name to the annotated Java field's name.
//example of json that is submitted
"Car":{
"Type":"Ferrari",
}
//where it gets mapped
public static class Car {
#JsonProperty("Type")
public String type;
}
well for what its worth now... JsonProperty is ALSO used to specify getter and setter methods for the variable apart from usual serialization and deserialization. For example suppose you have a payload like this:
{
"check": true
}
and a Deserializer class:
public class Check {
#JsonProperty("check") // It is needed else Jackson will look got getCheck method and will fail
private Boolean check;
public Boolean isCheck() {
return check;
}
}
Then in this case JsonProperty annotation is neeeded. However if you also have a method in the class
public class Check {
//#JsonProperty("check") Not needed anymore
private Boolean check;
public Boolean getCheck() {
return check;
}
}
Have a look at this documentation too:
http://fasterxml.github.io/jackson-annotations/javadoc/2.13/com/fasterxml/jackson/annotation/JsonProperty.html
Without annotations, inferred property name (to match from JSON) would be "set", and not -- as seems to be the intent -- "isSet". This is because as per Java Beans specification, methods of form "isXxx" and "setXxx" are taken to mean that there is logical property "xxx" to manage.
Adding the JsonProperty also ensures safety in case someone decides they want to change one of the property names not realizing the class in question will be serialized to a Json object. If they change the property name the JsonProperty ensures it will be used in the Json object, and not the property name.
As you know, this is all about serialize and desalinize an object. Suppose there is an object:
public class Parameter {
public String _name;
public String _value;
}
The serialization of this object is:
{
"_name": "...",
"_value": "..."
}
The name of variable is directly used to serialize data. If you are about to remove system api from system implementation, in some cases, you have to rename variable in serialization/deserialization. #JsonProperty is a meta data to tell serializer how to serial object. It is used to:
variable name
access (READ, WRITE)
default value
required/optional
from example:
public class Parameter {
#JsonProperty(
value="Name",
required=true,
defaultValue="No name",
access= Access.READ_WRITE)
public String _name;
#JsonProperty(
value="Value",
required=true,
defaultValue="Empty",
access= Access.READ_WRITE)
public String _value;
}
In addition to all the answers above, don't forget the part of the documentation that says
Marker annotation that can be used to define a non-static method as a
"setter" or "getter" for a logical property (depending on its
signature), or non-static object field to be used (serialized,
deserialized) as a logical property.
If you have a non-static method in your class that is not a conventional getter or setter then you can make it act like a getter and setter by using the annotation on it. See the example below
public class Testing {
private Integer id;
private String username;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getIdAndUsername() {
return id + "." + username;
}
public String concatenateIdAndUsername() {
return id + "." + username;
}
}
When the above object is serialized, then response will contain
username from getUsername()
id from getId()
idAndUsername from getIdAndUsername*
Since the method getIdAndUsername starts with get then it's treated as normal getter hence, why you could annotate such with #JsonIgnore.
If you have noticed the concatenateIdAndUsername is not returned and that's because it name does not start with get and if you wish the result of that method to be included in the response then you can use #JsonProperty("...") and it would be treated as normal getter/setter as mentioned in the above highlighted documentation.
As addition to other answers, #JsonProperty annotation is really important if you use the #JsonCreator annotation in classes which do not have a no-arg constructor.
public class ClassToSerialize {
public enum MyEnum {
FIRST,SECOND,THIRD
}
public String stringValue = "ABCD";
public MyEnum myEnum;
#JsonCreator
public ClassToSerialize(MyEnum myEnum) {
this.myEnum = myEnum;
}
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
ClassToSerialize classToSerialize = new ClassToSerialize(MyEnum.FIRST);
String jsonString = mapper.writeValueAsString(classToSerialize);
System.out.println(jsonString);
ClassToSerialize deserialized = mapper.readValue(jsonString, ClassToSerialize.class);
System.out.println("StringValue: " + deserialized.stringValue);
System.out.println("MyEnum: " + deserialized.myEnum);
}
}
In this example the only constructor is marked as #JsonCreator, therefore Jackson will use this constructor to create the instance. But the output is like:
Serialized: {"stringValue":"ABCD","myEnum":"FIRST"}
Exception in
thread "main"
com.fasterxml.jackson.databind.exc.InvalidFormatException: Can not
construct instance of
ClassToSerialize$MyEnum
from String value 'stringValue': value not one of declared Enum
instance names: [FIRST, SECOND, THIRD]
But after the addition of the #JsonProperty annotation in the constructor:
#JsonCreator
public ClassToSerialize(#JsonProperty("myEnum") MyEnum myEnum) {
this.myEnum = myEnum;
}
The deserialization is successful:
Serialized: {"myEnum":"FIRST","stringValue":"ABCD"}
StringValue: ABCD
MyEnum: FIRST
From JsonProperty javadoc,
Defines name of the logical property, i.e. JSON object field name to use for the property. If value is empty String (which is the default), will try to use name of the field that is annotated.

How do you modify default enum (de)serialization for non-annotated enums but retain standard behavior (#JsonProperty/#JsonValue/...) in Jackson?

Currently jackson (uncustomized) serializes enums like this:
If there is #JsonProperty or #JsonValue, they are used to derive serialized name
Otherwise, standard serialization is carried out: index or toString() might be used (depending on the mapper settings), or .name() is called by default
For deserialization, it's like this:
If there is #JsonProperty, #JsonValue or #JsonCreator, they influence deserialization
Otherwise, standard deserialization is used which mirrors serialization.
I'd like to keep items 1 in both cases (i.e. still support all 3 annotations, or more if I miss something), but modify behavior for the 'default case': for example, always serialize enums as .name().toLowercase() if no annotations are present.
I could use addSerializer(), addDeserializer() or (de)serializer modification mechanisms, but none of these allow easy and elegant solution. All I could invent is either copy/paste tons of code from jackson (which is ugly and fragile), or, using modification mechanism, introspect the enum class emulating jackson's involved logic to identify cases when the 'default' logic would apply and then use my 'to-lower-case' strategy.
Is there a better way to modify the 'default' serialization strategy while still leaving all the 'non-default' cases?
You can insert a new AnnotationIntrospector whose findEnumValues method changes the enum name. The EnumRenamingModule from the therapi-json-rpc project uses this technique. (Disclaimer: this is my pet project.) Here's the code, copied from the GitHub repo:
package com.github.therapi.jackson.enums;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.introspect.NopAnnotationIntrospector;
import com.fasterxml.jackson.databind.module.SimpleModule;
/**
* Customizes the way Jackson serializes enums.
*/
public abstract class EnumRenamingModule extends SimpleModule {
private boolean overrideExistingNames;
public EnumRenamingModule() {
super("therapi-enum-renaming");
}
/**
* Configures the module to clobber any enum names set by
* a previous annotation introspector.
*/
public EnumRenamingModule overrideExistingNames() {
this.overrideExistingNames = true;
return this;
}
#Override
public void setupModule(Module.SetupContext context) {
super.setupModule(context);
context.insertAnnotationIntrospector(new EnumNamingAnnotationIntrospector());
}
private class EnumNamingAnnotationIntrospector extends NopAnnotationIntrospector {
public String[] findEnumValues(Class<?> enumType, Enum<?>[] enumValues, String[] names) {
for (int i = 0; i < enumValues.length; i++) {
if (names[i] == null || overrideExistingNames) {
names[i] = EnumRenamingModule.this.getName(enumValues[i]);
}
}
return names;
}
}
/**
* #param value the enum value to inspect
* #return the JSON name for the enum value, or {#code null} to delegate to the next introspector
*/
protected abstract String getName(Enum<?> value);
}
Create a subclass to do the transformation you want:
public class LowerCaseEnumModule extends EnumRenamingModule {
#Override protected String getName(Enum<?> value) {
return value.name().toLowerCase(Locale.ROOT);
}
}
And register it with your ObjectMapper:
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new LowerCaseEnumModule());
Annotation #JsonCreator is used to deserialize (it's a static factory).
Annotation #JsonValue is used to serialize.
The given example does work for me :
class MyException extends RuntimeException {}
enum MyEnum {
TEST;
private final String value;
private MyEnum() {
this.value = this.name().toLowerCase();
}
#JsonValue
public final String value() {
return this.value;
}
#JsonCreator
public static MyEnum forValue(String value) {
return Arrays.stream(MyEnum.values()).filter(myEnum -> myEnum.value.equals(value)).findFirst().orElseThrow(MyException::new);
}
}

Nested enum with definition as String

I'm facing a problem with nested enum. So, I have an nested enum which has default values as you can see. Before there were just an enums like CDs and CMs. Now I set something like definition to every of it as you can see "Cool Ds" and etc. Currently I'm facing a problem that I can't read enums String, which is in () and I don't know how to fix it anymore. Does anyone have an idea?
package com.test.beans;
import java.io.Serializable;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
#JsonIgnoreProperties(ignoreUnknown = true)
public class RecordBean implements Serializable {
public enum Types {
CDs("Cool Ds"), CMs("Cool Ms");
private final String s;
private Types(String s) {
this.s=s;
}
public String getTypes(){
return s;
}
public static Types fromNumeric(int index) {
switch (index) {
default:
return null;
case 0:
return Types.CDs;
case 1:
return Types.CMs;
}
}
}
private Types type;
private float value;
public RecordBean() {
// default constructor for default instantiate
}
public RecordBean(Types type, float value) {
this.type = type;
this.value = value;
}
public Types getType() {
return type;
}
public void setType(Types type) {
this.type = type;
}
public float getValue() {
return value;
}
public void setValue(float value) {
this.value = value;
}
}
UPDATE
Error what I'm getting:
17/04/10 12:44:53 ERROR App$: Can not construct instance of com.test.beans.RecordBean$Types from String value 'Cool Ds': value not one of declared Enum instance names: CDs, CMs ]
So as you can see he is not comparing my String 'Cool Ds' with enums String in brackets but with pure enum or CDs and CMs
My USE-CASE is like this. I'm working spark streaming where data are incoming to my RecordBean class and where are comparing to my enum type. Because in database are changed types from CMs to Cool Ms I needed to change the same in my app by adding definition to Enum. Afterwards I couldn't accomplish part where app will process enum like CMs and read its definition or Cool Ms
Be sure, you can read with : CDs.getTypes() and CMs.getTypes()
Guessing: maybe the framework is calling toString() on your enum constants, thus you might want to add:
public enum Types {
...
#Overrride
public String toString() { return s; }
In other words: make sure that your existing enum constants are really using that "new" changed string name.
But beyond that: consider stepping back and changing your overall design. Your problem isn't uncommon - enums are nice from a programming point of view (as in: using enum constants leads to cleaner code); but: they are not suited for persistence. Versioning with enums is hell.

When is the #JsonProperty property used and what is it used for?

This bean 'State' :
public class State {
private boolean isSet;
#JsonProperty("isSet")
public boolean isSet() {
return isSet;
}
#JsonProperty("isSet")
public void setSet(boolean isSet) {
this.isSet = isSet;
}
}
is sent over the wire using the ajax ' success' callback :
success : function(response) {
if(response.State.isSet){
alert('success called successfully)
}
Is the annotation #JsonProperty required here ? What is the advantage of using it ?
I think I can remove this annotation without causing any side effects.
Reading about this annotion on https://github.com/FasterXML/jackson-annotations/wiki/Jackson-Annotations I don't know when this is required to be used ?
Here's a good example. I use it to rename the variable because the JSON is coming from a .Net environment where properties start with an upper-case letter.
public class Parameter {
#JsonProperty("Name")
public String name;
#JsonProperty("Value")
public String value;
}
This correctly parses to/from the JSON:
"Parameter":{
"Name":"Parameter-Name",
"Value":"Parameter-Value"
}
I think OldCurmudgeon and StaxMan are both correct but here is one sentence answer with simple example for you.
#JsonProperty(name), tells Jackson ObjectMapper to map the JSON property name to the annotated Java field's name.
//example of json that is submitted
"Car":{
"Type":"Ferrari",
}
//where it gets mapped
public static class Car {
#JsonProperty("Type")
public String type;
}
well for what its worth now... JsonProperty is ALSO used to specify getter and setter methods for the variable apart from usual serialization and deserialization. For example suppose you have a payload like this:
{
"check": true
}
and a Deserializer class:
public class Check {
#JsonProperty("check") // It is needed else Jackson will look got getCheck method and will fail
private Boolean check;
public Boolean isCheck() {
return check;
}
}
Then in this case JsonProperty annotation is neeeded. However if you also have a method in the class
public class Check {
//#JsonProperty("check") Not needed anymore
private Boolean check;
public Boolean getCheck() {
return check;
}
}
Have a look at this documentation too:
http://fasterxml.github.io/jackson-annotations/javadoc/2.13/com/fasterxml/jackson/annotation/JsonProperty.html
Without annotations, inferred property name (to match from JSON) would be "set", and not -- as seems to be the intent -- "isSet". This is because as per Java Beans specification, methods of form "isXxx" and "setXxx" are taken to mean that there is logical property "xxx" to manage.
Adding the JsonProperty also ensures safety in case someone decides they want to change one of the property names not realizing the class in question will be serialized to a Json object. If they change the property name the JsonProperty ensures it will be used in the Json object, and not the property name.
As you know, this is all about serialize and desalinize an object. Suppose there is an object:
public class Parameter {
public String _name;
public String _value;
}
The serialization of this object is:
{
"_name": "...",
"_value": "..."
}
The name of variable is directly used to serialize data. If you are about to remove system api from system implementation, in some cases, you have to rename variable in serialization/deserialization. #JsonProperty is a meta data to tell serializer how to serial object. It is used to:
variable name
access (READ, WRITE)
default value
required/optional
from example:
public class Parameter {
#JsonProperty(
value="Name",
required=true,
defaultValue="No name",
access= Access.READ_WRITE)
public String _name;
#JsonProperty(
value="Value",
required=true,
defaultValue="Empty",
access= Access.READ_WRITE)
public String _value;
}
In addition to all the answers above, don't forget the part of the documentation that says
Marker annotation that can be used to define a non-static method as a
"setter" or "getter" for a logical property (depending on its
signature), or non-static object field to be used (serialized,
deserialized) as a logical property.
If you have a non-static method in your class that is not a conventional getter or setter then you can make it act like a getter and setter by using the annotation on it. See the example below
public class Testing {
private Integer id;
private String username;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getIdAndUsername() {
return id + "." + username;
}
public String concatenateIdAndUsername() {
return id + "." + username;
}
}
When the above object is serialized, then response will contain
username from getUsername()
id from getId()
idAndUsername from getIdAndUsername*
Since the method getIdAndUsername starts with get then it's treated as normal getter hence, why you could annotate such with #JsonIgnore.
If you have noticed the concatenateIdAndUsername is not returned and that's because it name does not start with get and if you wish the result of that method to be included in the response then you can use #JsonProperty("...") and it would be treated as normal getter/setter as mentioned in the above highlighted documentation.
As addition to other answers, #JsonProperty annotation is really important if you use the #JsonCreator annotation in classes which do not have a no-arg constructor.
public class ClassToSerialize {
public enum MyEnum {
FIRST,SECOND,THIRD
}
public String stringValue = "ABCD";
public MyEnum myEnum;
#JsonCreator
public ClassToSerialize(MyEnum myEnum) {
this.myEnum = myEnum;
}
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
ClassToSerialize classToSerialize = new ClassToSerialize(MyEnum.FIRST);
String jsonString = mapper.writeValueAsString(classToSerialize);
System.out.println(jsonString);
ClassToSerialize deserialized = mapper.readValue(jsonString, ClassToSerialize.class);
System.out.println("StringValue: " + deserialized.stringValue);
System.out.println("MyEnum: " + deserialized.myEnum);
}
}
In this example the only constructor is marked as #JsonCreator, therefore Jackson will use this constructor to create the instance. But the output is like:
Serialized: {"stringValue":"ABCD","myEnum":"FIRST"}
Exception in
thread "main"
com.fasterxml.jackson.databind.exc.InvalidFormatException: Can not
construct instance of
ClassToSerialize$MyEnum
from String value 'stringValue': value not one of declared Enum
instance names: [FIRST, SECOND, THIRD]
But after the addition of the #JsonProperty annotation in the constructor:
#JsonCreator
public ClassToSerialize(#JsonProperty("myEnum") MyEnum myEnum) {
this.myEnum = myEnum;
}
The deserialization is successful:
Serialized: {"myEnum":"FIRST","stringValue":"ABCD"}
StringValue: ABCD
MyEnum: FIRST
From JsonProperty javadoc,
Defines name of the logical property, i.e. JSON object field name to use for the property. If value is empty String (which is the default), will try to use name of the field that is annotated.

Json Jackson Mapper Generic Map Type issue

This is my firs titme dealing with Type Maps and everytime i try to map the node to my Actual Type Object which has a custom property key as FooType with a Set<Integer> values. Here is how my Object looks like
public class Foo {
private String some;
Map<FooTypes,Set<Integer>> foos;
public Map<FooTypes, Set<Integer>> getFoos() {
return foos;
}
public void setFoos(Map<FooTypes, Set<Integer>> map) {
this.foos = map;
}
public String getSome() {
return some;
}
public void setSome(String some) {
this.some = some;
}
}
public class FooTypes {
private String name;
private String id;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
Now everytime i try to use the mapper to read the value :-
List <Foo> response = mapper.readValue("/home/foo/foo.json",List.class);
I get an error stating that :-
Can not find a (Map) Key deserializer for type [simple type, class cruft.FooTypes]
Can someone tell me on how can i fix this problem ? Thank you.
Json Output:-
{"foos":{"FooTypes [id=1, name=Test Foo]":[1,2,3]},"some":hello},{"foos":{"FooTypes [id=2, name=Another foo]":[5,6,7]}}
It's a bit hard to help you since we don't have the Json structure you want to deserialize, but the problem here is Jackson has no idea how to deserialize your class when it is used as a map key. All the information Jackson as is a simple String, and your class provide no way of creating it with only a string.
There's 3 way to achieve this:
Add a single string argument constructor to your FooType class
Add a single string argument factory method (static) and annotate it with #JsonCreator
Use a custom KeyDeserializer and annotate the foos field with #JsonDeserialize(keyUsing=YourDeserializer.class)
IMHO the static factory method is the cleaner way.
Since you have non-primitive type as a map key (FooTypes) you'll need to write your own custom deserializer as described in Jackson wiki, because Jackson can't simply convert string value "FooTypes [id=1, name=Test Foo]" (which seems to be a result of FooTypes.toString()) into FooTypes instance.
On my opinion, serializing map with non-string keys is not really a good practice. JSON notation doesn't support anything but strings for map keys (as specified here). A better approach, i think, would be to reorganize your data structure before serialization and after deserialization.

Categories

Resources