I am trying to deserialize JSON into a custom POJO that I am not able to modify. That POJO has annotations from a different custom internal serialization framework that I'm not able to use. How can I create a custom deserializer that will respect these annotations?
Here is an example POJO:
public class ExampleClass {
@Property(name = "id")
public String id;
@Property(name = "time_windows")
@NotNull
public List<TimeWindow> timeWindows = new ArrayList<>();
public static class TimeWindow {
@Property(name = "start")
public Long start;
@Property(name = "end")
public Long end;
}
}
So in this case, the deserializer would look for fields in the JSON that correspond to the Property
annotations, and use the value in that annotation to decide what field to grab. If a property doesn't have the Property
annotation, it should be ignored.
I have been going through the Jackson docs but haven't been able to find exactly what I need. Is this a place where an AnnotationIntrospector
would be useful? Or possibly a ContextualDeserializer
?
Any pointers in the right direction would be greatly appreciated!
Update: I tried implementing the advice in the comments, but without success.
Here is my initial implementation of the introspector:
class CustomAnnotationInspector : JacksonAnnotationIntrospector () {
override fun hasIgnoreMarker(m: AnnotatedMember?): Boolean {
val property = m?.getAnnotation(Property::class.java)
return property == null
}
override fun findNameForDeserialization(a: Annotated?): PropertyName {
val property = a?.getAnnotation(Property::class.java)
return if (property == null) {
super.findNameForDeserialization(a)
} else {
PropertyName(property.name)
}
}
}
And here is where I actually use it:
// Create an empty instance of the request object.
val paramInstance = nonPathParams?.type?.getDeclaredConstructor()?.newInstance()
// Create new object mapper that will write values from
// JSON into the empty object.
val mapper = ObjectMapper()
// Tells the mapper to respect custom annotations.
mapper.setAnnotationIntrospector(CustomAnnotationInspector())
// Write the contents of the request body into the StringWriter
// (this is required for the mapper.writeValue method
val sw = StringWriter()
sw.write(context.bodyAsString)
// Deserialize the contents of the StringWriter
// into the empty POJO.
mapper.writeValue(sw, paramInstance)
Unfortunately it seems that findNameForDeserialization
is never called, and none of the JSON values are written into paramInstance
. Can anybody spot where I'm going wrong?
Thank you!
Update 2: I changed the code slightly, I'm now able to identify the property names but Jackson is failing to create an instance of the object.
Here's my new code:
val mapper = ObjectMapper()
// Tells the mapper to respect CoreNg annotations.
val introspector = CustomAnnotationInspector()
mapper.setAnnotationIntrospector(introspector)
val paramInstance = mapper.readValue(context.bodyAsString,nonPathParams?.type)
My breakpoints in the custom annotation introspector are getting hit. But I'm getting the following exception:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `app.employee.api.employee.BOUpsertEmployeeRequest` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
Here is the POJO I'm trying to deserialize:
public class BOUpsertEmployeeRequest {
public BOUpsertEmployeeRequest () { }
@NotNull
@Property(name = "xref_code")
public String xrefCode;
@Property(name = "first_name")
public String firstName;
@Property(name = "last_name")
public String lastName;
@Property(name = "email_address")
public String emailAddress;
@Property(name = "phone")
public String phone;
@Property(name = "address")
public List<String> address;
@Property(name = "employment_status")
public String employmentStatus;
@Property(name = "pay_type")
public String payType;
@Property(name = "position")
public String position;
@Property(name = "skills")
public List<String> skills;
@Property(name = "gender")
public String gender;
}
As far as I can tell it has a default constructor. Anybody have any idea what the problem is?
Thank you!