Deserialise enum field with Jackson - java

I have a simple enum Days
public enum Days {
#JsonProperty("Monday")
MONDAY("Monday"),
#JsonProperty("Tuesday")
TUESDAY("Tuesday");
private String day;
Days(String day) {
this.day = day;
}
#JsonValue
public String getDay() {
return day;
}
}
and a class Event
public class Event {
private Days day;
private String name;
#JsonCreator
public Event(#JsonProperty("day") Days day,
#JsonProperty("name") String name) {
this.day = day;
this.name = name;
}
public Days getDay() {
return day;
}
public String getName() {
return name;
}
}
I am using Jackson 2.9, and this answer indicates that using #JsonProperty should be enough, however I struggle to deserialize this:
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
Event event = new Event(Days.MONDAY, "Birthday");
String serialisedEvent = objectMapper.writeValueAsString(event);
System.out.println(serialisedEvent);
// {"day":"Monday","name":"Birthday"}
Event deserialisedEvent = objectMapper.convertValue(serialisedEvent, Event.class);
// Exception in thread "main" java.lang.IllegalArgumentException: Cannot construct instance of `xyz.blabla.Event` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('{"day":"Monday","name":"Birthday"}')
// at [Source: UNKNOWN; line: -1, column: -1]
// at com.fasterxml.jackson.databind.ObjectMapper._convert(ObjectMapper.java:3750)
System.out.println(deserialisedEvent.getDay());
}
I am using Java 11 and Jackson 2.9 in a Spring Boot 2 project. How can I make this work?

Jackson deserialization method is called readValue.
The purpose of convertValue is different — it serializes an object (which may be a string — it would become a JSON-string-literal then) first, and then deserializes the result into an object of the target type.
The following should work:
Event deserialisedEvent = objectMapper.readValue(serialisedEvent, Event.class);

You don't have default constructor but an arg constructor.
You have to annotate it with #JsonCreator in order that Jackson uses it to deserialize the JSON :
#JsonCreator
public Event(Days day, String name) {
this.day = day;
this.name = name;
}
To serialize a Java object to JSON, the constructor is not used by Jackson since it doesn't create java instances but just use getters to retrieve properties of it. So it worked.
But to unserialize JSON to Java object, Jackson needs to instantiate the target class. By default it looks for the no arg constructor.
Note also that annotating the constructor parameters with #JsonProperty("...") is not needed if you use the ParameterNamesModule such as :
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new ParameterNamesModule());
Spring Boot 2 provides it for you as you depends on Jackson.
Here you need it because you don't use the Mapper wiring by Spring Boot but you instantiate it by yourself.
As well as the #JsonProperty annotations located in the enum are not needed either :
public enum Days {
#JsonProperty("Monday")
MONDAY("Monday"),
#JsonProperty("Tuesday")
TUESDAY("Tuesday");
//...
}
It allows to change the serialization output of the enum but actually you don't need to change it as you map it to the day field value currently used for the enum Jackson mapping...

To add to this answer, I scoured the internet looking for how to add ONE-TIME as an enum value (and have it saved with the dash). Note: enums do not allow for a dash. I fixed this by simply adding
#JsonProperty("ONE-TIME")
above the enum field declaration.

Related

Jackson MismatchedInputException (no String-argument constructor/factory method to deserialize from String value)

I am using SpringBoot 2.3.1-RELEASE and am trying to deserialize JSON string to a POJO containing list of objects but I keep running into this error:
Cannot construct instance of com.response.dto.RootDTO (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('Meta')
at [Source: (String)""Meta":[{"DimensionName":"Version","DimensionId":"3b4860b9-b215-4192-bd7a-a76f377fc465","DimensionType":"Regular","Alias":"C0","AttributeId":"211d5-d91f-40ec-9668-20e0da2ae7b3","AttributeName":"Version Name","AttributeKey":"VersionKey"; line: 1, column: 1]
This is what my JSON string looks like (but with escape chars in eclipse):
{"Meta":[{"DimensionName":"Version", "DimensionId":"3b4860b9-b215-4192-bd7a-a76f377fc465, "DimensionType":"Regular","Alias":"C0","AttributeId":"211b33d5-d91f-40ec-9668-20e0da2ae7b3","AttributeName":"Version Name","AttributeKey":"VersionKey"}]}.
Here is the class I want to deserialize it to:
#JsonIgnoreProperties(ignoreUnknown = true)
#Data
public class RootDTO
{
#JsonProperty("Meta")
private List<MetaDTO> Meta;
}
#JsonIgnoreProperties(ignoreUnknown = true)
#Data
public class MetaDTO
{
#JsonProperty("DimensionName")
private String DimensionName;
#JsonProperty("AttributeId")
private String AttributeId;
#JsonProperty("AttributeName")
private String AttributeName;
#JsonProperty("Name")
private String Name;
#JsonProperty("Alias")
private String Alias;
}
This is the code that blows up when trying to read the value:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
objectMapper.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.readValue(jsonFormattedString, RootDTO.class));
I only see this issue while running Junit (version : 4.12). I see jackson-databind-2.11.0, spring-test-5.2.7.RELEASE in the stack trace. However, I debug using a call from browser or postman it works fine. I am not sure why it is looking for the string Meta when I have specified it to be a list. What could be causing this issue? Any suggestions?
Edit: Turns out that the string which was being supplied to the ObjectMapper isn't the correct one. There is this line of code
String jsonFormattedString = responseEntity.getBody().substring(1, responseEntity.getBody().lastIndexOf("\"")).replaceAll("\\\\", ""); which makes my mocked string invalid. I'll need to figure out why we are doing this though.
Change the first letter's of variable to lowwer case. And remove the JsonProperty.
As below. And auto generate the setter and getter.
private String DimensionName;
private String attributeId;
private String attributeName;
private String name;
public void setName(String name){
this.name=name;
}
.........
.........
//All setter getter
Add #JsonGetter("Meta") each getter method.
For example as below.
#JsonGetter("Meta")
public List<Meta> getMeta(){
return meta;
}

Custom deserialisation of JSON field using Jackson

I'm using Jackson to deserialize some JSON and I've run into some trouble while trying to use a custom deserializer for one of the fields.
class MyClass
{
private static class SpecialPropertyDeserializer extends JsonDeserializer<SpecialProperty>
{
#Override
public SpecialProperty deserialize(JsonParser jsonParser,
DeserializationContext deserializationContext) throws IOException, JsonProcessingException
{
// do some custom deserialisation
}
}
private static class SpecialProperty
{
private String m_foo;
private String m_bar;
#JsonCreator
SpecialProperty(#JsonProperty("foo") String foo,
#JsonProperty("bar") String bar)
{
m_foo = foo;
m_bar = bar;
}
}
private String m_identifier;
private String m_version;
#JsonDeserialize(using = SpecialPropertyDeseializer.class)
private SpecialProperty m_specialProperty;
#JsonCreator
MyClass(#JsonProperty("identifier") String identifier,
#JsonProperty("version") String version,
#JsonProperty("specialProperty") SpecialProperty specialProperty)
{
m_identifier = identifier;
m_version = version;
m_specialProperty = specialProperty;
}
}
and this is the JSON I want to deserialize:
{
"identifier" : "some-id",
"version" : "1.7",
"specialProperty" : {
"foo" : "str1",
"bar" : "str2"
},
}
I invoke the mapper as follows:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true);
return objectMapper.readValue(input, MyClass.class);
I've observed the following behaviour:
Without a special property it all works fine - i.e. remove all
references to SpecialProperty from the code and the JSON.
If I include SpecialProperty in the JSON but remove the custom
deserializer for it then it also works fine. The ctor for
SpecialProperty is called.
With the custom deserializer it doesn't work. The ctor for SpecialProperty is called but the custom deserializer is not.
What am I doing wrong?
#JsonDeserialize annotation can be placed on a field, a setter or a class. Jackson will take it into account if what is annotated is what it uses to set the value.
E.g.1 It will notice #JsonDeserialize over a setter if it uses the setter to set the value of a field.
E.g.2 It will notice #JsonDeserialize over a field if it directly sets this field without using a setter or a constructor.
It will tend to take it into account if it's on a class unless it's overridden by a more specific annotation on a field or setter docs. I reckon the docs could be clearer on the above details.
In your case you have the annotation over the SpecialProperty field but you are setting this field in the MyClass constructor so it's ignored.
In this case you can move #JsonDeserialize over the class instead of over the field. That's probably the simplest solution in your case. E.g.
#JsonDeserialize(using = MyClass.SpecialPropertyDeserializer.class)
private static class SpecialProperty {
Or you can skip the annotation altogether and register the deserializer on the mapper. First make SpecialProperty and SpecialPropertyDeserializer non private in MyClass and then:
ObjectMapper objectMapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(MyClass.SpecialProperty.class, new MyClass.SpecialPropertyDeserializer());
objectMapper.registerModule(module);
You can also get rid of constructor of MyClass and the current annotation over the SpecialProperty field will be taken into account.

Jackson: Use default (de)serializer

I'm trying to (de)serialize an object that has a property with a type that comes from a maven dependency, so I can't change the class of this type.
The class of this type has a #JsonSerialize and #JsonDeserialize annotation.
However, I want to use the default serializer and deserialzer, because the custom serializer writes an array instead of an object. Is there a way, using annotations, to tell jackson to use the default (de)serializer?
You can disable the annotations using Jackson's mixins feature.
In the following example, any attempt at deserializing to a CustomerObj will result in an exception due to its defective Builder:
#JsonDeserialize(builder = CustomerObj.class)
public class CustomerObj {
public String name;
public int age;
public CustomerObj build() {
throw new RuntimeException("JsonDeserializer invoked");
}
}
Create a mixin with a JsonDeserialize annotation that disables the broken builder:
#JsonDeserialize(builder = java.lang.Void.class)
public static abstract class CustomerMixin { }
Register the mixin on the ObjectMapper instance:
ObjectMapper om = new ObjectMapper();
om.addMixIn(CustomerObj.class, CustomerMixin.class);
Enjoy working deserialization:
final String json = "{\"name\":\"Brian\",\"age\":41}";
CustomerObj customer = om.readValue(json, CustomerObj.class);

Jersey - JSON marshall only specific fields

My REST service returns following JSON
{
"name": "John",
"id" : 10
}
Can I use Jersey to marshall it into following Bean:
public class User{
private String name;
//getter & setter
}
I wanted to do this with following code but it doesn't work
WebResource webResource = client.resource(url);
webResource.accept(MediaType.APPLICATION_JSON_TYPE);
User user = webResource.get(User.class);
Is this even possible or I have to implement full JSON structure in Java Beans to get it work?
I know that I can parse this JSON with Jackson and any other methods.
With Jackson, easiest way is to configure ObjectMapper like so:
objectMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES,
false);
Check this sample provider
package com.company.rest.jersey;
#Provider
#Component
#Produces({MediaType.APPLICATION_JSON})
public class JacksonMapperProvider implements ContextResolver<ObjectMapper> {
ObjectMapper mapper;
public JacksonMapperProvider(){
mapper = new ObjectMapper();
mapper.configure(Feature.INDENT_OUTPUT, true);
// Serialize dates using ISO8601 format
// Jackson uses timestamps by default, so use StdDateFormat to get ISO8601
mapper.getSerializationConfig().setDateFormat(new StdDateFormat());
// Deserialize dates using ISO8601 format
// MilliDateFormat simply adds milliseconds to string if missing so it will parse
mapper.getDeserializationConfig().setDateFormat(new MilliDateFormat());
// Prevent exceptions from being thrown for unknown properties
mapper.configure(
DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES,false);
}
#Override
public ObjectMapper getContext(Class<?> aClass) {
return mapper;
}
}
With Jackson :
You have two options:
Jackson works on setters-getters of fields. So, you can just remove getter of field which you want to omit in JSON. ( If you don't need getter at other place.)
Or, you can use the #JsonIgnore annotation of Jackson on getter method of that field and you see there in no such key-value pair in resulted JSON.
#JsonIgnore
public int getSecurityCode(){
return securityCode;
}
In your bean, add the annotation #JsonIgnoreProperties(ignoreUnknown = true) at the class level and it should skip the id property in the JSON since it's not present in the bean.
#JsonIgnoreProperties(ignoreUnknown = true)
public class User{
private String name;
//getter & setter
}
(See http://wiki.fasterxml.com/JacksonAnnotations for details)

Jackson incorrectly deserializing nested object's values into parent

I have the following JSON file which I am trying to deserialize:
{
"name": "ComponentX",
"implements": ["Temperature.Sensor"],
"requires": [
{"type": "interface", "name": "Temperature.Thermostat", "execute": [
"../Thermostat.exe"
]}
]
}
It is a description of a component in a code sample for a distributed system.
Here is the class that this is supposed to deserialize to:
public class ComponentDescription {
#JsonProperty("name")
public String Name;
#JsonProperty("implements")
public String[] Implements;
#JsonProperty("requires")
public ComponentDependency[] Requires;
#JsonIgnore
public String RabbitConnectionName;
private static final ObjectMapper mapper = new ObjectMapper();
public static ComponentDescription FromJSON(String json)
throws JsonParseException, JsonMappingException, IOException
{
return mapper.readValue(json, ComponentDescription.class);
}
public class ComponentDependency
{
#JsonCreator
public ComponentDependency() {
// Need an explicit default constructor in order to use Jackson.
}
#JsonProperty("type")
public DependencyType Type;
#JsonProperty("name")
public String Name;
/**
* A list of ways to start the required component if it is not already running.
*/
#JsonProperty("execute")
public String[] Execute;
}
/**
* A dependency can either be on "some implementation of an interface" or it
* can be "a specific component", regardless of what other interface implementations
* may be available.
*/
public enum DependencyType
{
Interface,
Component
}
}
When I run ComponentDescription.FromJSON(jsonData), which uses the Jackson ObjectMapper to deserialize the JSON into the appropriate classes, I get the following exception:
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "type" (class edu.umd.cs.seam.dispatch.ComponentDescription), not marked as ignorable (3 known properties: , "implements", "name", "requires"])
at [Source: java.io.StringReader#39346e64; line: 1, column: 103] (through reference chain: edu.umd.cs.seam.dispatch.ComponentDescription["requires"]->edu.umd.cs.seam.dispatch.ComponentDescription["type"])
It seems that Jackson is trying to deserialize the requires array in the JSON object as an array of ComponentDescription instead of an array of ComponentDependency. How do I get it to use the correct class? I would prefer an answer that gets Jackson to look at the type of public ComponentDependency[] Requires and see use it automatically over an answer that requires me to put the type name in again somewhere else (such as an # attribute).
My guess is that the problem comes from ComponentDependency not being static. As it is not declared static, it means it can only be instantiated with an existing instance of ComponentDescription.
For more details, see here.

Categories

Resources