Search code examples
javajsonjacksondeserializationjson-deserialization

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?


Solution

  • 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: