How to deserialize JSON string using standard setters - java

I am using jackson-core, databind, annotations 2.3.3 jars. I have the following simple class
public class ClassA {
private int value;
public int getValue() {
return this.value;
}
public void setValue(int value) {
this.value = value;
}
}
And here is the code to try to deserialize a JSON string to the object:
import com.fasterxml.jackson.databind.ObjectMapper;
...
final ObjectMapper objectMapper = new ObjectMapper();
ClassA request = objectMapper.readValue("{\"Value\": 1}", ClassA.class);
But I am getting the following error:
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "Value" (class ClassA), not marked as ignorable (one known property: "value"])
at [Source: java.io.StringReader#3bff5976; line: 1, column: 12] (through reference chain: ClassA["Value"])
If I changed the JSON string to lower case then it worked. I thought Jackson would be able to map the value to the setter by following the setter convention. I understand i could add JsonProperty annotation to ClassA to make it work but I cannot modify ClassA in my situation.
I also tried explicitly enabling the following mapping features before calling readValue, but it still got the same error:
import com.fasterxml.jackson.databind.MapperFeature;
...
objectMapper.enable(MapperFeature.AUTO_DETECT_GETTERS);
objectMapper.enable(MapperFeature.AUTO_DETECT_SETTERS);
How can I have Jackson bind to standard getters/setters (getXxx and setXxx) without specify annotation to the class being bound?
Thanks!

It looks like this is happening because of the default PropertyNamingStrategy provided by Jackson. From the documentation:
In absence of a registered custom strategy, default Java property
naming strategy is used, which leaves field names as is, and removes
set/get/is prefix from methods (as well as lower-cases initial
sequence of capitalized characters).
The mapper default uses the Java default property naming strategy. If your JSON properties are Pascal Case (not sure because you only provided 1 property) then you can give the mapper the PascalCaseStrategy.
final ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setPropertyNamingStrategy(new PropertyNamingStrategy.PascalCaseStrategy());
ClassA request = objectMapper.readValue("{\"Value\": 1}", ClassA.class);

Jackson follows bean naming conventions.
I understand i could add JsonProperty annotation to ClassA to make it work but I cannot modify ClassA in my situation.
That's where mixins come in handy. Create an abstract class that has the same method declarations (same getters for example) as ClassA and annotate them with the appropriate #JsonProperty annotation. You then register the mixin with the ObjectMapper and voila! The ObjectMapper will now use the mixin class as a template for serializing and deserializing ClassA.

Related

Deserializing Java class with incomplete ParameterTyped properties - Jackson

Consider we are having the following classes
//following classes are present in external lib, we can not modify them.
class A{
private Map mapOfListOfB; // this should have been properly typed Map<String,List<B>>
}
class B{
private int val1;
private String val2;
private C val3; // Class C can be anything but the point here is the same object of C can be used in multiple B objects
//which means we can reuse the reference using #JsonIdentityInfo
}
now, when we use Jackson's objectMapper to serialize and deserialize this Class A,
we would not be able to deserialize because we are not giving any typed info to Jackson so it ends up creating List<LinkedHashMap>
here is one solution that I know, works for class having a collection with a specific class type
eg:
class D{
private Map mapOfB // which should have been Map<String,B>
}
// this can be typed by using jackson's mixin
abstract class DmixIn{
#JsonDeserialize(contentAs = B.class)
Map mapOfB
}
but how we can tell the type which is present in class A to Jackson as we can not pass ParameterizedType to contentAs, it just takes class instance.
I know we can write a custom deserializer to achieve the end result but I am looking for a more readable solution here, like any Jackson annotation or any simple config which we can be set on the property level.
and another issue(which can be because of my awareness) is I even need to maintain deserialization context while writing custom deserializers by using #JsonDeserialize(contentUsing = CustomDeserializer.class) because Class C references are reused and I may need to resolve IDs for this POJO while deserializing

Deserialize using Jackson ObjectMapper to Pojo<T>

I'm trying to deserialize using ObjectMapper to a POJO and im getting the error:
No suitable constructor found for type [simple type, class LambdaResult<java.lang.Object>]: can not instantiate from JSON object (missing default constructor or creator, or perhaps need to add/enable type information?)
My Pojo is quite simple
class LambdaResult<T> {
LambdaResult() {}
String Status
ArrayList<T> Results
}
And my deserialization code is the following
static <T> T Deserialize(final TypeReference<T> type,
final String json) {
return new ObjectMapper().readValue(json, type)
}
LambdaResult<Object> result = Serialization.Deserialize(new TypeReference<LambdaResult<Object>>() {},jsonResult)
Json example:
{"status": "success", "locale": "sg", "results": [{"status": "pending"}]}
I come from a C# background so there's something im probably missing here.
Thanks
I would recommend creating and configuring an ObjectMapper like this (Java, sorry don't speak Groovy):
ObjectMapper mapper = new ObjectMapper();
mapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper in this case should probably be a field in the same class as Deserialize method that would then look like:
return mapper.readValue(json, type);
Explanation for configuration:
This MapperFeature tells Jackson to match field names in a case insensitive manner. So it will find String Status although it doesn't follow Java Beans naming conventions. This is neat in your case but comes with a small performance penalty for transforming a String to lowercase. You can alternatively annotate a field in a class with #JsonProperty and specify any JSON field name you want.
This tells Jackson to match JSON fiels to Java object fields regardless of field visibility private, public etc. You may also annotate your class with #JsonAutoDetect and specify visibility there. In both cases you don't need to define a constructor but you can keep it if you don't want it to be public.
The last one tells Jackson to not fail when encountering fields in JSON that don't exist in your class. In this case "locale".

Where to register custom ObjectMapper for my REST Service application?

I am building a REST service application with Spring and Jersey.
The "Response Content Type" can be either JSON or XML, which is why I use JAXB annotations on the fields of my POJO, which works great so far. No configuration was needed to get this to work, I just put the required .jars on the classpath.
Now, I want to add custom behaviour for (de-)serialization of Java 8 Date/Time objects and register the JavaTimeModule from Jackson.
Where would be the right place to register the Module? I know the code snippet is supposed to look something like this:
ObjectMapper tmpMapper = new ObjectMapper();
tmpMapper.registerModule(new JavaTimeModule());
But where do I actually put this code?
The place that comes to mind is my ResourceConfig class from Jersey. It looks like this:
#ApplicationPath("/rest")
public class ApplicationResourceConfig extends ResourceConfig {
public ApplicationResourceConfig() {
// Not needed because since jersey 2.9 JacksonFeature implements AutoDiscoverable
// register(JacksonFeature.class);
register(WadlFeature.class);
// Registers our own request listener for monitoring purposes
register(RestServiceApplicationEventListener.class);
// Registers the package that contains our REST resources
packages(PingResource.class.getPackage().getName());
}
What I did now was to add the following lines at the bottom of the constructor:
ObjectMapper tmpMapper = new ObjectMapper();
tmpMapper.registerModule(new JavaTimeModule());
JacksonJaxbJsonProvider tmpProvider = new JacksonJaxbJsonProvider();
tmpProvider.setMapper(tmpMapper);
register(tmpProvider);
The DateTime conversion now worked. However, some of the the JAXB behaviour changed which caused the deserialization of other beans to break. Here is what is not working anymore:
I have super class which defines a getter of a field:
public abstract class SuperClass {
#XmlElement(name = "yourField")
public abstract String getMyField();
}
(This maps the internal name of the field to different name which is exposed to the outside)
public class SubClass {
private String myField;
#Override
public String getMyField() {
return myField;
}
}
This mechanism used to work before and I could e.g. define a POST parameter of type SubClass calling the method with a JSON snippet that looks like this:
{
"yourField" : "hello world"
}
When trying this now I get the exception:
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "yourField" (class com.myProject.SubClass), not marked as ignorable (1 known property: "myField"])
at [Source: org.glassfish.jersey.message.internal.ReaderInterceptorExecutor$UnCloseableInputStream#728b1889; line: 4, column: 14] (through reference chain: com.myProject.Subclass["yourField"])
Note however, that the basic JAXB features still work, only in this particular superclass/subclass example.
Could it be that explicitly registering a JacksonJaxbJsonProvider somehow altered the default behaviour on how to deal with JAXB annotated beans? Is there a way to maybe retrieve the existing JacksonJaxbJsonProvider (that must have been registered implicitly somehow) or the existing ObjectMapper object and register the JavaTimeModule to it instead of creating a new Mapper object and a new Provider object?

Jackson Subclass serialization

I'm using an java API that I do not own and cannot modify. That API defines a class Aggregation which is a Immutables (immutables.github.io) abstract class with Jackson serialization and deserialization.
The class looks like
#Value.Immutable
#JsonSerialize(as = Immutable_Aggregation.class)
#JsonDeserialize(as = Immutable_Aggregation.class)
public abstract class Aggregation {
I want to add a field to Aggregation which should be picked up at serialization time, but instances of which can still be passed to methods accepting Aggregation.
I want to do something like:
public abstract class ExtendedAggregation extends Aggregation {}
Is this possible?
I haven't managed to inherit between two classes marked with #Value.Immutable as you would like, so I tried to use Jackson to solve the issue instead.
Found the following approach, which uses the #JsonAppend annotation and mixins to add properties without touching the Aggregation class.
First, define a mix-in class with the annotation:
#JsonAppend(
attrs = {
#JsonAppend.Attr(value = "version")
})
public class AggregationMixin {}
Register the Mixin with your ObjectMapper instance:
mapper.addMixIn(Aggregation.class, AggregationMixin.class);
Serialize the class by creating a dedicated ObjectWriter, in which you specify the value of the added property:
ObjectWriter writer = om.writerFor(Aggregation.class).withAttribute("version", "42");
...
String output = writer.writeValueAsString(aggregationInstance);
The #JsonAppend annotation can be configured to obtain its value in a multitude of ways. Consult its documentation for details.
Some further reading :
http://www.baeldung.com/jackson-advanced-annotations#jsonappend
http://wiki.fasterxml.com/JacksonMixInAnnotations

Map BasicNameValuePair from JSON with Jackson

I'm currently trying to deserialize an API result, which looks like the following
[{"name":"MyName","value":"MyValue"},{"name":"MyName2","value":"MyValue2"}]
ArrayList<BasicNameValuePair> entities = JsonUtils.getObjectMapper()
.readValue(receivedData.toString(),
new TypeReference<ArrayList<BasicNameValuePair>>() {});
Then the following exceptions occurs
Exception mapping result.
No suitable constructor found for type...
Since this is an internal class from org.apache.http.message.BasicNameValuePair, I can not annotate or edit it in any way. But I see (from other android projects) a lot of people using this class. Is there some way to get this working? Serializing to String from BasicNameValuePair works.
Jackson uses reflection to create an instance of your class. By default, it expects a no-arg constructor. The BasicNameValuePair class does not have such a constructor. It has a constructor with two parameters, one for name and one for value.
Typically, if you had control of the class, you could annotate the constructor parameters with #JsonProperty so that Jackson used that constructor instead of the no-arg constructor. Since you don't have control of the code, use Mixins.
Declare a class like so
public static abstract class BasicNameValuePairMixIn {
private BasicNameValuePairMixIn(#JsonProperty("name") String name, #JsonProperty("value") String value) { }
}
And configure your ObjectMapper like so
// configuration for Jackson/fasterxml
objectMapper.addMixInAnnotations(BasicNameValuePair.class, BasicNameValuePairMixIn.class);
Jackson will now use the mixin as a template for your class.
If you are using the older version of Jackson, use the configuration as described here.
Try not to use reserve keywords in your parameter names and then try again. "name","value"

Categories