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

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;
}

Related

Postman: Create complex MULTIPART_FORM_DATA request

I need to create a request using postman. The spring boot endpoint is:
#RequestMapping(
value = "/group",
method = RequestMethod.POST,
consumes = MediaType.MULTIPART_FORM_DATA_VALUE
)
public ResponseEntity<String> group(
#RequestPart("items") List<ItemType> items,
#RequestPart("group") GroupType group);
where:
public class ItemType {
private String description;
private String security;
private Date bestdate;
private MultipartFile content;
}
public class GroupType {
private String description;
private String security;
private String metadata;
}
As you can see, ItemType contains an MultiPartFile.
I mean, what do I need to write on "items" and "group" parts:
There is a option in Postman to provide the form data, where you can pass both the text and file.
The json part of the body should also be set as "File" rather then "Text", and put your json data in a json file for example "a.json".
Check below , Hope this will help.

Values in the response of api are empty

ResponseInJsonFormat is a class,
#Builder
#Getter
public class ResponseInJsonFormat {
// Id field in Eloqua
private String id;
// List of field values
private List<FieldValues> fieldValues;
}
And FieldValues is also a class defined as,
#Builder
#Setter
#JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
public class FieldValues {
// id of the parameter
private String ID;
// parameters
private String type;
// Value :- value of the parameter
private String value;
}
I'm writing an api ,
#RequestMapping(value = "/service/XYZService", method = RequestMethod.POST)
public ResponseInJsonFormat sendData(#RequestParam String a) {
FieldValues parameter = FieldValues.builder()
.ID(FIELD_ID)
.type(FILED_TYPE)
.value(a)
.build();
List<FieldValues> values = Arrays.asList(parameter);
ResponseInJsonFormat response = ResponseInJsonFormat .builder()
.fieldValues(values)
.id(ID)
.build();
// need to return ResponseInJsonFormat
return response
}
But when I call to a service it returns something like,
{
"id": "110",
"fieldValues": [
{}
]
}
Could somebody assist about this empty json object of fieldValues.
I havnt understand why to put #JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
if I dont use this annotation I get an error as,
no properties discovered to create BeanSerializer
So I used the annotation, the error has gone but getting empty value for the same, even though the value is explicitly assigned.
I do not understand why to do all this it just being a list.
Is there any document I should read about,please advice.
If I'm not wrong, spring return the response in JSON format. Then why to do all this.

Spring Boot request body semi-required fields

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;
}
}

How to send Java collections containing subclasses to spring controller

I'm trying to send collections to my spring MVC controller:
#RequestMapping("/postUsers.do")
public #ResponseBody ResponseDTO postUsers(#ModelAttribute("mapperList") MapperList mapperList) {
//prints {"users":null}
System.out.println(new ObjectMapper().writeValueAsString(mapperList));
return new ResponseDTO();
}
this is the code posting my users :
public ResponseDTO postUsers(ArrayList<User> users) {
ResponseDTO serverResponse = null;
URL url = new URL(urlString);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setDoOutput(true);
connection.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
connection.setRequestMethod("POST");
// prints {"users":[{"property1":"x","property1":y}]}
System.out.println(objectMapper.writeValueAsString(new MapperList(users)));
objectMapper.writeValue(connection.getOutputStream(), objectMapper.writeValueAsString(new MapperList(users)));
//blabla ...
}
and this is the object containing my list :
public class MapperList implements Serializable {
private static final long serialVersionUID = 8561295813487706798L;
private ArrayList<User> users;
public MapperList() {}
public MapperList(ArrayList<User> users) {
this.setUsers(users);
}
public ArrayList<User> getUsers() {
return users;
}
public void setUsers(ArrayList<User> users) {
this.users = users;
}
}
and this is the users type to post:
public abstract class User implements Serializable {
private static final long serialVersionUID = -1811485256250922102L;
private String property1;
private String property2;
public User() {}
public User(String prop1, String prop2) {
// set properties
}
// getters and setters
}
the problem is, when I output the value of the users's array before to post it to the controller, I got the following json value :
{"users":[{"property1":"x","property1":y}]}
but in the controller, when I print what I get from the request body, I only get :
{"users":null}
I also tryed with the annotation #RequestBody instead of #ModelAttribute("mapperList") and a JSONException is displayed :
*A JSONObject text must begin with '{' at 1 [character 2 line 1]\r\n*
My array list of users contains only one user that should be displayed. I don't understand why this doesn't work...
Thanks for any help !
You can chnage your MapperList class definition as public class MapperList extends ArrayList<User>{ ..} you dont need to define any instance variable like private ArrayList users inside MapperList class. Use #Requestbody annotation. You will be able to use MapperList as a ArrayList
Try to use:
public class MapperList{
private List<User> users;
//setter and getter
//toString
}
public class User{
private String property1;
private String property2;
//getter + setter
}
json:
{"users":[{"property1":"x", "property2":"y"}]}
in controller use #RequestBody. In that case Jackson will map your json to ArrayList of users.
#ResponseStatus(HttpStatus.OK)
#RequestMapping("/postUsers.do")
public #ResponseBody ResponseDTO postUsers(#RequestBody MapperList users) {
System.out.println(users);
return null;
}
no need to get objectMapper in that case. Don't forget to set content-type in request header to application/json. It required by Spring to handle #RequestBody processing.
If not working try to change MapperList:
List<User> users = new ArrayList<User>();
On the server side keep the #RequestBody annotation:
public #ResponseBody ResponseDTO postUsers(#RequestBody MapperList mapperList)
...
But this line causes problems:
objectMapper.writeValue(
connection.getOutputStream(),
objectMapper.writeValueAsString(new MapperList(users))
);
First it converts the object to JSON and then again uses objectMapper to JSON-encode the string into output stream. Try the following instead:
connection.getOutputStream().write(
objectMapper.writeValueAsString(new MapperList(users))
.getBytes("UTF-8")
);
or directly output to stream:
objectMapper.writeValue(
connection.getOutputStream(),
new MapperList(users))
);
Zbynek gave me part of the answer. Indeed
objectMapper.writeValue(
connection.getOutputStream(),
objectMapper.writeValueAsString(new MapperList(users))
);
doesn't work properly in my case
But moreover, my User class was an abstract class, with many type of User as subclasses. so the #RequestBody annotation couldn't work without specified the object type in the Json.
I used the following annotations on User class to make it working :
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(value = SubClassA.class, name = "a"),
#JsonSubTypes.Type(value = SubClassB.class, name = "b")
})
Thanks a lot for all your answers.

