Jackson deserialization: Can I inject a value with an annotation on the field of the to deserializable object?



I have an object like this to deserialize:

public class RelationsInput {

   Relation relation1;

   Relation relation2;

}

whereas the class Relation looks like this:

public class Relation {

   RelationType relationtype;
   ... (more fields)

}

RelationType is en enum and is not a value which will be deserialized, while all others are.

Is it possible, that I could “inject” the enum value for the field relationType with an annotation on the field in the class RelationInput? Like the following

public class RelationsInput {

   @RelationType(RelationType.OWNER)
   Relation owner;

   @RelationType(RelationType.TENANT)
   Relation tenant;

}

Does Jackson provide something like this?

Answer

You can try to implement custom deserialiser with com.fasterxml.jackson.databind.deser.ContextualDeserializer interface. It allows to create deserialiser instance with a context.

See below example:

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.json.JsonMapper;
import lombok.Data;

import java.io.File;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

public class JsonContextualDeserializerApp {
    public static void main(String[] args) throws IOException {
        File jsonFile = new File("./resource/test.json").getAbsoluteFile();

        ObjectMapper mapper = JsonMapper.builder().build();
        RelationsInput info = mapper.readValue(jsonFile, RelationsInput.class);

        System.out.println(info.toString());
    }
}

@Data
class RelationsInput {

    @JsonDeserialize(using = RelationStdDeserializer.class)
    @RelationTypeInfo(RelationType.OWNER)
    private Relation owner;

    @JsonDeserialize(using = RelationStdDeserializer.class)
    @RelationTypeInfo(RelationType.TENANT)
    private Relation tenant;

}

@Data
class Relation {

    private int id;
    private RelationType relationtype;
}

enum RelationType {OWNER, TENANT}

@Retention(RetentionPolicy.RUNTIME)
@interface RelationTypeInfo {
    RelationType value();
}

class RelationStdDeserializer extends StdDeserializer<Relation> implements ContextualDeserializer {

    private RelationType propertyRelationType;

    public RelationStdDeserializer() {
        this(null);
    }

    public RelationStdDeserializer(RelationType relationType) {
        super(Relation.class);
        this.propertyRelationType = relationType;
    }

    @Override
    public Relation deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        JsonDeserializer<Object> deser = ctxt.findRootValueDeserializer(ctxt.getTypeFactory().constructType(Relation.class));
        Relation instance = (Relation) deser.deserialize(p, ctxt);
        if (this.propertyRelationType != null) {
            instance.setRelationtype(this.propertyRelationType);
        }
        return instance;
    }

    @Override
    public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) {
        RelationTypeInfo typeInfo = property.getMember().getAllAnnotations().get(RelationTypeInfo.class);

        return new RelationStdDeserializer(typeInfo.value());
    }
}

Above code for a payload:

{
  "owner": {
    "id": 1
  },
  "tenant": {
    "id": 2
  }
}

prints:

RelationsInput(owner=Relation(id=1, relationtype=OWNER), tenant=Relation(id=2, relationtype=TENANT))

See also:



Source: stackoverflow