Java Spring custom Enum to String conversion in JSON Serialization - java

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;
}
}

Related

Spring Boot parse JSON data to Java Class with different field names

I am new to Spring Boot and I am trying to figure out how to parse json data. I see a lot of tutorials on how to map json string object to an annotated Java class and using and object mapper, like this:
json:
{
"UUID": "xyz",
"name": "some name"
}
public class MyClass{
#JsonProperty
private UUID id;
#JsonProperty
private String name;
#JsonAnyGetter
public UUID getId() {
return this.id;
}
#JsonAnySetter
public void setId(UUID id) {
this.id = id;
}
#JsonAnyGetter
public String getName() {
return this.name;
}
#JsonAnySetter
public void setName(String name) {
this.name = name;
}
}
ObjectMapper objectMapper = new ObjectMapper();
MyClass customer = objectMapper.readValue(jsonString, MyClass.class);
The problem is that the system I am getting the json string from does not match the class naming conventions we use (and I cannot change either one). So, instead of having the example json string above, it might look like this:
{
"randomdstring-fieldId": "xyz",
"anotherrandomstring-name": "some name"
}
This use case only has two fields, but my use case has a larger payload. Is there a way to either map the field names from the json object to the field names in the Java class or is there a way to just parse the json string as a key value pair (so that I can just manually add the fields to my Java object)?
In Jackson with #JsonProperty you can customize the field name with it's annotation parameter value
Therefore, you just have to annotate the entity fields with the #JsonProperty annotation and provide a custom JSON property name, like this:
public class MyClass{
#JsonProperty("original_field_name_in_json")
private UUID id;
...
The #JsonProperty will do it for you:
#JsonProperty("name_in_json")
private Long value;

Is there a way to upper case a JSON value before binding it - using SpringBoot?

I am working on a SpringBoot REST service. The REST service works when the UI sends the right JSON values (formatted).
Sometimes the UI team will forget to upper case a property value and cause an exception. I want to make the REST service handle such cases.
JSON property is being POSTed as
"category":"patient"
It is supposed to be POSTed with uppercase.
"category":"PATIENT"
The Java object property category is a ENUM
public enum StaffCategory {
PATIENT, EQUIPMENT
}
The ui model object
#JsonProperty("category")
private StaffCategory category;
#JsonProperty("category")
public StaffCategory getCategory() {
return category;
}
#JsonProperty("category")
public void setCategory(StaffCategory category) {
this.category = category;
}
#JsonProperty("category")
private StaffCategory category;
This is the error I get
Can not deserialize value of type model.constants.StaffCategory
from String "patient": value not one of declared Enum instance names: [PATIENT, EQUIPMENT]
Although UI team should stick to backend API specs, still you can use ObjectMapper configuration to overcome this specific scenario:
#Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS, true);
return mapper;
}
You dont need to convert it to uppercase because it lowers readability and also avoids maintainability.You only need to change your Enum definition as:
public enum StaffCategory {
PATIENT("patient"), EQUIPMENT("equipment");
private String value;
private StaffCategory(String value) { this.value = value; }
#JsonValue
public String getValue() { return this.value; }
}
This way it get easily deserialized with no breaking your code or facing any problems.

How can I avoid duplicate #JsonProperty annotations with an immutable object and non-matching property name?

The json I'm dealing with uses underscores in the property names, but I wish to keep camel case in Java. Further, I'm using immutable style POJOs, since that's a best practice our team has long adopted.
Everything works fine if I put duplicate #JsonProperty annotations in the constructor and on the getter, but this adds a lot of unnecessary bloat (in our classes, we have a couple dozen properties.) Is there a way to tell Jackson exactly once how to transform the Java property name to the JSON property name?
public class Foo {
public final String someProperty;
#JsonCreator
public Foo(#JsonProperty("some_property") someProperty) {
this.someProperty = someProperty;
}
#JsonProperty("some_property")
public String getSomeProperty() {
return someProperty;
}
}
You can choose the naming convention used for JSON. In this case you need SNAKE_CASE. It will convert someProperty field to "some_property": "" JSON. Then you don't need the #JsonProperty in the property.
ObjectMapper mapper = new ObjectMapper();
mapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
In this case, in Foo, you need to provide the field in the constructor, what requires the #JsonProperty in the constructor params:
public class Foo {
public final String someProperty;
#JsonCreator
public Foo(#JsonProperty("some_property") String someProperty) {
this.someProperty = someProperty;
}
public String getSomeProperty() {
return someProperty;
}
}
At least you can get rid of one of the annotations.

