With the below GET request:
ResponseEntity<String> entity = restTemplate.exchange(uri, HttpMethod.GET, requestEntity, String.class );
entity.getBody();
returns a JSON String like this:
{"userRegistrations":[{"userRegistrationToken":"fb398972","userRegistrationTokenAlias":"87f15f8"}]}
But I want to make this work with an object not with a string. So with the code below I receive a UserRegistrations object with a null UserTokenResponse List
ResponseEntity<UserRegistrations> entity = restTemplate.exchange(uri, HttpMethod.GET, requestEntity, UserRegistrations.class );
entity.getBody();
And my domain class looks like this:
public class UserRegistrations {
List<UserTokenResponse> userRegistrationList;
//..getters and setters
}
public class UserTokenResponse {
private String userRegistrationToken;
private String userRegistrationTokenAlias;
//getters and setters
}
What am I missing?
Assuming you're using Jackson, RestTemplate automatically registers a MappingJackson2HttpMessageConverter which configures the underlying ObjectMapper to ignore unknown properties.
The JSON object has a single attribute named userRegistrations, whereas your Java class has a single attribute named userRegistrationList. They don't match.
They need to match, or you need to add a #JsonProperty annotation of the attribute to make Jackson serialize/parse it as userRegistrations.
This happens when your class property names doesn't match with the JSON property names coming in the response. For instance take the below example
public class ScheduledCallbacks {
private List<Callback> callbacks;
public List<Callback> getCallbacks() {
return callbacks;
}
public void setCallbacks(List<Callback> callbacks) {
this.callbacks = callbacks;
}
#Override
public String toString() {
return "ScheduledCallbacks [callbacks=" + callbacks + "]";
}
}
and if the response is the following way
{
"scheduledCallbacks": [
{
"sessionId": "string",
"customerNbr": "string",
"topicName": "string",
"desiredTime": "string",
"callbackState": "string",
"serviceName": "string",
"expirationTime": "string",
"programCode": "string"
}
]
}
Then you get null response because the name scheduledCallbacks in the JSON response doesn't match with the name callbacks in class.
But if your class is as following
public class ScheduledCallbacks {
private List<Callback> scheduledCallbacks;
public List<Callback> getScheduledCallbacks() {
return scheduledCallbacks;
}
public void setScheduledCallbacks(List<Callback> scheduledCallbacks) {
this.scheduledCallbacks = scheduledCallbacks;
}
#Override
public String toString() {
return "ScheduledCallbacks [scheduledCallbacks=" + scheduledCallbacks + "]";
}
}
Then you get the expected response in response entity
I encountered a similar error and it was returning null too. The problem is over when Object.class is replaced with the name of the class we want to convert on the client side.
Like that:
Token = restTemplate.exchange(uri, HttpMethod.POST, request, Object.class);
the problem was probably due to the fact that it is not directly compatible with the class we want to convert.
Related
I have a POST endpoint which accepts a JSON as request body.
#Path("/drink")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public class DrinkResource {
#POST
public Drink getDrink(Fruit fruit) {
return new Drink(fruit.getName());
}
}
The request body is supposed to be deserialized into this POJO :
public class Fruit {
private final String name;
#JsonCreator
public Fruit(#JsonProperty("name") String name) {
this.name = name;
}
public String getName() {
return name;
}
}
I'm using Jackson for the deserialization.
Is it possible to make the deserialization fail when the JSON in the request body has duplicate keys ?
For example, if the request body looks like this : {"name" : "banana", "name" : "orange"}, I would like to get a 500 status code or another kind of error instead of having the json deserialized with the last property.
Basically, I'm looking for a solution with the same logic as the JsonParser.Feature.STRICT_DUPLICATE_DETECTION with the ObjectMapper but for a POST endpoint.
I'm also using quarkus so I don't know if there is a property for this. Something similar to quarkus.jackson.fail-on-unknown-properties=true but for the duplicate properties.
Add the following:
#Singleton
public class MyCustomizer implements ObjectMapperCustomizer {
#Override
public void customize(ObjectMapper objectMapper) {
objectMapper.enable(JsonParser.Feature.STRICT_DUPLICATE_DETECTION);
}
}
If you do, the ObjectMapper that Quarkus uses will throw a JsonParseException thus leading to a HTTP 400 response.
We have a rest api built with spring boot and the openapi code generator. Let's assume there is a path /user in our api spec:
...
"paths": {
"/user": {
"get": {
"parameters": [{
"name": "id",
"in": "query",
"required": false,
"type": "integer",
"format": "int64"
}],
"responses": { ... }
},
}
// more paths
}
...
A call to this path could then be: /user?id=1234.
The code generator creates an interface MyControllerApi:
#javax.annotation.Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "some date")
#Api(value="user")
public interface MyControllerApi {
#ApiOperation(value="", nickname="userGet", response = User.class, /* ... */)
#ApiResponse(/* ... */)
#GetMapping(value="/user", produces = { "application/json" })
ResponseEntity<User> userGet(#ApiParam(value = "id") #RequestParam(value = "id", required = false) Long id);
}
The controller then looks like this:
#RestController
public class MyController implements MyControllerApi
{
#Autowired
UserService service;
#Override
public ResponseEntity<User> userGet(#RequestParam(value = "id") Long id) {
return service.get(id);
}
}
If /user?id=<value> is requested, spring boot automatically checks if the type of the passed parameter value <value> matches the required type. If not BAD_REQUEST is returned:
{
"timestamp": "2022-10-19T17:20:48.393+0000",
"status": 400,
"error": "Bad Request",
"path": "/user"
}
We are now in a situation in which we want to pass null to each parameter of userGet that would cause a type mismatch. To be more clear: if /user?id=abcd is requested, userGet should be called with id set to null, so that we can return some default user. Is there a way to achieve this?
Of course there are a lot more paths and parameters and this behavior should apply for every query parameter of type Long or Boolean.
This example may not make much sense, but it is also just an example.
Thanks in advance
What I tried myself in the meantime
1. Setting the spring-boot useOptional option ...
... to true in the pom.xml (see here).
This has the affect that the query parameters in the controllers method are of type Optional<?>. In my example above this would lead to:
#Override
public ResponseEntity<User> userGet(Optional<Long> id) {
Long id_val = id.isPresent() ? id.get() : null;
return service.get(id_val);
}
But this did not work either and spring boot also creates a BAD_REQUEST response on parameter type mismatches.
2. Using a request interceptor
A request interceptor is a kind of middleware and can be created by implementing HandlerInterceptor and enables you to process the request before it is passed to the controller.
My interceptor looks like this:
public class CustomRequestInterceptor implements HandlerInterceptor {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception
{
HandlerMethod method = (HandlerMethod) handler;
MethodParameter[] handlerParams = method.getMethodParameters();
while (request.getParameterNames().hasMoreElements())
{
// the param name of the query
String paramName = request.getParameterNames().nextElement();
the param value
String paramValue = request.getParameter(paramName);
MethodParameter methodParam = null;
for (MethodParameter mp : handlerParams)
{
// We can access annotations here ...
Annotation anno = mp.getParameterAnnotation(RequestParam.class);
if(anno != null)
{
RequestParam rp = (RequestParam) anno;
// ... and am able to get the param name in the controller
// Check if we found the param
String requestParamName = rp.value();
if (requestParamName.toLowerCase().equals(paramName.toLowerCase()))
{
// and now?
break;
}
}
}
if (methodParam != null)
{
Type type = methodParam.getGenericParameterType();
}
}
return HandlerInterceptor.super.preHandle(request, response, handler);
}
}
So far so good, but the RequestParam-Object does not hold any informations about the parameter type, nor about the index of that param in the methods param list. And the MethodParameter does not hold the name of the parameter (because its a compiled class I think).
What I'm wondering is how spring boot itself maps the query parameters to the ones in the controllers methods..... ?
After doing some research I found a solution by using a custom HandlerMethodArgumentResolver:
public class MyCustomResolver implements HandlerMethodArgumentResolver {
#Override
#Nullable
public Object resolveArgument(
MethodParameter methodParam,
#Nullable ModelAndViewContainer mvContainer,
NativeWebRequest request,
#Nullable WebDataBinderFactory binderFac
) throws Exception {
String paramValue = request.getParameter(methodParam.getParameterName());
if (paramValue == null)
return null;
if (methodParam.getParameterType().equals(Long.class)) {
try {
// Set param value to the parsed one, if it can be parsed
return Long.parseLong(paramValue);
} catch (Exception e) {
// Set param value to null otherwise
return null;
}
}
return null;
}
#Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterType().equals(Long.class));
}
}
The method supportsParameter() gives you the opportunity to decide whether or not the passed parameter will be processed in resolveArgument.
resolveArgument then gets the parameter as MethodParameter. Unlike in the HandlerInterceptor the MethodParameter now contains the parameter name and type.
Using this approach id will have the following values:
id = 1 on /user?id=1
id = null on /user?id=abc
Now I'am able to handle falsy parameter values in the controller directly.
Remarks
If you use a parameter of type Boolean the param value will not be null but "false" for every parameter value except true. Only if "true" is passed in the request resolveArgument will parse it to true and getUser will receive true.
I'm having a trouble with deserializing JSON like:
[{
"foo": "bar",
"example": "value"
}]
For some reason, API I use packs it in unnamed Array with one element. I'm accessing that API with RestController.
As long as I used default Jackson Converter, I could easily prepare model classes like that (getters, setters, constructors omitted for clarity):
public class Model {
private String foo;
private String example;
}
public class Response {
#JsonValue
private List<Model> model;
}
Repository class
public class FooRepository {
private RestTemplate restTemplate;
public String getFoo() {
ResponseEntity<Response> response =
restTemplate
.exchange("http://localhost:8080/api"
HttpMethod.GET,
new HttpEntity<>(new HttpHeaders()),
Response.class);
return response.getBody().getModel().get(0).getFoo();
}
I cannot find any annotation that works like #JsonValue for Jsonb. Is there one? Or maybe there is another way to fix my problem without using Jackson?
edit:
I figured out a workaround solution.
public String getFoo() {
ResponseEntity<List<Model>> response =
restTemplate
.exchange("http://localhost:8080/api"
HttpMethod.GET,
new HttpEntity<>(new HttpHeaders()),
ParameterizedTypeReference<List<Model>>());
return response.getBody().get(0).getFoo();
}
I have a simple controller:
#RequestMapping(method = { RequestMethod.POST })
public ResponseEntity<MyResponse> print(#RequestBody final RequestModel request) throw ApiException {
return null;
}
And in my RequestModel:
class RequestModel {
private String name;
private CustomData data;
}
CustomData:
class CustomData {
private String data;
}
When I make POST request without the "data" field, it works. But if I add the "data" field, I'm getting a 400, The request sent by the client was syntatically incorrect.
O dont know If you wrote all the code, but tou should implements serializable and write setters and getters.
But, answering your question, you should annotate your fields with #JsonProperty to specify the required flag.
Your posted JSON should be something like this :
{
"name":"Luke",
"data": {
"data":"I am your father"
}
}
OBS: if you are using Postman, please set the header : key: Content-Type, value: application/json
You should specify an endpoint:
Example :
#PostMapping("/data")
Instead of
#RequestMapping(method = { RequestMethod.POST })
If you are using default port, try again the post to :
http://localhost:8080/data
OBS: RequestModel and CustomerData must have getters and setters.
I'm trying to create a POST servlet that should be called with JSON request. The following should work, but does not. What might be missing?
#RestController
public class MyServlet {
#PostMapping("/")
public String test(#RequestParam String name, #RequestParam String[] params) {
return "name was: " + name;
}
}
JSON POST:
{
"name": "test",
"params": [
"first", "snd"
]
}
Result: name is always null. Why?
"Response could not be created: org.springframework.web.bind.MissingServletRequestParameterException: Required String parameter 'name' is not present"
In general I don't pass a request param in a POST method. Instead, I am using a DTO to pass it in the body like:
#RequestMapping(value = "/items", method = RequestMethod.POST)
public void addItem(#RequestBody ItemDTO itemDTO)
Then, you need to create the ItemDTO as a POJO with the necessary fields.
In addition to #stzoannos answer, if you do not want to create POJO for json object, you can use google GSON library to parse json into JsonObject class, which allow to work with parameters through same as get and set methods.
JsonObject jsonObj = new JsonParser().parse(json).getAsJsonObject();
return "name is: " + jsonObj.get("name").getAsString();