-
Notifications
You must be signed in to change notification settings - Fork 38.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add @FormAttribute attributes to customize x-www-form-urlencoded [SPR-13433] #18012
Comments
Rossen Stoyanchev commented Adding link to an (old) related ticket #13880. Wouldn't that have to be |
Martin Myslík commented I should have clarified this in my simplified example. It should , of course, be:
As for the requirement, I am not saying that this is a core functionality of Spring that I am desperately missing but imagine this scenario: You are processing a form-encoded payload from a third party service and you want to use POJO to map the values. You use camelCase in your whole project and now you are forced to use underscopes if the third party service does so (in the POJO) or write your own converter just for this purpose. When I encountered this problem, I immediately looked for some Spring functionality to tackle this and was surprised that there is none. Feel free to close this issue if you feel that I am being unreasonable but I expected more people having this issue as well. |
Rossen Stoyanchev commented I think the scenario is quite clear. There is nothing unreasonable about your request. I'm only wondering whether annotations would solve this effectively. An annotation here and there to customize a field name is okay but having to put one on every field to adapt to different naming strategy -- that feels more like something that should be more centralized, otherwise you'd have to have one on every field. If the use case is purely input based, i.e. data binding from a request such as a REST service, then a Filter could wraps the request and overrides the getRequestParam() and related method to check for both "firstValue" and also "first_value". |
Martin Myslík commented I had several use cases where I had to extract just several fields from a huge form-encoded request which would mean putting this annotation on a couple of properties but you are right that you still have to annotate every property of such POJO. Perhaps some higher level annotation for the whole classto automatically convert underscopes in the field names to camel case or a filter as you are suggesting would be more elegant solution. |
Micah Silverman commented Bringing this up again. ;) There are still a number of important providers that insist on x-www-form-urlencoded when POSTing. Regarding the concern around lots of annotations on a POJO, I recently encountered this directly in working with Slack's slash command features. https://api.slack.com/slash-commands. In short, you register an endpoint with Slack, and when you issue a "slash" command, slack POSTs to your endpoint with x-www-form-urlencoded Content-type. So, one approach to handle that would look like this: @RequestMapping(
value = "/slack",
method = RequestMethod.POST,
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE
)
public MyResponse onReceiveSlashCommand(
@RequestParam("token") String token,
@RequestParam("team_id") String teamId,
@RequestParam("team_domain") String teamDomain,
@RequestParam("channel_id") String channelId,
@RequestParam("channel_name") String channelName,
@RequestParam("user_id") String userId,
@RequestParam("user_name") String userName,
@RequestParam("command") String command,
@RequestParam("text") String text,
@RequestParam("response_url") String responseUrl
) {
...
} This is pretty gnarly, especially in the age of Spring boot's built in Jackson mapper handling. So, I set out to do this: public class SlackSlashCommand {
private String token;
private String command;
private String text;
@JsonProperty("team_id")
private String teamId;
@JsonProperty("team_domain")
private String teamDomain;
@JsonProperty("channel_id")
private String channelId;
@JsonProperty("channel_name")
private String channelName;
@JsonProperty("user_id")
private String userId;
@JsonProperty("user_name")
private String userName;
@JsonProperty("response_url")
private String responseUrl;
...
} If the POST were sent as application/json, then the controller would look like this and we'd be done: @RequestMapping(value = "/slack", method = RequestMethod.POST)
public @ResponseBody SlackSlashCommand slack(@RequestBody SlackSlashCommand slackSlashCommand) {
log.info("slackSlashCommand: {}", slackSlashCommand);
return slackSlashCommand;
} But, slack will only POST with x-www-form-urlencoded. So, I had to make the controller method like this: @RequestMapping(
value = "/slack", method = RequestMethod.POST,
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, produces = MediaType.APPLICATION_JSON_VALUE
)
public @ResponseBody SlackSlashCommand slack(SlackSlashCommand slackSlashCommand) {
log.info("slackSlashCommand: {}", slackSlashCommand);
return slackSlashCommand;
} Only problem is that the underscore properties coming in from Slack get ignored when materializing the SlackSlashCommand. If there were an analog to What I did for now to get around this, and so as to not pollute my SlackSlashCommand class, is a little ugly, but it works: // workaround for customize x-www-form-urlencoded
public abstract class AbstractFormSlackSlashCommand {
public void setTeam_id(String teamId) {
setTeamId(teamId);
}
public void setTeam_domain(String teamDomain) {
setTeamDomain(teamDomain);
}
public void setChannel_id(String channelId) {
setChannelId(channelId);
}
public void setChannel_name(String channelName) {
setChannelName(channelName);
}
public void setUser_id(String userId) {
setUserId(userId);
}
public void setUser_name(String userName) {
setUserName(userName);
}
public void setResponse_url(String responseUrl) {
setResponseUrl(responseUrl);
}
abstract void setTeamId(String teamId);
abstract void setTeamDomain(String teamDomain);
abstract void setChannelId(String channelId);
abstract void setChannelName(String channelName);
abstract void setUserId(String userId);
abstract void setUserName(String userName);
abstract void setResponseUrl(String responseUrl);
}
public class SlackSlashCommand extends AbstractFormSlackSlashCommand {
...
} That's a lot of boilerplate to accomplish what Jackson can do automatically with the http -v -f POST localhost:8080/api/v1/slack2 token=token team_id=team_id team_domain=team_domain channel_id=channel_id channel_name=channel_name user_id=user_id user_name=user_name command=command text=text response_url=response_url
POST /api/v1/slack2 HTTP/1.1
Accept: */*
Content-Type: application/x-www-form-urlencoded; charset=utf-8
...
token=token&team_id=team_id&team_domain=team_domain&channel_id=channel_id&channel_name=channel_name&user_id=user_id&user_name=user_name&command=command&text=text&response_url=response_url
HTTP/1.1 200
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Content-Type: application/json;charset=UTF-8
Date: Mon, 22 May 2017 05:28:27 GMT
...
{
"channel_id": "channel_id",
"channel_name": "channel_name",
"command": "command",
"response_url": "response_url",
"team_domain": "team_domain",
"team_id": "team_id",
"text": "text",
"token": "token",
"user_id": "user_id",
"user_name": "user_name"
} |
Micah Silverman commented Two approaches to solve the problem. Both require more boilerplate than is necessary for accomplishing the same thing with JSON: https://gist.github.com/dogeared/7e60a2caebd00c959f0d7e24ef79b54e This first approach uses an abstract super-class that breaks Java naming conventions. Pros: subclass is kept "clean". It's clear what's going on in the super class and why. No additional converters or configuration needed. Cons: Breaks Java naming conventions. The second approach is the more "proper" approach. It uses an HttpMessageConverter. Pros: it's more idiomatic Spring and doesn't break any naming conventions. It enables using |
Micah Silverman commented I created a gist for a third approach: https://gist.github.com/dogeared/3ebb46b9d948c023e702ccb9ecfdf35e |
Micah Silverman commented I also created a blog post on the subject ;) https://afitnerd.com/2017/05/24/what-if-spring-boot-handled-forms-like-json/ |
This comment was marked as duplicate.
This comment was marked as duplicate.
This comment was marked as duplicate.
This comment was marked as duplicate.
This comment was marked as duplicate.
This comment was marked as duplicate.
Zhang Jie commented Hi, all, I have found that, if we want to use custom |
Hi I'm newbie ^^; |
This issue is still "Open". |
This would be a nice improvement & boost to the framework. |
No one mentioned hyphens? I feel it's not all that uncommon for a query param to be "team-id". Without this feature, it seems impossible to do this now? |
I have made a simple example in my project for form data according to Zhang Jie 's suggestion. |
I was encountering similar issues with query params and form data having different formats to my |
Thanks @mattbertolini. Two things I'm wondering about. One is nested binding. Taking the Slack commands example from above with keys like Two is the other side of parsing, i.e. formatting and displaying bound values and errors. Would you expect that a |
Hi @rstoyanchev. Thanks for the questions. Re: nested binding: You are correct that in the slack commands example nested binding would be unnecessary. My vision for nested bindings was mainly for grouping together similarly linked parameters. More of a logical thing for the engineer than for modeling an external API. I mostly expect the library to be used for flat objects and nested binding to be a niche case. Since I was modeling my library after JAX-RS *Param annotations, I added nested binding in as well. I'm definitely looking for additional use cases for nested binding to highlight in my documentation so if anyone has some I'd love to hear about them. Re: formatting and errors. You are correct that the I mostly use my approach for API development ( I hope this answers your questions. Thanks for taking a look at my library. Much appreciated. |
+1 |
I did this: Controller:
POJO:
|
I found this article: https://www.metakoder.com/blog/parse-snake-case-json-in-spring-boot/ I hope this help you as much as it did it to me! |
I my case, the Json I was receving it's in UpperCamelCaseStrategy, so I added this sentence just above the class definition of the POJO I´m receiving with @RequestBody @JsonNaming(PropertyNamingStrategy.UpperCamelCaseStrategy.class) |
@puertajennifer |
Related questions with workarounds:
I don't like |
I did this: Controller: @RequestMapping(value = "/slack", method = RequestMethod.POST, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public @ResponseBody SlackSlashCommand slack(@RequestBody MultiValueMap<String, Object> reqMap) {
objectMapper.enable(DeserializationFeature.UNWRAP_ROOT_VALUE);
objectMapper.enable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS);
SlackSlashCommand slackSlashCommand = objectMapper.convertValue(reqMap, SlackSlashCommand.class);
return slackSlashCommand;
} Just use @JsonProperty with underscore. public class SlackSlashCommand {
@JsonProperty("team_id")
private Long teamId; // Type long
@JsonProperty("team_domain")
private String teamDomain; // Type String
@JsonProperty("insert_timestamp")
private LocalDateTime insertTimestamp; // Type LocalDateTime
} |
This comment was marked as duplicate.
This comment was marked as duplicate.
@Nasil I'm not sure you have to create and configure
You have to declare it as a bean )) I wonder if the "hack" will work on complex objects instead of |
This comment was marked as duplicate.
This comment was marked as duplicate.
1 similar comment
This comment was marked as duplicate.
This comment was marked as duplicate.
For 6.1, we've added enhanced support for constructor binding in public class SlackSlashCommand {
private String token;
private String command;
private String text;
private String teamId;
private String teamDomain;
public SlackSlashCommand(
String token, String command, String text,
@BindParam("team_id") String teamId, @BindParam("team_domain") String teamDomain) {
// ...
}
} In light of this, I am closing this issue as superseded. |
@rstoyanchev Could you clarify the solution? I have a Like for your example if I write:
for my POST form with |
@gavenkoa, yes that's how it is expected to work. Just minor note that it's |
Does this solution support binding individual fields? Using / forcing a constructor is not ideal. For example, an object that uses Lombok, Jackson, or requires other annotations, versus the stated solution would funnel the fields through a constructor. I know they are slightly different concerns but it makes it harder to get them to work together.
|
A constructor is required and I think |
Thanks for replying. While
|
What I said is that you do not need to annotate the constructor, and also that you do not need to use |
Lombok doesn't provide a constructor. |
Sorry, I'm no expert on Lombok. I was looking here https://projectlombok.org/features/Data. |
@nkavian I gave the scenario with Lombok a try. If I change |
I'll avoid try to avoid any further replies, I've already tried to point out this solution is too narrow (that requires the client to do very specific things to make it work). It would be better to see a simpler solution with fewer dependencies on how the client writes their code.
|
The solution doesn't require you do very specific things. It's rather meant for use with constructors and final fields, which I consider a good practice. It's fine if you prefer field injection, but we don't support that currently. |
Phil Webb opened SPR-13433 and commented
As requested on the Spring Boot issue tracker:
spring-projects/spring-boot#3890
—
When processing an
HttpRequest
withx-www-form-urlencoded
content, we can use a controller with POJO matching the payload structure.Example payload:
value_first=value1&value_second=value2
POJO:
The problem is that the variable names must match the field names in the
HttpRequest
payload in order to be processed by the controller. There is not way of naming them differently, for example, if I do not want to use underscores in Java variable and method names.What I would appreciate would be using something like this:
Issue Links:
20 votes, 16 watchers
The text was updated successfully, but these errors were encountered: