1

In a Spring Boot Controller method, how do I get the body of a POST? All of the examples I have seen use @RequestBody. How do I get the body without using @RequestBody?

I am writing a method to handle Slack Events. When Slack POSTs an event, the body is in JSON and often contains a "user" key. Depending on the type of event, the value of "user" can either be a string or an object. Because of this, I cannot create a single Class and write

@RequestMapping(path = "/slackRequest", method = RequestMethod.POST)
    public String handleSlackRequest(@RequestBody final SlackRequest slackRequest)

Answer: Implementing the approach suggested by @ChiDov, the solution is to keep the @RequestBody, import

import com.fasterxml.jackson.annotation.JsonSetter;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

define the user field (and a new field to store the 'user' if it is a simple String value) as

@OneToOne
private SlackEventUser user;
private String slackUserId;

and define its Setter method as

@JsonSetter("user")
public void setUser(JsonNode userNode) {
    ObjectMapper mapper = new ObjectMapper();
    mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    if (userNode.isObject()) {
        SlackEventUser slackEventUser = mapper.convertValue(userNode, SlackEventUser.class);
        this.user = slackEventUser;
    } else {
        String userString = mapper.convertValue(userNode, String.class);
        this.slackUserId = userString;
        this.user = null;
    }
}
Allen Cypher
  • 428
  • 5
  • 16
  • Just accept a `String` and map it manually depending on content? – Darren Forsythe Oct 06 '17 at 17:52
  • @DarrenForsythe The deserializer knows the "{" is the start of an object, and not a String: "Failed to read HTTP message: org.springframework.http.converter.HttpMessageNotReadableException: Could not read document: Can not deserialize instance of java.lang.String out of START_OBJECT token" – Allen Cypher Oct 06 '17 at 20:14

1 Answers1

0

Updated: I would make your DTO like :

Class SlackRequest{
    ...
    private String eventType;
    private JsonNode user;
    ...
    public String getUser(){
        return user.asText();
    }
}

and in controller:

@RequestMapping(path = "/slackRequest", method = RequestMethod.POST)
    public String handleSlackRequest(@RequestBody final SlackRequest slackRequest){
    if(slackRequest.getEventType == "objectEvent"){
      SomeObject user = mapper.convertValue(slackRequest.getUser(), SomeObject.class);
      // do something with the object
    }else{
     // do something with the user string
   }
}

Get Inspiration from : How to deserialize dynamic field using Jackson?

Chi Dov
  • 1,447
  • 1
  • 11
  • 22
  • The handler never gets invoked. The deserializer knows the "{" is the start of an object, and not a String: "Failed to read HTTP message: org.springframework.http.converter.HttpMessageNotReadableExc‌​eption: Could not read document: Can not deserialize instance of java.lang.String out of START_OBJECT token" – Allen Cypher Oct 06 '17 at 20:16
  • Thanks. I'll look into 1) an "untyped" mapping and 2) custom deserializers – Allen Cypher Oct 06 '17 at 20:31
  • you're welcome, hope my answer help you. I would suggest you working on a custom deserializer, if you don't have a lot of field in the slackRequest. so you can deserialze it into a string or object base on the eventType. – Chi Dov Oct 06 '17 at 20:34
  • "untyped" mapping: [Jackson deserialization of type with different objects](https://stackoverflow.com/questions/20143114/jackson-deserialization-of-type-with-different-objects/20147483) – Allen Cypher Oct 06 '17 at 20:38