Prasanth
Prasanth

Reputation: 1014

Customize jackson unmarshalling behavior

I am using Jackson fasterxml for unmarshalling JSON. In my object there are two kinds of properties:Input properties and Calculated properties. In the input JSON, I get only input values.

The calculated values are actually dependent on input values. I have to populate these values before the object gets referred. So I am just checking if there are any hooks provided by Jackson so that I can do my calculations there. For example JAXB provides afterUnmarshal method to customize the unmarshaling behavior:

void afterUnmarshal(Unmarshaller u, Object parent)

But I could not find similar information about customizing Jackson. Are any such framework hooks provided by Jackson to customize the unmarshaling behavior?

Upvotes: 3

Views: 4523

Answers (2)

Michał Ziober
Michał Ziober

Reputation: 38655

Let's assume that your JSON looks like this:

{
    "input1" : "Input value",
    "input2" : 3
}

And your POJO class looks like this:

class Entity {

    private String input1;
    private int input2;
    private String calculated1;
    private long calculated2;

    ...
}

In this case you can write a custom deserializer for your Entity class:

class EntityJsonDeserializer extends JsonDeserializer<Entity> {

    @Override
    public Entity deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException,
            JsonProcessingException {
        InnerEntity innerEntity = jp.readValueAs(InnerEntity.class);

        Entity entity = new Entity();
        entity.setInput1(innerEntity.input1);
        entity.setInput2(innerEntity.input2);
        entity.recalculate();

        return entity;
    }

    public static class InnerEntity {
        public String input1;
        public int input2;
    }
}

In above class you can see that Entity has a recalculate method. It could look like this:

public void recalculate() {
    calculated1 = input1 + input2;
    calculated2 = input1.length() + input2;
}

You can also move this logic to your deserializer class.

Now, you have to inform Jackson that you want to use your custom deserializer:

@JsonDeserialize(using = EntityJsonDeserializer.class)
class Entity {
...
}

The example below shows how to use these classes:

ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.readValue(json, Entity.class));

This program prints:

Entity [input1=Input value, input2=3, calculated1=Input value3, calculated2=14]

Upvotes: 2

Alexey Gavrilov
Alexey Gavrilov

Reputation: 10853

I'd rather recommend to keep your model objects immutable by using constructor creators. That is, all the JSON values are passed to a constructor which would initialize the other calculated properties.

Anyway, if you want to customize an object after deserialization (without writing a deserializer for every type) you can modify the deserializer in a way that at the end it calls a special method(s) of a newly constructed instance. Here is an example which would work for all the classes that implements a special interface (one can consider using an annotation to mark the post construct methods).

public class JacksonPostConstruct {

    public static interface PostConstructor {
        void postConstruct();
    }

    public static class Bean implements PostConstructor {
        private final String field;

        @JsonCreator
        public Bean(@JsonProperty("field") String field) {
            this.field = field;
        }

        public void postConstruct() {
            System.out.println("Post construct: " + toString());
        }

        @Override
        public String toString() {
            return "Bean{" +
                    "field='" + field + '\'' +
                    '}';
        }
    }

    private static class PostConstructDeserializer extends DelegatingDeserializer {
        private final JsonDeserializer<?> deserializer;

        public PostConstructDeserializer(JsonDeserializer<?> deserializer) {
            super(deserializer);
            this.deserializer = deserializer;
        }

        @Override
        protected JsonDeserializer<?> newDelegatingInstance(JsonDeserializer<?> newDelegatee) {
            return deserializer;
        }

        @Override
        public Object deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
            Object result = _delegatee.deserialize(jp, ctxt);
            if (result instanceof PostConstructor) {
                ((PostConstructor) result).postConstruct();
            }
            return result;
        }
    }

    public static void main(String[] args) throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        SimpleModule module = new SimpleModule();
        module.setDeserializerModifier(new BeanDeserializerModifier() {
            @Override
            public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config,
                                                          BeanDescription beanDesc,
                                                          final JsonDeserializer<?> deserializer) {
               return new PostConstructDeserializer(deserializer);
            }
        });
        mapper.registerModule(module);
        String json = "{\"field\":\"value\"}";
        System.out.println(mapper.readValue(json, Bean.class));
    }

}

Output:

Post construct: Bean{field='value'}
Bean{field='value'}

Upvotes: 5

Related Questions