3

I was just wondering, given a pojo:

public class MyProfileDto {
    private List<String> skills;
    //mutators; getSkills; setSkills + bunch of other fields
}

and JSON for the skills field:

"skills":{
 "values":[
  {
     "id":14,
     "skill":{
      "name":"C++"
     }
  },
  {
     "id":15,
     "skill":{
      "name":"Java"
     }
  }
 ],
 "_total":2
}

Is there any way using Jackson to get the skills/values/skill/name field (i.e. "Java", "C++") into the target Dto's String List without creating a custom deserializer for the entire Dto? It has many fields so an ideal solution would involve some custom annotation or deserializer for the one field if possible??

Billy Ross
  • 235
  • 1
  • 3
  • 8
  • This is not possible. Think about it, which of the elements would actually be used as the `String` value in the `List`? – Sotirios Delimanolis Sep 09 '13 at 23:27
  • I was thinking like Xpath or some predicate solution which would could iterate each value and retrieve the 'name' value of it's 'skill' object and collect. In a custom deserializer I would retrieve and iterate over each of the JsonNodes and return as list – Billy Ross Sep 09 '13 at 23:33
  • I believe there are a few `JsonPath` style libraries that do that, but not Jackson, and probably not with a single annotation. – Sotirios Delimanolis Sep 09 '13 at 23:35
  • Just out of curiosity, can one create a custom deserialization on one property? I guess that would solve the problem – Billy Ross Sep 09 '13 at 23:38
  • Yes, you can. Just travel down a path until you find the property you want and add it to the list. – Sotirios Delimanolis Sep 09 '13 at 23:39

1 Answers1

6

Jackson does not contains any XPath feature but you can define converter for each property. This converter will be used by Jackson to convert input type to output type which you need. In your example input type is Map<String, Object> and output type is List<String>. Probably, this is not the simplest and the best solution which we can use but it allows us to define converter for only one property without defining deserializer for entire entity.

Your POJO class:

class MyProfileDto {

    @JsonDeserialize(converter = SkillConverter.class)
    private List<String> skills;

    public List<String> getSkills() {
        return skills;
    }

    public void setSkills(List<String> skills) {
        this.skills = skills;
    }
}

Converter for List<String> skills; property:

class SkillConverter implements Converter<Map<String, Object>, List<String>> {

    @SuppressWarnings("unchecked")
    public List<String> convert(Map<String, Object> value) {
        Object values = value.get("values");
        if (values == null || !(values instanceof List)) {
            return Collections.emptyList();
        }

        List<String> result = new ArrayList<String>();
        for (Object item : (List<Object>) values) {
            Map<String, Object> mapItem = (Map<String, Object>) item;
            Map<String, Object> skillMap = (Map<String, Object>) mapItem.get("skill");
            if (skillMap == null) {
                continue;
            }

            result.add(skillMap.get("name").toString());
        }

        return result;
    }

    public JavaType getInputType(TypeFactory typeFactory) {
        return typeFactory.constructMapLikeType(Map.class, String.class, Object.class);
    }

    public JavaType getOutputType(TypeFactory typeFactory) {
        return typeFactory.constructCollectionLikeType(List.class, String.class);
    }
}

And example usage:

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.fasterxml.jackson.databind.util.Converter;

public class JacksonProgram {

    public static void main(String[] args) throws IOException {
        ObjectMapper mapper = new ObjectMapper();

        MyProfileDto dto = mapper.readValue(new File("/x/json"), MyProfileDto.class);

        System.out.println(dto.getSkills());
    }
}

Above program prints:

[C++, Java]
Michał Ziober
  • 37,175
  • 18
  • 99
  • 146