Spring Boot request body semi-required fields - spring

In our application user can write a message based on user id or screen name.
class Message {
public final Long userId;
public final String screenName;
public final String text;
#JsonCreator
public Message(#JsonProperty(value = "user_id", required = ???) Long userId,
#JsonProperty(value = "screen_name", required = ???) String screenName,
#JsonProperty(value = "text", required = true) String text) {
this.userId = userId;
this.screenName = screenName;
this.text = text;
}
}
Fields userId and screenName can't be optional at same time, one should be provided.
How in Spring Boot to mark that they are semi-required?

This seems like more of a validation concern rather than deserialization.
Create a Validator then put #Valid within the #RequestMapping on the controller.
See more here:
Spring REST Validation Example

From jenkov tutorials:
#JsonValue
The Jackson annotation #JsonValue tells Jackson that Jackson should
not attempt to serialize the object itself, but rather call a method
on the object which serializes the object to a JSON string. Note that
Jackson will escape any quotation marks inside the String returned by
the custom serialization, so you cannot return e.g. a full JSON
object. For that you should use #JsonRawValue instead (see previous
section).
The #JsonValue annotation is added to the method that Jackson is to
call to serialize the object into a JSON string. Here is an example
showing how to use the #JsonValue annotation:
public class PersonValue {
public long personId = 0;
public String name = null;
#JsonValue
public String toJson(){
return this.personId + "," + this.name;
}
}
The output you would get from asking Jackson to serialize a
PersonValue object is this:
"0,null"
So you can use #JsonValue and put your code either to ignore or not from some fields when you try to convert into JSON
#JsonValue
public String toJson(){
//ignore fields or include them here
}

Just throw an IllegalArgumentException. The best case would be to deserialize, then run through a validator though so you separate the concerns of serialization, and domain validation.
class Message {
public final Long userId;
public final String screenName;
public final String text;
#JsonCreator
public Message(#JsonProperty(value = "user_id", required = false) Long userId,
#JsonProperty(value = "screen_name", required = false) String screenName,
#JsonProperty(value = "text", required = true) String text) {
if(userId == null && screenName == null) {
throw new IllegalArgumentException("userId or screenName must be provided.");
}
this.userId = userId;
this.screenName = screenName;
this.text = text;
}
}

Related

How to annotate type argument in Kotlin?

Essentially, I'm trying to get something like this
public class Post {
public final #Nullable Optional<#Size(min = 10) String> title;
public Post(#JsonProperty("title") Optional<String> title) {
this.title = title;
}
}
but in Kotlin.
I've tried
class Post(
#field:Size(min = 10) val title: Optional<String>?
)
but that annotates the title property and not the String inside the Optional.
Is there a way to achieve this in Kotlin?
The context of this is to use validators in Spring Boot to validate the given string, if it is present. The Optional is there to allow me to distinguish between the user sending
{
"title": null
}
or
{}

How to annotate DTO so that it shows up in SwaggerUI Schema?

