javajsonjacksonflickr

Custom JSON Deserialization with Jackson


I'm using the Flickr API. When calling the flickr.test.login method, the default JSON result is:

{
    "user": {
        "id": "21207597@N07",
        "username": {
            "_content": "jamalfanaian"
        }
    },
    "stat": "ok"
}

I'd like to parse this response into a Java object:

public class FlickrAccount {
    private String id;
    private String username;
    // ... getter & setter ...
}

The JSON properties should be mapped like this:

"user" -> "id" ==> FlickrAccount.id
"user" -> "username" -> "_content" ==> FlickrAccount.username

Unfortunately, I'm not able to find a nice, elegant way to do this using Annotations. My approach so far is, to read the JSON String into a Map<String, Object> and get the values from there.

Map<String, Object> value = new ObjectMapper().readValue(response.getStream(),
        new TypeReference<HashMap<String, Object>>() {
        });
@SuppressWarnings( "unchecked" )
Map<String, Object> user = (Map<String, Object>) value.get("user");
String id = (String) user.get("id");
@SuppressWarnings( "unchecked" )
String username = (String) ((Map<String, Object>) user.get("username")).get("_content");
FlickrAccount account = new FlickrAccount();
account.setId(id);
account.setUsername(username);

But I think, this is the most non-elegant way, ever. Is there any simple way, either using Annotations or a custom Deserializer?

This would be very obvious for me, but of course it doesn't work:

public class FlickrAccount {
    @JsonProperty( "user.id" ) private String id;
    @JsonProperty( "user.username._content" ) private String username;
    // ... getter and setter ...
}

Solution

  • You can write custom deserializer for this class. It could look like this:

    class FlickrAccountJsonDeserializer extends JsonDeserializer<FlickrAccount> {
    
        @Override
        public FlickrAccount deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
            Root root = jp.readValueAs(Root.class);
    
            FlickrAccount account = new FlickrAccount();
            if (root != null && root.user != null) {
                account.setId(root.user.id);
                if (root.user.username != null) {
                    account.setUsername(root.user.username.content);
                }
            }
    
            return account;
        }
    
        private static class Root {
    
            public User user;
            public String stat;
        }
    
        private static class User {
    
            public String id;
            public UserName username;
        }
    
        private static class UserName {
    
            @JsonProperty("_content")
            public String content;
        }
    }
    

    After that, you have to define a deserializer for your class. You can do this as follows:

    @JsonDeserialize(using = FlickrAccountJsonDeserializer.class)
    class FlickrAccount {
        ...
    }