Change property names while deserialzing class to JSON in Spring MVC

I'm trying to consume a rest API call using Spring as below:
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", "Basic " + base64Creds);
HttpEntity<String> request = new HttpEntity<String>(headers);
RestTemplate restTemplate = new RestTemplate();
Item item = restTemplate.exchange(url, HttpMethod.GET, request, Item.class).getBody();
The response I get from the API is in following form:
{
"item":[{
"itemname": "abc",
"qty":...
}]
}
The Item class has following fields:
Class Item{
#JsonProperty("itemname")
String name;
#JsonProperty("qty")
int quantity;
// Getter / setter methods
}
I've added JsonProperty annotations to the fields as their names are different from the json I get from the API. With this, I'm able to deserialize the api response successfully.
However, when I try to serialize the Item class again as a json, the field names are "itemname" and "qty". Is there any way to keep these as "name" and "quantity", and yet be able to map to the API response?
Thanks in advance.
If you just want to serialize in different form, you can do it like this:
public static class Item {
private String name;
private int quantity;
#JsonProperty("name")
public String getName() {
return name;
}
#JsonProperty("itemname")
public void setName(String name) {
this.name = name;
}
#JsonProperty("quantity")
public int getQuantity() {
return quantity;
}
#JsonProperty("qty")
public void setQuantity(int quantity) {
this.quantity = quantity;
}
}
This would read "{"itemname": "abc", "qty":10 }" and write "{"name": "abc", "quantity":10 }".
But there is a big drawback - you wont be able to read "{"name": "abc", "quantity":10 }", with this ObjectMapper (This is worse possible solution).
You can use 2 ObjectMappers and instead of class Annotations use Mixins, to configure specific deserialization
This is how your Mixin would look like:
abstract public static class ItemMixin {
ItemMixin(#JsonProperty("itemname") String itemname, #JsonProperty("qty") int qty) { }
// note: could alternatively annotate fields "w" and "h" as well -- if so, would need to #JsonIgnore getters
#JsonProperty("itemname") abstract String getName(); // rename property
#JsonProperty("qty") abstract int getQuantity(); // rename property
}
Here is how to add Mixin in ObjectMapper.
objectMapper.addMixIn(Item.class, ItemMixinA.class);
So if you Deserialize with Mixin ObjectMapper, and serialize with standard ObjectMapper there will be no problem.
You can write custom JsonDeserialization for your class.
It's easy to do for class with few fields, but complexity would grow proportionally as number of fields grows.
Try using #JsonAlias annotation to specify the other property names that can be used for deserializing the object. More information can be gotten from here:
https://fasterxml.github.io/jackson-annotations/javadoc/2.9/com/fasterxml/jackson/annotation/JsonAlias.html

Resources