Mich Betancourt
Mich Betancourt

Reputation: 13

FasterJackson - fails to convert a key (String of JSON) within a Map into an Object that is using AnyGetter/AnySetter annotations

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

Answers (1)

Mich Betancourt
Mich Betancourt

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

Related Questions