JsonMappingException using Jackson - mistakenly places a field in the wrong class - java

so I am getting a JsonMappingException with what I consider to be a weird error:
org.codehaus.jackson.map.exc.UnrecognizedPropertyException:
Unrecognized field "CoTravellers" (Class JobPush), not marked as
ignorable at [Source: java.io.StringReader#416a60a0; line: 1, column:
947] (through reference chain: >JobPush["CoTravellers"])
Now, what I have is this:
ProtocolContainer --> JobPush (inherits from DataPacket) --> Job --> CoTravellers
The JobPush, mentioned in the error above, is a sub-class of DataPacket. So, the ProtocolContainer has one DataPacket, and I have several classes inheriting DataPacket, where JobPush is one.
The JobPush is simple, looks like this:
public class JobPush extends DataPacket
{
public Job Job;
}
and it is in the Job-class that the CoTravellers field exists, not in the JobPush:
public class Job implements Serializable
{
#JsonDeserialize(using=CustomMapCoTravellerDeserializer.class)
public Map<Objects.CoTravellers, Integer> CoTravellers;
// ....
}
As you can see, I am trying to use a custom deserializer (see here for reference).
Now, I cannot understand why I get an error saying that there is no field "CoTravellers" in JobPush? I never said that CoTravellers is in the JobPush, as it is inside the Job-class.
The JSON I am parsing looks like this (this is cropped a bit for clarity, where SubPacket is the variable name, holding the DataPacket which in this case is a JobPush):
"SubPacket":{
"__type":"JobPush:#DataPackets.ToVehicle",
"Job":{
"CoTravellers":[
{
"Key":{
"CoTravellerId":0,
"Name":"Medresenär"
},
"Value":1
}
]
}
}
Anyone out there who can clue me in? =)
---- EDIT 1 ----
Adding some stuff, for clairity:
So, classes inherit from the class "DataPacket", and thus I have used #JsonSubTypes annotation to deal with that. This is the DataPacket-class:
#JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.PROPERTY, property="__type")
#JsonSubTypes(
{
#JsonSubTypes.Type(value = JobPush.class, name = "JobPush:#DataPackets.ToVehicle"),
})
public class DataPacket
{
}

you talk about a CoTraveller field, but in the JSON it is CoTravellers with an 's' on the end.

Related

Java Jackson Deserialization of Nested Objects

Working with AWS Lambdas, and when one fails due to an exception, AWS serializes that exception as JSON and sends it back to whatever invoked that Lambda. Here's what that might look like:
{
"errorMessage":"USER_SERVICE_FAILURE",
"errorType":"com.company.project.lambda.core.exceptions.LambdaFailureException",
"stackTrace":[
"com.company.project.lambda.worker.MainWorkerLambda.handleRequest(AccountWorker.java:127)",
"sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)",
"sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)",
"sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)",
"java.lang.reflect.Method.invoke(Method.java:498)"
],
"cause":{
"errorMessage":"some other exception error message",
"errorType":"com.company.project.lambda.core.exceptions.SomeOtherException",
"stackTrace":[
"insert other stack trace strings here...",
"...",
"..."
],
"cause":{
"errorMessage": "...",
...continue in to perpetuity...
}
}
}
The errorMessage, errorType, and stackTrace fields are easy enough to deserialize - they will always be a single String, a single String, and a List<String> respectively.
Where I'm stuck is the cause field. This could be an empty object if there is no cause, or one nested exception, or two, or a hundred...
How do I deserialize this? Here's my POJO so far.
#Data
#NoArgsConstructor
#AllArgsConstructor
public class ExceptionRep {
String errorMessage;
String errorType;
List<String> stackTrace;
// how do I do the cause field?
}
Define your class as a node structure that has an attribute of its same type:
#Data
#NoArgsConstructor
#AllArgsConstructor
public class ExceptionRep {
String errorMessage;
String errorType;
List<String> stackTrace;
private ExceptionRep cause;
}
I would simply reuse your ExceptionRep class for the reference type, and annotate the object as optional (so if there is no cause in the JSON, it won't fail de-serialization).
Something like:
#JsonProperty(required=false)
ExceptionRep child;
This way if there are nested causes, they are recursively de-serialized.
If there are none, the property is ignored.
You can use a HashMap if you don't want to have to define all the properties in a custom class.

Jackson Deserialization Fails because of non-default constructor created by lombok

Jackson can deserialize json for the following class in 2.6.5 but fails in 2.8.8.
Model:
public static class Parent {
public long id;
public List<Child> children;
}
#RequiredArgsConstructor
public static class Child {
public long childId;
#NonNull
#JsonIgnore
public Parent parent;
public Child() { }
}
JSON:
{
"id": 1,
"children": [
{
"childId": 2
}
]
}
The exception is:
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "childId" (class Parent), not marked as ignorable (2 known properties: "children", "id"])
I have found that the Child constructor created by lombok is causing this error. When I get rid of the lombok annotation or if I create the constructor manually, this stops happening. Either way, it should be using the no-args Child() constructor. What is causing this issue?
Lombok adds the annotation #ConstructorProperties({"parent"}) to the generated constructor. In Jackson 2.8.8, this causes the constructor to be treated as a "delegate creator".
A delegate creator allows Jackson to deserialize json for one type of object into another type of Java object.
In this case, because lombok generates the constructor #ConstructorProperties({"parent"}) Child(Parent parent) {...} Jackson will try to deserialize the child json as a Parent object, which could then be passed into the constructor to create a Child. It then throws the exception because childId is not a field in Parent.
One workaround is to configure the ObjectMapper used to deserialize the JSON with a custom JacksonAnnotationIntrospector so it won't interpret the constructor as a delegate creator.
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setAnnotationIntrospector(new JacksonAnnotationIntrospector().setConstructorPropertiesImpliesCreator(false));
Update
Version 1.16.20 of project lombok did indeed default lombok.anyConstructor.suppressConstructorProperties to true as Roel indicated might happen in his comment.
That makes upgrading lombok to the latest version another fix for this issue.

Jackson polymorphic deserialization

I've got the following problem with Jackson and type hierarchy. I'm serializing a class SubA which extends Base into a String,
and trying afterwards to derserialize it back. Of course at compile time, the system does not know whether it will be
Base or SubA so I'm expecting a Base and will do some other operations afterwards, if it is a SubA.
My Base class looks like:
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type")
#JsonSubTypes({
#Type(value = SubA.class, name = "SubA")
})
public class Base {
protected String command; // +get +set
protected String type; // +get +set
}
... and a class deriving from Base:
#JsonTypeName("SubA")
public class SubA extends Base {
private AnotherClass anotherClass; // +get +set
private String test; // +get +set
#JsonIgnore
#Override
public String getType() {
return "SubA";
}
}
... and I'm trying to execute the following code:
ObjectMapper mapper = new ObjectMapper();
ObjectWriter ow = mapper.writer().withDefaultPrettyPrinter();
Base payload = new SubA(); // + setting anotherClass as well as test variables
String requestStringSend = ow.writeValueAsString(payload);
System.out.println("Sending: " + requestStringSend);
Base received = mapper.readValue(requestStringSend, Base.class);
String requestStringReceived = ow.writeValueAsString(received);
System.out.println("Received: " + requestStringReceived);
The String requestStringSend is:
Sending: {
"command" : "myCommand",
"type" : "SubA",
"anotherClass" : {
"data" : "someData"
},
"test" : "test123"
}
But I'm keep getting the same error over and over again. The mapper does now know what to do with the anotherClass parameter - it does not exist in Base. But I thought the mapper will convert it into an SubA class?
Exception in thread "main" org.codehaus.jackson.map.exc.UnrecognizedPropertyException: Unrecognized field "anotherClass" (Class com.test.Base), not marked as ignorable
at [Source: java.io.StringReader#1256ea2; line: 4, column: 21] (through reference chain: com.test.Base["anotherClass"])
at org.codehaus.jackson.map.exc.UnrecognizedPropertyException.from(UnrecognizedPropertyException.java:53)
at org.codehaus.jackson.map.deser.StdDeserializationContext.unknownFieldException(StdDeserializationContext.java:267)
at org.codehaus.jackson.map.deser.std.StdDeserializer.reportUnknownProperty(StdDeserializer.java:649)
at org.codehaus.jackson.map.deser.std.StdDeserializer.handleUnknownProperty(StdDeserializer.java:635)
at org.codehaus.jackson.map.deser.BeanDeserializer.handleUnknownProperty(BeanDeserializer.java:1355)
at org.codehaus.jackson.map.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:717)
at org.codehaus.jackson.map.deser.BeanDeserializer.deserialize(BeanDeserializer.java:580)
at org.codehaus.jackson.map.ObjectMapper._readMapAndClose(ObjectMapper.java:2723)
at org.codehaus.jackson.map.ObjectMapper.readValue(ObjectMapper.java:1854)
at com.test.Foo.main(Foo.java:32)
I had a look at the following questions/resources:
Json deserialization into another class hierarchy using Jackson
http://wiki.fasterxml.com/JacksonPolymorphicDeserialization
Your code looks correct for the use case. One possible problem is that you could be accidentally using Jackson 2 annotations with Jackson 1 ObjectMapper (I can see latter is Jackson from package names in exception). Version of annotations and mapper must match; otherwise annotations will be ignored, and this would explain problems you are seeing.