I have a controller with a #RequestBody DTO. I need to show the DTO's schema instead of the default string in the RequestBody Schema in Swagger.
By using #Operation above the API and #Parameter within, I've been able to describe the DTO in two places
and fill in the example (see code). I've tried #Schema in the #Operation (under requestBody) and #Parameter annotations. The former throws an NPE and the latter changes nothing, with a variety of tries regarding counterpart annotations in the DTO itself.
Sample Controller
#RequestMapping(value = "/{myPathVar}", method = RequestMethod.POST)
#Operation(summary = "Create something.",
parameters = { #Parameter(in = ParameterIn.PATH, name = "myPathVar", description = "Some path variable. Swagger uses this description.") },
requestBody = #io.swagger.v3.oas.annotations.parameters.RequestBody(
description = "My description here.",
content = #Content(examples = #ExampleObject("{\"A\" : \"a\",\"B\" : \"{\"b\" : \"foo\", \"bb\" : \"bar\"}"))))
#ApiResponse(content = #Content(schema = #Schema(implementation = MyReturningType.class)))
public MyReturningType doSomethingCool(
#Parameter(description = "Some description Swagger ignores.", example = "123") #PathVariable(value = "myPathVar") int myPathVar,
#Parameter(description = "My other description here.", schema = #Schema(implementation = MyDto.class)) #RequestBody MyDto dto) {
// do something cool
}
Sample DTO
// Do I need certain annotations here?
public class MyDto {
// What annotation goes here? #Parameter, #JsonProperty, #Schema, something else?
private int someInt;
private String someString;
private Object someObject;
}
What combination of annotations do I need to correctly label the DTO Schema within the DTO and then reference this Schema from the controller such that the Schema field is populated in SwaggerUI?
The issue might have been caused by the fact that the fields in your DTO are of private visibility and from the code you shared, doesn't look like they have getters and setters available.
Refer to the below example for a working example of how it can be done
Controller
// Using the specific mapping annotation will keep the code clean
#PostMapping("/{myPathVar}")
// The below annotation describes the operation
#Operation(
summary = "Brief description of the operation",
description = "Detailed description of the operation")
// Describe the possible responses next. Prefer using #ApiResponses for multiple response
#ApiResponse(
// specify the http response code
responseCode = "201",
// some description. Maybe use the corresponding http response code's description
description = "Created",
// describe the content that will be returned for this response code
content = #Content(
// optionally, specify the media type for the response here as shown in the below code
mediaType = MediaType.APPLICATION_JSON_VALUE,
// specify the implementation of the response DTO here
schema = #Schema(implementation = Void.class)))
public Void doSomethingCool(
// Use #Parameter for #PathVariable and #RequestVariable
#Parameter(description = "Description for path/request-parameter here")
#PathVariable(value = "myPathVar")
int myPathVar,
// Both these #RequestBody annotations are mandatory.
#io.swagger.v3.oas.annotations.parameters.RequestBody(
description = "Controller-level model description here")
#org.springframework.web.bind.annotation.RequestBody
TestDTO dto) {
// ... do some cool stuff here
return null;
}
DTO
#Schema(description = "Model-level description here")
public class TestDTO {
#Schema(description = "Field-level description here")
private int someInt;
#Schema(description = "Another Field-level description here")
private String someString;
#Schema(description = "Yet another Field-level description here")
private Object someObject;
// all getters and setters here
}
This gives you the output as below
Annotated your dto like the following . It works for me
#Schema(description = "User Dto")
#Data
public class UserDto {
private int id;
#JsonProperty
private String email;
#JsonProperty
private String password;
#JsonProperty
private String firstName;
#JsonProperty
#Schema(description = "User Id")
private String lastName;
}

How to generate cache CustomKey for Redis in Spring Boot

