Jackson, deserialize property based on another property (dependent property)

Viewed 567

Using Jackson, is there a way to deserialize a proprty that depends on the value of another property?

if i have this json {"foo":"a","bar":"b"} i'd like to deserialize it to the Test class below as Test [foo=a, bar=b_a], where bar is the value of the json property "bar" and the value of the property "foo".

Of course this is a trivial example, the real deal would be to deserialize a datamodel entity: {"line":"C12", "machine": {"line":"C12", "code":"A"}} machine.line and line are always the same, and i'd like to express it like this: {"line":"C12", "machine": "A"}

import java.io.IOException;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;

public abstract class Main{

    private static class Test {
        @JsonProperty
        private String foo;
        @JsonProperty
        @JsonDeserialize(using = CustomDeserializer.class)
        private String bar;

        // ...other fields to be deserialized with default behaviour

        private Test() {
        }

        public Test(String a, String bar) {
            this.foo = a;
            this.bar = bar;
        }

        @Override
        public String toString() {
            return "Test [foo=" + foo + ", bar=" + bar + "]";
        }
    }

    private static class CustomDeserializer extends StdDeserializer<String> {

        protected CustomDeserializer() {
            super(String.class);
        }

        @Override
        public String deserialize(JsonParser p, DeserializationContext ctxt)
                throws IOException, JsonProcessingException {
            String foo = //how to get foo property?
            String value = p.getValueAsString();
            if (!foo.isEmpty()) {
                return value + "_" + foo;
            } else {
                return value;
            }
        }

    }

    public static void main(String[] args) throws IOException {

        ObjectMapper mapper = new ObjectMapper();

        Test foo2 = mapper.readValue("{\"foo\":\"a\",\"bar\":\"b\"}", Test.class);
        System.out.println(foo2); // Test [foo=a, bar=b_a]

    }

}
1 Answers

One way to solve your problem is specify a custom deserializer that involves your Test class instead of your string field because the deserialization of your property is based on the value of another property:

public class CustomDeserializer extends JsonDeserializer<Test> {}

@JsonDeserialize(using = CustomDeserializer.class)
public class Test {}

Then you can deserialize your object reading the JsonNode tree built from your input string:

public class CustomDeserializer extends JsonDeserializer<Test> {

    @Override
    public Test deserialize(JsonParser jp, DeserializationContext dc) throws IOException, JsonProcessingException {
        JsonNode node = jp.getCodec().readTree(jp);
        String foo = node.get("foo").asText();
        String bar = node.get("bar").asText();
        if (!foo.isEmpty()) {
            bar = (bar + '_' + foo);
        }
        return new Test(foo, bar);
    }
    
}

//your example
public class Main {
    public static void main(String[] args) throws JsonProcessingException {
        ObjectMapper mapper = new ObjectMapper();
        Test foo2 = mapper.readValue("{\"foo\":\"a\",\"bar\":\"b\"}", Test.class);
        System.out.println(foo2); // Test [foo=a, bar=b_a]
    }
}

Related