Reputation: 13
I am trying to deserialize an Object from a Map(String, Object) via FasterJackson's ObjectMapper.convertValue(map, ComplexRecord.class) method.
My destination object contains many other objects within it. The only problematic object is the embedded "AnyObject" object that uses FasterJackson's (Jackson's) AnyGetter/AnySetter methods.
The AnyObject instance works for every other use case with FasterJackson except, at the moment, for this one where it involves a more "ComplexRecord" deserialization.
This is what it looks like:
@Data
public class ComplexRecord {
private String id;
private AnyObject data;
private String status;
private Instant createdDateTime;
}
@Data
public class AnyObject {
@Getter(AccessLevel.NONE)
@Setter(AccessLevel.NONE)
private final Map<String, Object> data;
public AnyObject() {
this(new LinkedHashMap<>());
}
public AnyObject(Map<String, Object> sourceData) {
this.data = new LinkedHashMap<>(sourceData);
}
@JsonAnySetter
public void setData(String name, Object value) {
this.data.put(name, value);
}
@JsonAnyGetter
public Map<String, Object> getData() {
return Collections.unmodifiableMap(this.data);
}
}
When I try to use ObjectMapper.convertValue(map, ComplexRecord.class) it fails to deserialize the "data" field due to this error:
Cannot construct instance of `AnyObject`
(although at least one Creator exists): no String-argument
constructor/factory method to deserialize from String value
('{"id":"123","v":"anything"}')
at [Source: UNKNOWN; line: -1, column: -1]
I'd like to find a very clean workaround for this. This issue seems to stem from the fact that I am using the ObjectMapper.convertValue method from a complex source of Map where the key "data" is originally available as a String. If I perform a similar operation but with ObjectMapper.readValue() instead of ObjectMapper.convertValue() then the deserialization into AnyObject works just fine.
Since I do not have the luxury of changing the source object of Map into something that would work with ObjectMapper.readValue() method I may be left with only a few choices.
One option I found is using FasterJackson's Custom Deserializer. The only catch I see is that there is no clear way to access the internal ObjectMapper that is provided to the Deserializer. When I debug the deserializer, I do see that JsonParser.getCodec() is the ObjectMapper, but even when trying to do a readValue within the the custom Deserializer the deserialization fails with the same error. i.e.
AnyObject value = jsonParser.getCodec().readValue(p, AnyObject.class);
However following set of calls work just fine:
String stringValue = jsonParser.getCodec().readValue(p, String.class);
AnyObject anyObject = objMapper.readValue(stringValue, AnyObject.class);
Other than this being a 2 step process to deserialize, rather than a 1 step process; the only other issue is that I do not have a clean way to use the "objMapper" (ObjectMapper) instance I am referring to above without casting the codec into an ObjectMapper instance.
This seems like a hack to me but I'd like to get your thoughts and also see if there any other friendlier solution available.
There are a few more thoughts / options but I'd like to get an unbiased opinion on this to see if there is a much simpler way of handling this type of "complex" conversion.
The most ideal outcome is to force coercion of the String into my desired type -- AnyObject. Via annotation or some very simple strategy.
But if I do have to deal with this via a Custom Deserializer, my preference would be to be able to get the handle to the in-process ObjectMapper in a clean way.
Thoughts?
Upvotes: 0
Views: 464
Reputation: 13
Tried a few things and landed on the following by implementing a custom FasterJackson deserializer:
JsonNode node = jsonParser.readValueAsTree();
AnyObject val = jsonParser.getCodec().getFactory().createParser(node.asText().getBytes()).readValueAs(AnyObject.class);
I am still open to solutions that may be simpler or lean towards better performance with less intermediate garbage generation during parsing.
Upvotes: 0