3

I'm using Spring RestTemplate and Jackson 2.1 and I'm trying to deserialise the JSON string below. Whereas the first number is an unique ID so this will be dynamic:

{"2127388":{"name":"John","city":"Amsterdam","country":"The Netherlands"}}

With RestTemplate I do the following:

final ResponseEntity<UserDetailsWrapper> re = restTemplate.getForEntity(apiUrl, UserDetailsWrapper.class);

The POJO's I have are

class UserDetailsWrapper {
    private long uniqueId; // [getter + setter]
    private UserDetails userDetails; // [getter + setter]

    // no args constructor + all properties constructor
}

class UserDetails {
    private String name; // [getter + setter]
    private String city; // [getter + setter]
    private String country; // [getter + setter]

    // no args constructor + all properties constructor
}

The UserDetailsWrapper class gets instantiated but all its properties remain null.

When I simply do:

{"name":"John","city":"Amsterdam","country":"The Netherlands"}

I am able to deserialise to the UserDetails class with all properties filled as expected, so my configuration should be in order. Probably I need to have the UserDetailsWrapper class annotated at specific places or I need to have a custom deserialiser. I tried both but to be honest I have no clue what to do precisely.

If someone can help me out with this I will be a happy man again.

ZT827
  • 161
  • 1
  • 2
  • 12

3 Answers3

3

From the class name, you really don't want a Wrapper, you want a Map, something like in this answer: https://stackoverflow.com/a/18014407/785663

Instead of the String they use there, use Long (untested):

TypeFactory typeFactory = mapper.getTypeFactory();
MapType mapType = typeFactory.constructMapType(HashMap.class, Long.class, UserDetails.class);
HashMap<Long, UserDetails> map = mapper.readValue(json, mapType);

Digging into the code this exactly what RestTemplate does internally by calling TypeFactory.constructType with your provided type, unfortunately losing your generics on the way:

restTemplate.getForEntity("apiUrl", Map.class);

You're not alone in noticing the lack of generics support and the linked answers points to what you've already found:

restTemplate.exchange("apiUrl", 
        HttpMethod.GET,
        null,
        new ParameterizedTypeReference<Map<Long, UserDetails>>(){});
Community
  • 1
  • 1
mabi
  • 5,279
  • 2
  • 43
  • 78
1

I've basically solved it by using a HashMap and then get the first entry of the map.

final ParameterizedTypeReference<Map<Long, UserDetails>> ptr =
            new ParameterizedTypeReference<Map<Long, UserDetails>>(){};

final ResponseEntity<Map<Long, UserDetails>> re =
    restTemplate.exchange("apiUrl", HttpMethod.GET, null, ptr);

Because RestTemplate::getForEntity() doesn't support adding a type reference I had to use RestTemplate::exchange()

ZT827
  • 161
  • 1
  • 2
  • 12
0

Payload reflecting your POJO structure would be:

{"uniqueId":"2127388", "userDetails":{"name":"John","city":"Amsterdam","country":"The Netherlands"}}

I don't think it's a good idea to have variable JSON property name (in your case 2127388). Just change the payload structure and use your POJOs as they are.

Reaction on comment:

I would personally reconsider using such API, as it's obviously pretty bad API. According JSON spec, name of the property should be string, whereas here it is obviously semantically meant as number.

If you are forced to use it, just use Map<String, UserDetail> as your wrapper. It will obviously have just one element anyway.

luboskrnac
  • 23,973
  • 10
  • 81
  • 92