Java - How to convert a String into specific enum implementing an interface

Consider below inner Enum implementing an interface:
public interface NotificationTypes {
public enum CONTACT_LIST implements NotificationTypes{
ADDED("CONTACT_LIST-ADDED"),
REMOVED("CONTACT_LIST-REMOVED");
public enum INVITATION implements NotificationTypes{
ACCEPTED("CONTACT_LIST-INVITATION-ACCEPTED"),
REJECTED("CONTACT_LIST-INVITATION-REJECTED");
String name = "";
private INVITATION(String name){
this.name = name;
}
public String getName(){
return name;
}
};
String name = "";
private CONTACT_LIST(String name){
this.name = name;
}
public String getName(){
return name;
}
}
public String getName();
}
Now consider that data in database/mongodb is stored in the form of String for NotificationTypes in a table/document.
{
"_id" : ObjectId("59882ba49e5d82c72ba44fde"),
"template" : "Contact list Invitation accepted by your friend",
"type" : "CONTACT_LIST-INVITATION-ACCEPTED"
}
So my question is: How to convert that string back into specific enum at runtime without knowing exactly the name of enum to be mapped?
Domain class is looks like this:
#Document(collection = CollectionNames.XXX_TEMPLATE)
public class XXXTemplate {
private NotificationTypes type;
//Other parameters, getters & setters + Constructor
}
I'd build a Map<String, NotificationTypes> and populate that with all instances you have. You can then look up from that map.
I don't think the compiler can help you a lot with keeping that in sync, other than that you can loop over EnumType.values() (but you have to remember to do it for all of your enum types).
How to convert that string back into specific enum at runtime without knowing exactly the name of enum to be mapped?
Via Enum.valueOf().
Basically just building on #Thilo's answer, but perhaps a more 'Springified' way if it's something that you'd want - you could define a #Bean in your config that contains all your enum values, like:
#Configuration
public class Config {
#Bean
public List<NotificationTypes> notificationTypes() {
List<NotificationTypes> notificationTypes = new ArrayList<>();
notificationTypes.addAll(Arrays.asList(NotificationTypes.CONTACT_LIST.values()));
notificationTypes.addAll(Arrays.asList(NotificationTypes.CONTACT_LIST.INVITATION.values()));
return notificationTypes;
}
}
And then #Autowire this #Bean into a parser to do the actual matching of String to enum, something like:
#Component
public class NotificationTypeParser {
#Autowired
private List<NotificationTypes> notificationTypes;
public NotificationTypes parseNotificationType(String type) {
for (NotificationTypes notificationType : notificationTypes) {
if (notificationType.getName().equals(type)) {
return notificationType;
}
}
return null;
}
}
Obviously you probably want something better than just returning null if the enum isn't found, and you could potentially do something smarter in the #Bean definition to validate that the enums all have different names, etc. Or, conceivably, use reflection in there to find all the implementations of NotificationTypes.
I'm not sure that this really gives you any additional benefits over just storing all the possible values in a Map, but, as I say, I suppose it's a bit Spring-ier.

How to ensure field inclusion in Jackson

I've a POJO and I want to create an instance of this class from JSON. I'm using jackson for converting JSON to Object. I want to ensure that JSON will conain all properties of my POJO. The JSON may contain other extra fields but it must contain all the attributes of the POJO.
Example:
class MyClass {
private String name;
private int age;
public String getName(){return this.name;}
public void setName(String name){this.name = name;}
public int getAge(){return this.age;}
public void setAge(int age){this.age = age;}
}
JSON #1
{
"name":"Nayan",
"age": 27,
"country":"Bangladesh"
}
JSON #2
{
"name":"Nayan",
"country":"Bangladesh"
}
Here, I want JSON#1 to be successfully converted to MyClass but JSON#2 should fail. How can I do this? Is there an annotation for this?
Well, there is an annotation that you could apply to your properties that say they are required.
#JsonProperty(required = true)
public String getName(){ return this.name; }
The bad part is, as of right now (2.5.0), validation on deserialization isn't supported.
...
Note that as of 2.0, this property is NOT used by BeanDeserializer: support is expected to be added for a later minor version.
There is an open issue from 2013 to add validation: Add support for basic "is-required" checks on deserialization using #JsonProperty(required=true)

Categories