Search code examples
javajsonjacksonjson-deserializationfasterxml

Deserialize flat JSON to complex POJO


I use fasterxml in 2.6.4 and getting the following JSON by an external service on which I have no influence on the given output:

{
     "name": "dunnosName",
     "widthValue": 46.1,
     "heightValue": 56.1,
     "depthValue": 66.1,
     "unit": "mm"
}

and want to map it to the following POJOs:

public class Dunno {
    private String name;
    private ValueWithUnit width;
    private ValueWithUnit height;
    private ValueWithUnit depth;
}

public class ValueWithUnit {
    private Float value;
    private String unit;
}

My excepted mapping should look something like this:

name -> Dunno.name

widthValue -> Dunno.width.value

heightValue -> Dunno.height.value

depthValue -> Dunno.depth.value

unit -> Dunno.width.unit

unit -> Dunno.height.unit

unit -> Dunno.depth.unit

Is it possible to realize the expected mapping using fasterxml? And if so which fasterxml annotations or classes do I have to implement to realize this mapping?


Solution

  • You don't need a transitional TempDunno. You need a Custom Deserializer. This is a textbook example where you would use one. Add the following annotation to Dunno class:

    @JsonDeserialize(using = DunnoDeserializer.class)
    

    and here it is, and with input validation as well:

    @SuppressWarnings("serial")
    public class DunnoDeserializer extends StdDeserializer<Dunno>
    {
        public DunnoDeserializer()
        {
            this(null);
        }
    
        public DunnoDeserializer(Class<?> vc)
        {
            super(vc);
        }
    
        @Override
        public Dunno deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException
        {
            Dunno dunno = new Dunno();
            // first parse the input into a map, which is more convenient to work with
            @SuppressWarnings("unchecked")
            Map<String, Object> values = jp.getCodec().readValue(jp, Map.class);
            dunno.name = values.containsKey("name") ? values.get("name").toString() : "empty";
            String unit = values.containsKey("unit") ? values.get("unit").toString() : "default-units";
            if (values.containsKey("widthValue")) {
                dunno.width = new ValueWithUnit();
                dunno.width.value = ((Number)values.get("widthValue")).floatValue();
                dunno.width.unit = unit;
            }
            if (values.containsKey("heightValue")) {
                dunno.height = new ValueWithUnit();
                dunno.height.value = ((Number)values.get("heightValue")).floatValue();
                dunno.height.unit = unit;
            }
            if (values.containsKey("depthValue")) {
                dunno.depth = new ValueWithUnit();
                dunno.depth.value = ((Number)values.get("depthValue")).floatValue();
                dunno.depth.unit = unit;
            }
            System.out.println(values);
            values.values().forEach(v -> System.out.println(v.getClass()));
            return dunno;
        }
    }
    

    test method:

    public static void main(String[] args)
    {
        String jsonString = "{ \"name\": \"dunnosName\"," + "\"widthValue\": 46.1," + "\"heightValue\": 56.1,"
                + "\"depthValue\": 66.1," + "\"unit\": \"mm\"}";
    
        ObjectMapper mapper = new ObjectMapper();
        try {
            Dunno d = (Dunno)mapper.readValue(jsonString, Dunno.class);
            System.out.format("%s: %.2f(%s) %.2f(%s) %.2f(%s)", 
                    d.name, d.width.value, d.width.unit, d.height.value, d.height.unit, d.depth.value, d.depth.unit);
    
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    

    which gives expected output:

    dunnosName: 46.10(mm) 56.10(mm) 66.10(mm)