Search code examples
javajacksondeserializationjson-deserializationjackson2

Deserializing attributes of same name but different types in Jackson?


I have a REST API which returns a JSON response as:

{
    "channel" : "JHBHS"
}

and sometimes it returns:

{
    "channel": {
                    "id": 12321,
                    "name": "Some channel"
               }
}

I have a POJO like:

public class Event {
    private String channel;
    @JsonProperty("channel")
    private Channel channelObj;
}

public class Channel {
    private int id;
    private String name;
}

So, is there a way (other than writing your own custom deserializer) in Jackson2 which will help me map channel in JSON to String type when it's a String and Channel type when it's a JSON Object?

Or in other words, is there a way in Jackson which maps by type of the variable and not just by name?


Solution

  • I can suggest you to use JsonNode like this:

    class Event {
    
        @JsonProperty("channel")
        private JsonNode channelInternal;
    
        private Channel channel;
    
        private String channelStr;
    
        /**
         * Custom getter with channel parsing
         * @return channel
         */
        public Channel getChannel() {
            if (channel == null && channelInternal != null) {
                if (channelInternal.isObject()) {
                    int id = channelInternal.get("id").intValue();
                    String name = channelInternal.get("name").asText();
                    channel = new Channel(id, name);
                }
            }
            return channel;
        }
    
        /**
         * Custom getter with channel string parsing
         * @return channel string
         */
        public String getChannelStr() {
            if (channelStr == null && channelInternal != null) {
                if (channelInternal.isTextual()) {
                    channelStr = channelInternal.asText();
                }
            }
            return channelStr;
        }
    }
    

    or like this:

    class Event {
    
        private Channel channel;
    
        private String channelStr;
    
        @JsonSetter("channel")
        public void setChannelInternal(JsonNode channelInternal) {
            if (channelInternal != null) {
                if (channelInternal.isTextual()) {
                    channelStr = channelInternal.asText();
                } else if (channelInternal.isObject()) {
                    int id = channelInternal.get("id").intValue();
                    String name = channelInternal.get("name").asText();
                    channel = new Channel(id, name);
                }
            }
        }
    
        public Channel getChannel() {
            return channel;
        }
    
        public String getChannelStr() {
            return channelStr;
        }
    }
    

    But I think using custom deserializer is more common.

    Here is test code

    public static void main(String[] args) throws IOException {
        ObjectMapper objectMapper = new ObjectMapper();
        String source1 = "{\n" +
                "    \"channel\" : \"JHBHS\"\n" +
                "}";
        String source2 = "{\n" +
                "    \"channel\": {\n" +
                "                    \"id\": 12321,\n" +
                "                    \"name\": \"Some channel\"\n" +
                "               }\n" +
                "}";
    
        //Test object parsing
        Event result = objectMapper.readValue(source2, Event.class);
        System.out.println(String.format("%s : %s", result.getChannel().getId(), result.getChannel().getName()));
    
        //Test string parsing
        result = objectMapper.readValue(source1, Event.class);
        System.out.println(result.getChannelStr());
    }
    

    And the output:

    12321 : Some channel
    JHBHS