Cannot with (de)serialize a List with polymorphic items in Jackson

I have an issue (de)serializing JSON that is not defined by me.
Here is some code and JSON that explains the issue:
Code:
public static class Base {
public String klass = "base";
}
public static class SubBase extends Base {
}
public static class Sub1 extends SubBase {
public Sub1() {
klass = "Sub1";
}
}
public static class Sub2 extends SubBase {
public Sub2() {
klass = "Sub2";
}
}
public static class Holder {
#JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.WRAPPER_ARRAY, property="type")
#JsonSubTypes({#JsonSubTypes.Type (name = "sub1", value = Sub1.class),#JsonSubTypes.Type(name = "sub2", value = Sub2.class)})
public List<Base> items = new ArrayList<Base>();
}
Holder holder = new Holder();
holder.items.add(new Sub1());
holder.items.add(new Sub1());
mapper.writeValueAsString(holder);
produces
{"items":[["sub1",{"klass":"Sub1"}],["sub1",{"klass":"Sub1"}]]}
If I change the JsonTypeInfo annotation to
#JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.WRAPPER_OBJECT, property="type")
produces
{"items":[{"sub1":{"klass":"Sub1"}},{"sub1":{"klass":"Sub1"}}]}
So far, all is good :)
However, the JSON I'm getting from the server has a slightly different structure:
{"type":"sub1", "items":[{"klass":"Sub1"},{"klass":"Sub1"}]}
Where the type of the items array is defined in the "type" property (note that all items in the "items" array are of the same class).
I just cannot figure out which JsonTypeInfo combination to use to make this happen. I tried setting the 'include' to ".EXTERNAL_PROPERTY", but this doesn't work.
Deserializing using this inclusion gives me "Exception in thread "main" org.codehaus.jackson.JsonGenerationException: Can not write a field name, expecting a value" error message.
My question is: Which annotation do I need to use so that '{"type":"sub1", "items":[{"klass":"Sub1"},{"klass":"Sub1"}]}' will fill the 'items' array with all Sub1 instances based on the "type" property of the Holder?
If this is just not possible, is there another way to accomplish this (without the need of an custom serializer for Holder; a custom serializer just for the 'items' array would be fine)?
Thanks!
There is no way to map JSON you show automatically; it is not one of 4 types supported by Jackson.
If you can't make server produce more standard structure (for which automatic support exists), you will need to write a custom deserializer; or to do data-binding in two steps, first into an intermediate easily mappable structure (like JsonNode or Map) and then from that into desired structure manually extracting type, using that (for example, with ObjectMapper.convertValue(object, resultType).

JsonMappingException: No suitable constructor found for type [simple type, class ]: can not instantiate from JSON object

I am getting the following error when trying to get a JSON request and process it:
org.codehaus.jackson.map.JsonMappingException: No suitable constructor found for type [simple type, class com.myweb.ApplesDO]: can not instantiate from JSON object (need to add/enable type information?)
Here is the JSON I am trying to send:
{
"applesDO" : [
{
"apple" : "Green Apple"
},
{
"apple" : "Red Apple"
}
]
}
In Controller, I have the following method signature:
#RequestMapping("showApples.do")
public String getApples(#RequestBody final AllApplesDO applesRequest){
// Method Code
}
AllApplesDO is a wrapper of ApplesDO :
public class AllApplesDO {
private List<ApplesDO> applesDO;
public List<ApplesDO> getApplesDO() {
return applesDO;
}
public void setApplesDO(List<ApplesDO> applesDO) {
this.applesDO = applesDO;
}
}
ApplesDO:
public class ApplesDO {
private String apple;
public String getApple() {
return apple;
}
public void setApple(String appl) {
this.apple = apple;
}
public ApplesDO(CustomType custom){
//constructor Code
}
}
I think that Jackson is unable to convert JSON into Java objects for subclasses. Please help with the configuration parameters for Jackson to convert JSON into Java Objects. I am using Spring Framework.
EDIT: Included the major bug that is causing this problem in the above sample class - Please look accepted answer for solution.
So, finally I realized what the problem is. It is not a Jackson configuration issue as I doubted.
Actually the problem was in ApplesDO Class:
public class ApplesDO {
private String apple;
public String getApple() {
return apple;
}
public void setApple(String apple) {
this.apple = apple;
}
public ApplesDO(CustomType custom) {
//constructor Code
}
}
There was a custom constructor defined for the class making it the default constructor. Introducing a dummy constructor has made the error to go away:
public class ApplesDO {
private String apple;
public String getApple() {
return apple;
}
public void setApple(String apple) {
this.apple = apple;
}
public ApplesDO(CustomType custom) {
//constructor Code
}
//Introducing the dummy constructor
public ApplesDO() {
}
}
This happens for these reasons:
your inner class should be defined as static
private static class Condition { //jackson specific
}
It might be that you got no default constructor in your class (UPDATE: This seems not to be the case)
private static class Condition {
private Long id;
public Condition() {
}
// Setters and Getters
}
It could be your Setters are not defined properly or are not visible (e.g. private setter)
I would like to add another solution to this that does not require a dummy constructor. Since dummy constructors are a bit messy and subsequently confusing. We can provide a safe constructor and by annotating the constructor arguments we allow jackson to determine the mapping between constructor parameter and field.
so the following will also work. Note the string inside the annotation must match the field name.
import com.fasterxml.jackson.annotation.JsonProperty;
public class ApplesDO {
private String apple;
public String getApple() {
return apple;
}
public void setApple(String apple) {
this.apple = apple;
}
public ApplesDO(CustomType custom){
//constructor Code
}
public ApplesDO(#JsonProperty("apple")String apple) {
}
}
When I ran into this problem, it was a result of trying to use an inner class to serve as the DO. Construction of the inner class (silently) required an instance of the enclosing class -- which wasn't available to Jackson.
In this case, moving the inner class to its own .java file fixed the problem.
Generally, this error comes because we don’t make default constructor.
But in my case:
The issue was coming only due to I have made used object class inside parent class.
This has wasted my whole day.
Thumb Rule: Add a default constructor for each class you used as a mapping class. You missed this and issue arise!
Simply add default constructor and it should work.
Can you please test this structure. If I remember correct you can use it this way:
{
"applesRequest": {
"applesDO": [
{
"apple": "Green Apple"
},
{
"apple": "Red Apple"
}
]
}
}
Second, please add default constructor to each class it also might help.
You have to create dummy empty constructor in our model class.So while mapping json, it set by setter method.
Regarding the last publication I had the same problem where using Lombok 1.18.* generated the problem.
My solution was to add #NoArgsConstructor (constructor without parameters), since #Data includes by default #RequiredArgsConstructor (Constructor with parameters).
lombok Documentation
https://projectlombok.org/features/all
That would solve the problem:
package example.counter;
import javax.validation.constraints.NotNull;
import lombok.Data;
#Data
#NoArgsConstructor
public class CounterRequest {
#NotNull
private final Integer int1;
#NotNull
private final Integer int2;
}
If you start annotating constructor, you must annotate all fields.
Notice, my Staff.name field is mapped to "ANOTHER_NAME" in JSON string.
String jsonInString="{\"ANOTHER_NAME\":\"John\",\"age\":\"17\"}";
ObjectMapper mapper = new ObjectMapper();
Staff obj = mapper.readValue(jsonInString, Staff.class);
// print to screen
public static class Staff {
public String name;
public Integer age;
public Staff() {
}
//#JsonCreator - don't need this
public Staff(#JsonProperty("ANOTHER_NAME") String n,#JsonProperty("age") Integer a) {
name=n;age=a;
}
}
You must realize what options Jackson has available for deserialization. In Java, method argument names are not present in the compiled code. That's why Jackson can't generally use constructors to create a well-defined object with everything already set.
So, if there is an empty constructor and there are also setters, it uses the empty constructor and setters. If there are no setters, some dark magic (reflections) is used to do it.
If you want to use a constructor with Jackson, you must use the annotations as mentioned by #PiersyP in his answer. You can also use a builder pattern. If you encounter some exceptions, good luck. Error handling in Jackson sucks big time, it's hard to understand that gibberish in error messages.
Add default constructors to all the entity classes
Failing custom jackson Serializers/Deserializers could also be the problem. Though it's not your case, it's worth mentioning.
I faced the same exception and that was the case.
For me, this used to work, but upgrading libraries caused this issue to appear. Problem was having a class like this:
package example.counter;
import javax.validation.constraints.NotNull;
import lombok.Data;
#Data
public class CounterRequest {
#NotNull
private final Integer int1;
#NotNull
private final Integer int2;
}
Using lombok:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.0</version>
</dependency>
Falling back to
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.10</version>
</dependency>
Fixed the issue. Not sure why, but wanted to document it for future.

Categories

Resources