How to get Spring controller to accept multiple ty

2019-07-27 01:47发布

问题:

I have a Spring REST service (written in Groovy; although that shouldn't matter) that exposes a secured endpoint like so:

class AppData {
    String id
    String payload
    String fizzbuzz
}

@RequestMapping(method=RequestMethod.POST)
@ResponseStatus(value = HttpStatus.OK)
@ResponseBody AppResponse onAppData(@RequestBody AppData appData) {
    // Does some processing on appData and then saves
    // its 'payload' field to a MongoDB
}

And this works perfectly fine when it receives JSON of the following form:

{
    "id" : "12345",
    "payload" : "Please save me to a MongoDB",
    "fizzbuzz" : "wakka wakka"
}

I'm using Jackson to handle JSON serialization here.

The problem is that I now want the payload property to be true JSON, not a String. So I want to be able to send the endpoint something like this:

{
    "id" : "12345",
    "payload" : {
        "foo" : 24,
        "bar" : false,
        "whistlefeather" : "Yes of course"
    },
    "fizzbuzz" : "wakka wakka"
}

And have the payload properly saved off to MongoDB. But here's the catch:

Lots of different teams (potentially hundreds) are going to be sending this endpoint AppData, and all of their payloads are going to look completely different (each team has different payloads they want to send me and have saved in MongoDB). And I don't want to have to write a new endpoint and AppData subclass for each new team I onboard (and thus expose this endpoint to).

Is there any way I can:

  • Keep my endpoint exactly the way it is; but
  • Allow the AppData#payload property to accept JSON in any form?

Keeping my code exactly the way it is, if I send a JSON message where the payload is also JSON (and isn't a string), Spring just returns a 400 with an empty response body. This is happening because Spring expects payload to be a String, but sees that its a JSON object.

Any ideas on what I can do inside the AppData class to handle this, or any notions of what the best practice is here?

回答1:

Use a JsonNode as the type for the payload:

public class AppData {
    String id;
    JsonNode payload;
    String fizzbuzz;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public JsonNode getPayload() {
        return payload;
    }

    public void setPayload(JsonNode payload) {
        this.payload = payload;
    }

    public String getFizzbuzz() {
        return fizzbuzz;
    }

    public void setFizzbuzz(String fizzbuzz) {
        this.fizzbuzz = fizzbuzz;
    }

    public static void main(String[] args) throws IOException {
        String json = "{\n" +
            "    \"id\" : \"12345\",\n" +
            "    \"payload\" : {\n" +
            "        \"foo\" : 24,\n" +
            "        \"bar\" : false,\n" +
            "        \"whistlefeather\" : \"Yes of course\"\n" +
            "    },\n" +
            "    \"fizzbuzz\" : \"wakka wakka\"\n" +
            "}";

        ObjectMapper om = new ObjectMapper();
        AppData appData = om.readValue(json, AppData.class);
        System.out.println("appData ID = " + appData.getId());
        System.out.println("appData fizzbuzz = " + appData.getFizzbuzz());
        System.out.println("appData payload = " + appData.getPayload());

        String json2 = "{\n" +
            "    \"id\" : \"12345\",\n" +
            "    \"payload\" : \"Please save me to a MongoDB\",\n" +
            "    \"fizzbuzz\" : \"wakka wakka\"\n" +
            "}";
        appData = om.readValue(json2, AppData.class);
        System.out.println("appData ID = " + appData.getId());
        System.out.println("appData fizzbuzz = " + appData.getFizzbuzz());
        System.out.println("appData payload = " + appData.getPayload());

    }
}

Output:

appData ID = 12345
appData fizzbuzz = wakka wakka
appData payload = {"foo":24,"bar":false,"whistlefeather":"Yes of course"}
appData ID = 12345
appData fizzbuzz = wakka wakka
appData payload = "Please save me to a MongoDB"

Note that to get back a JSON string for the payload, you shouldn't use toString() as in the above example. You should use objectMapper.writeValueAsString(payload).



回答2:

You can try using a Map, i don't know what it is in Groovy, but this would be the structure in Java.

public class AppData {
    String id;
    Map<String, Object> payload;
    String fizzbuzz;
}