1

I am trying to receive a JSON object in @RequestBody which is not known to me i.e. the JSON Object could be of any length and data.

Let's say, JSON could be as shown below

{'seqNo': 10 }

{'country': 'US', 'state': 'CA'}

{'customer': 'Alex', product: 'UPS', date:'25-Mar-2018'}

And In Spring Boot Api, I have a method to receive that JSON Object.

@PostMapping(value = "/lookup")
public ResponseEntity<AppResponse> getLookup(@RequestBody LookupRequestObject lookupRequestObject) {

        // THIS METHOD KNOWS WHICH FIELD TO USE
        // FURTHER LOGIC WOULD BE HERE.


        return ResponseEntity.ok(response);

    }

I have read about Jackson Serialization but still finding solution for this.

Customize the Jackson ObjectMapper

Any help would be much appreciated.

TAB
  • 1,944
  • 8
  • 28
  • 45
  • You may accept the request body as a `String` , then use Jackson by yourself to deserialize the string to the relevant class. – Arnaud Apr 02 '19 at 06:52
  • @Arnaud, the relevant class means each JSON (shown above) has relevant class. – TAB Apr 02 '19 at 06:55

3 Answers3

6

You could just use a map for your input. Then you can access filed in the map depending on what kind of fields it contains.

@PostMapping(value = "/lookup")
public ResponseEntity<AppResponse> getLookup(@RequestBody Map<String, Object> lookupRequestObject) {

    // THIS METHOD KNOWS WHICH FIELD TO USE
    // FURTHER LOGIC WOULD BE HERE.

    return ResponseEntity.ok(response);
}
Patrick Adler
  • 196
  • 2
  • 9
4

If JSON object structure is not known, you can always use Map<String, Object> type and convert it to POJO or directly work on Map. In case you need POJO you can use convertValue method:

@PostMapping(value = "/lookup")
public ResponseEntity<AppResponse> getLookup(@RequestBody Map<String, Object> payload) {
    // read map
    ObjectMapper objectMapper = new ObjectMapper();
    if (payload.containsKey("seqNo")) {
        Sequence seq = objectMapper.convertValue(payload, Sequence.class);
        // other logic
    } else if (payload.containsKey("country")) {
        Country country = objectMapper.convertValue(payload, Country.class);
    }
    // the same for other types

    // FURTHER LOGIC WOULD BE HERE.
    return ResponseEntity.ok(response);
}

You can also try with deserialising to com.fasterxml.jackson.databind.JsonNode but it binds controller with Jackson which is not good from other side.

Michał Ziober
  • 37,175
  • 18
  • 99
  • 146
  • Map, I need to type cast the Object while using. Could it be costly? – TAB Apr 02 '19 at 07:10
  • 1
    @TAB, casting is not costly. In `Java` generics are replaced with casting everywhere and it works fine. So, using map in that way `Integer seq = (Integer)payload.get("seqNo")` is fine. – Michał Ziober Apr 02 '19 at 07:56
1

The answer by @Patrick Adler is absolutely correct. You can use Map as your parameter of the method. Two important additions: Map corresponds to JSON Object so when a JSON object is passed to your method Spring (using Jackson by default) will convert it to map, so no additional code needed. Also to be sure you can add to your annotation that you expect to receive JSON input: So change the line

@PostMapping(value = "/lookup")

to

@PostMapping(value = "/lookup", headers = "Accept=application/json")

And finally the input that you posted is not a valid single JSON Object. It is 3 separate JSON Objects. So either you expect a JSON array containing JSON Objects or a Single JSON Object. If you expect a JSON Array then instead of Map<String, Object> parameter in your method use List<Map<String, Object>> so your solution should look either

@PostMapping(value = "/lookup", headers = "Accept=application/json")
public ResponseEntity<AppResponse> getLookup(@RequestBody Map<String, Object> lookupRequestObject) {

    // THIS METHOD KNOWS WHICH FIELD TO USE
    // FURTHER LOGIC WOULD BE HERE.

    return ResponseEntity.ok(response);
}

or the same but with List<Map<String, Object>> param instead of just map
Michael Gantman
  • 7,315
  • 2
  • 19
  • 36
  • A little bit safer is `List` instead of `List>` because in case of array of primitives `[1,2,3]` it will work by default. – Michał Ziober Apr 02 '19 at 07:58
  • Yes, I have a bit complicated JSON like, { 'seqNo': 10, 'sort': { 'field': 'ctry', 'desc': 0 } 'filter': { 'field': 'region', 'operator': 'startsWith', 'value': 'europe' } page: 0, size: 20, userData: { ... } } – TAB Apr 02 '19 at 09:36
  • 1
    @TAB, your JSON is fine in this case your parameter still is Map. it will work just fine. BTW if the solution works for you mark it as an accepted answer :) – Michael Gantman Apr 02 '19 at 09:55
  • 1
    MichaelGantman, Yes I am working and now I have almost done my work. Specially Thanks for MichaelGantman, @MichałZiober and PatrickAdler for your quick responses. Every answer help me but I have to mark just one as answer so... :) – TAB Apr 02 '19 at 10:12