I have spring boot application which is integrated with Redis cache. Have to implement caching for one of the method call. That method argument is an object with multiple params which is external Request object. This object params will vary for each request also based on that param and its values output of the method is varies. I need to create a cache key using that Request object field/param values. How to achieve it.
We can use SimpleKeyGenerator only when method params are static?
UserService.java
#Cacheable(value = "usercache", keyGenerator="customKeyGenerator")
public UserResponse getUserResp(User user){
//Some backend calls
return user
}
User.java
public class User {
private String firstname;
private String lastname;
private Integer age;
private Date dob;
private Address address;
// Another 10 params
}
In this method implementation User object is dynamic. I have to create a cache key based on User object fields which is having valid non null values. How to achieve it.
I have implemented as like below.
User.java
public class User implements Serializable {
private String firstname;
private String lastname;
private Integer age;
private Date dob;
private Address address;
// Another 10 params
#Override
public int hashCode() {
final int prime = 31;
//Add necessary fields
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
//Add necessary fields
}
}
public class UserKeyGenerator implements KeyGenerator{
private static final String UNDERSCORE_DELIMITER = "_";
#Override
public Object generate(Object target, Method method, Object... params) {
String cacheKey = null;
if(params.length > 0) {
StringJoiner paramStrJoiner = new StringJoiner(UNDERSCORE_DELIMITER);
User userReq = (User) params[0];
paramStrJoiner.add(target.getClass().getSimpleName());
paramStrJoiner.add(method.getName());
paramStrJoiner.add(String.valueOf(userReq.hashCode()));
cacheKey = paramStrJoiner.toString();
}
return cacheKey;
}

Spring Boot marshall Xml from RestTemplate without RootElement

I am using a RestTemplate like this:
return this.getForEntity(baseUrl, BasicResponse.class, parameters);
This is the BasicResponse class:
public class BasicResponse {
private String status;
private String statusMsg;
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getStatusMsg() {
return statusMsg;
}
public void setStatusMsg(String statusMsg) {
this.statusMsg = statusMsg;
}
}
No exceptions are thrown but the fields in the returned ResponseEntity body are 'null'. I think it's because the element does not have a valid XML structure (as in no root element). I do not have control over the parsed XML. How can I map my object?
Since the XML is not valid,
I believe that you will not be able to use RestTemplate.getForEntity
to get a BasicResponse object.
Try this:
private static final String VALUE_END_TAG = "</blammy>";
private static final String VALUE_START_TAG = "<blammy>";
private XmlMapper xmlMapper; // initialize this correctly, somewhere off page.
method stuff
{
final String actualResponse;
final StringBuilder correctedResponse = new StringBuilder();
final BasicResponse returnValue;
actualResponse = restTemplate.getForEntity(baseUrl, BasicResponse.class, parameters);
correctedResponse.append(VALUE_START_TAG);
correctedResponse.append(actualResponse);
correctedResponse.append(VALUE_END_TAG);
returnValue = xmlMapper.readValue(correctedResponse.toString(), BasicResponse.class);
return returnValue;
}
Use some reasonable value as the element name in the start and end tags,
perhaps "" and "".
Consider using some Jackson annotations,
for example #JacksonXmlRootElement(localName = "blammy")
(this local name matches my example).

Validate input before Jackson in Spring Boot

I've built a REST endpoint using Spring Boot. JSON is posted to the endpoint. Jackson converts the JSON giving me an object.
The JSON look like this:
{
"parameterDateUnadjusted": "2017-01-01",
"parameterDateAdjusted": "2017-01-02"
}
Jackson converts the JSON to an object based on this class:
public class ParameterDate {
#NotNull(message = "Parameter Date Unadjusted can not be blank or null")
#DateTimeFormat(pattern = "yyyy-MM-dd")
private Date parameterDateUnadjusted;
#NotNull(message = "Parameter Date Adjusted can not be blank or null")
#DateTimeFormat(pattern = "yyyy-MM-dd")
private Date parameterDateAdjusted;
private Date parameterDateAdded;
private Date parameterDateChanged;
}
This all works fine. The issue I'm having is that I would like to validate the data before Jackson converts the data. For instance if I post
{
"parameterDateUnadjusted": "2017-01-01",
"parameterDateAdjusted": "2017-01-40"
}
Where parameterDateAdjusted is not a valid date (there is no month with 40 days in it). Jackson converts this to 2017-02-09. One way of getting around this is to have a class that is only strings let's call it ParameterDateInput. Validate each filed with Hibernate Validator in the parameterDateInput object and then copy the parameterDateInput object to parameterDate where each field has the correct type (dates are of type Date and not of type String). This to me doesn't look like a very elegant solution. Is there some other way I can solve this? How is data generally validated in Spring Boot when posted as JSON? I like to be able to send back a message to the user/client what is wrong with the data that is being posted.
How about a custom JSON deserializer where you can write down the logic you want:
#RestController
public class JacksonCustomDesRestEndpoint {
#RequestMapping(value = "/yourEndPoint", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public Object createRole(#RequestBody ParameterDate paramDate) {
return paramDate;
}
}
#JsonDeserialize(using = RoleDeserializer.class)
public class ParameterDate {
// ......
}
public class RoleDeserializer extends JsonDeserializer<ParameterDate> {
#Override
public ParameterDate deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
ObjectCodec oc = jsonParser.getCodec();
JsonNode node = oc.readTree(jsonParser);
String parameterDateUnadjusted = node.get("parameterDateUnadjusted").getTextValue();
//Do what you want with the date and set it to object from type ParameterDate and return the object at the end.
//Don't forget to fill all the properties to this object because you do not want to lose data that came from the request.
return something;
}
}
There is a way to check the dates. setLenient() method
public static boolean isValidDate(String inDate, String format) {
SimpleDateFormat dateFormat = new SimpleDateFormat(format);
dateFormat.setLenient(false);
try {
dateFormat.parse(inDate.trim());
} catch (ParseException pe) {
return false;
}
return true;
}
Just define own annotation to validate the value
#Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE })
#Retention(RUNTIME)
#Constraint(validatedBy = MyDateFormatCheckValidator.class)
#Documented
public #interface MyDateFormatCheck {
String pattern();
...
and the validator class
public class MyDateFormatCheckValidator implements ConstraintValidator<MyDateFormatCheck, String> {
private MyDateFormatCheck check;
#Override
public void initialize(MyDateFormatCheck constraintAnnotation) {
this.check= constraintAnnotation;
}
#Override
public boolean isValid(String object, ConstraintValidatorContext constraintContext) {
if ( object == null ) {
return true;
}
return isValidDate(object, check.pattern());
}
}

Resources