I am using Swagger Core 2.0.2 for Java to generate an OpenAPI documentation. Among others, I have the following class SomeDTO:
#Schema(name = "SomeDTO", description = "some description")
public class SomeDTO {
#Schema(description = "description of name")
private String name;
#Schema(required = true, description = "description of OtherDTO")
private OtherDTO otherDTO;
}
OtherDTO is described as follows:
public class OtherDTO {
#Schema(required = true)
private String someField;
private String someOtherField;
}
My problem is that neither the description nor the required field above the otherDTO field has any effect.
The resulting openapi.json looks like this:
"components": {
"schemas": {
"SomeDTO" : {
"type": "object",
"properties": {
"name": {
"type" : "string"
}
"otherDTO" : {
"$ref": "#/components/schemas/OtherDTO"
}
},
"description": "some description"
},
"OtherDTO": {
"required": ["someField"],
"type": "object",
"properties": {
"somefield": {
"type": "string"
},
"someOtherField": {
"type": "string"
}
}
}
}
}
I was expecting the SomeDTO schema to have a required array containing OtherDTO, which it does not. The description is also lost.
I have tried numerous combinations of Schema settings, to no avail. I would highly appreciate any help to understand what I am doing wrong.
Thanks in advance.
I have found a solution to part of my problem.
The problem is caused by the fact that when using a $ref element, sibling elements are ignored. So elements (description, name etc.) related to the referenced element need to be specified as a #Schema in the referenced object itself (OtherDTO in the example above). Specifying these elements in the parent object (e.g. SomeDTO) will leave them ignored.
However, the schema elements in the referenced element do not seem to propagate up to the parent object. So, to make otherDTO a required field in SomeDTO, I need to add requiredProperties = { "OtherDTO" }) to SomeDTO's schema.
Here is the updated code:
SomeDTO
#Schema(name = "SomeDTO", description = "some description",
requiredProperties = { "OtherDTO" })
public class SomeDTO {
#Schema(description = "description of name")
private String name;
private OtherDTO otherDTO;
}
OtherDTO
#Schema(name = "OtherDTO", description = "Description of OtherDTO")
public class OtherDTO {
#Schema(required = true)
private String someField;
private String someOtherField;
}
However, it does not solve my problem completely, as I still can't figure out how to set the description of otherDTO in SomeDTO. But it gets me a step closer.
Related
I would like to add an example with Swagger in my method, I have tried a few things, but they didn't work.
I have my Interface, where I define the method:
#Api(value = "test API")
#RequestMapping("/api/v1/product")
public interface TestController {
#ApiOperation(
value = "Service that return a Product",
notes = "This service returns a Product by the ID",
nickname = "getProductById",
response = ProductResponse.class)
#ApiResponses(value = {
#ApiResponse(code = 200, message = "The request has succeeded.", response = ProductResponse.class),
#ApiResponse(code = 500, message = "Internal server error.", response = ProductResponse.class) })
#GetMapping(
value = "/productById",
produces = { "application/json" }
)
ResponseEntity<ProductResponse> getProductById(#RequestParam(value = "productId", required = true) String productId);
The ProductResponse class is the following:
#Getter
#Setter
#AllArgsConstructor
public class ProductResponse {
private Product product;
private CustomException customException;
}
The Product class is the following:
#Getter
#Setter
#AllArgsConstructor
public class Product {
#JsonProperty("id")
private String id;
#JsonProperty("productName")
private String productName;
#JsonProperty("productDescription")
private String productDescription;
#JsonProperty("unitPrice")
private Double unitPrice;
And the CustomException class is the following:
#Getter
public class CustomException {
private final String message;
private final String errorCode;
private final String errorType;
private final Exception exceptionDetail;
public CustomException(String message, String errorCode, String errorType, Exception exceptionDetail) {
this.message = message;
this.errorCode = errorCode;
this.errorType = errorType;
this.exceptionDetail = exceptionDetail;
}
When the response is 200, the response is like:
{
"product": {
"id": "12345",
"productName": "Product name",
"productDescription": "This is a description",
"unitPrice": 3.25
},
"customException": null
}
But when the response is 500, the response is like:
{
"product": "null,",
"customException": {
"message": "/ by zero",
"errorCode": "500",
"errorType": "Internal server error",
"exceptionDetail": null,
"cause": null,
"stackTrace": [
{
"classLoaderName": "app",
"moduleName": null,
"moduleVersion": null,
"methodName": "getProductById",
"fileName": "TestControllerImpl.java",
"lineNumber": 33,
"className": "com.myproject.testmicroservice.controller.impl.TestControllerImpl",
"nativeMethod": false
}
]
}
}
How can I add a custom example in the #ApiResponse annotation?
You are probably missing the #Operation annotation, where inside you put the #ApiResponse.
Example:
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.Operation;
#Operation(responses = {
#ApiResponse(responseCode = "200", content = #Content(examples = {
#ExampleObject(name = "getUserAttribute",
summary = "Retrieves a User's attributes.",
description = "Retrieves a User's attributes.",
value = "[{\"value\": [\"area1\", \"area2\", \"area3\"], \"key\":\"GENERAL_AREAS\"}, {\"value\":\"933933933\", \"key\":\"FONyE\"}]")
}, mediaType = MediaType.APPLICATION_JSON_VALUE))})
public ResponseEntity<List<UserPreferenceDto>> getUserPreferenceByCode(
#Pattern(regexp = "\\w+") #PathVariable String userCode, #Parameter(hidden = true) Pageable pageable) {
...
}
Good evening hope you are doing well. In the case you are describing, I would do something like this
#ApiResponses(value = {
#ApiResponse(responseCode = "200", description = "Found the book",
content = { #Content(mediaType = "application/json",
schema = #Schema(implementation = Book.class)) }),
#ApiResponse(responseCode = "400", description = "Invalid id supplied",
content = #Content),
the approach described is explained here. I think that paragraph 9. Generate Documentation Using #Operation and #ApiResponses is of particular interest in your case. I hope this helps, Have a good night
You can try something like this. In your controller you already have #ApiResponses annotation. What you need to do is add #ApiModel to your Product class and then add
#ApiModelProperty(notes = "Your comments", required = true, example = "example value")
to members of your Product class i.e. ProductResponse and CustomException. One thing that you will need to verify is whether #ApiModelProperty can be set on custom objects like ProductResponse and CustomException. If not you will need to set #ApiModelProperty 1 level deep.
As shown in article, the examples are auto populated from model property to response.
PS: As of now, I do not have setup of a swagger project so can only help you theoretically.
may be this late answer but incase any one need it, you can add the requestBody description along with content type within #Operation
#io.swagger.v3.oas.annotations.Operation(summary = "", description = "",
requestBody = #io.swagger.v3.oas.annotations.parameters.RequestBody(content = #Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
I need to deserialize JSON to java class.
I have JSON like the following:
{
"data": {
"text": "John"
},
"fields":[
{
"id": "testId",
"name": "fieldName",
"options": {
"color": "#000000",
"required": true
}
},
{
"id": "testId",
"name": "fieldName1",
"options": {
"color": "#000000",
"required": false
}
}
]
}
and I need to deserialize this JSON (only "fields" section) to java class like the following:
public class Field {
public final String id;
public final String name;
public final String color;
public final boolean required;
}
and I need to get something like the following:
// The key is the id from field object (it can be the same in the multiple objects.)
Map<String, List<Field>> fields = objectMapper.readValue(json, Map<String, List<Field>>);
How can I do it using Jackson?
As long as jackson doesn't support #JsonWrapped, you have to use the following work around.
First you need to create a custom class which contains the fields:
public class Fields {
public List<Field> fields;
}
Depending on your ObjectMapper configuration you have to add #JsonIgnoreProperties(ignoreUnknown = true) to the Fields class, to ignore any other properties.
Next is that you have to define the nested Options class which is solely used temporarily:
public class Options {
public String color;
public boolean required;
}
And at last add this constructor to your Field class:
#JsonCreator
public Field(#JsonProperty("id") String id, #JsonProperty("name") String name, #JsonProperty("options") Options options){
this.id = id;
this.name = name;
this.color = options.color;
this.required = options.required;
}
The #JsonCreator annotation indicates to jackson that this constructor needs to be used for the deserialization. Also the #JsonProperty annotations are required as arguments to constructors and methods are not preserved in the bytecode
Then you can deserialize your json just like this:
List<Field> fields = objectMapper.readValue(json, Fields.class).fields;
Problem
For example, I have a list of some products in my cart like cinema ticket, carsharing and new book.
[
{
"name": "Cinema Ticket"
},
{
"name": "Car Sharing",
"properties": { ... }
},
{
"name": "New Book",
}
]
As you can see, not all products have properties.
Note: this field is polymorphic.
Question
Can I turn "non-existing" field properties into null using Jackson or it's better to change api?
And if I can then how to do that?
Jackson Version: 2.10.1
Thanks for answers!
I suppose you have something like:
public class BaseProduct {
public String name;
}
public class CarSharing extends BaseProduct {
public String properties;
}
public class Book extends BaseProduct {
}
Instead of having the properties field set to null when is not there, you can use Jackson polymorphic functionality to not show it. Something like:
#JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS, include = JsonTypeInfo.As.PROPERTY, property = "type")
public class BaseProduct {
public String name;
}
The output would be:
[
{
"type": ".Book",
"name": "Book name"
},
{
"type": ".CarSharing",
"name": "Car share name",
"properties": "a property field"
}
]
I am using spring restTemplate to map a response to a POJO.
The response of the rest api is like this:
"attributes": {
"name": {
"type": "String",
"value": ["John Doe"]
},
"permanentResidence": {
"type": "Boolean",
"value": [true]
},
"assignments": {
"type": "Grid",
"value": [{
"id": "AIS002",
"startDate": "12012016",
"endDate": "23112016"
},{
"id": "AIS097",
"startDate": "12042017",
"endDate": "23092017"
}]
}
}
in the parent class, I have:
public class Users {
private Map<String, Attribute> attributes;
}
If all the values of were String type, then I could have done like:
public class Attribute {
private String type;
private String[] value;
}
But the values are of different types. So I thought of doing the following:
public class Attribute {
private String type;
private Object[] value;
}
The above should work, but at every step I have to find out what is the type of Object.
So, my question is can I have something like this:
public class Attribute {
#JsonProperty("type")
private String type;
#JsonProperty("value")
private String[] stringValues;
#JsonProperty("value")
private Boolean[] booleanValues;
#JsonProperty("value")
private Assignments[] assignmentValues; // for Grid type
}
But it is not working and throwing errors: Conflicting setter definitions for property "value"
What is the recommended way of handling this scenario?
I would recommend Jackson facilities for handling polymorphism here:
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(value = BooleanAttribute.class, name = "Boolean"),
#JsonSubTypes.Type(value = StringAttribute.class, name = "String")
})
class Attribute {
private String type;
}
class BooleanAttribute extends Attribute {
private Boolean[] value;
}
class StringAttribute extends Attribute {
private String[] value;
}
JsonTypeInfo tells Jackson that this is a base class and the type will be determined by a JSON field named "type"
JsonSubTypes maps subtypes of Attribute to values of "type" in JSON.
If you add an appropriate subtype for Assignments and getters/setters Jackson will be able to parse your JSON.
When producing the schema for a Java type I would like to specify that certain String values (or other types) could be null or "Nullable", for example, if there is a "name" property with this requirement I'd like to see something like this in the output:
"name" : {
"type" : ["string", "null"]
}
Additionally, what if I want to allow a value to be either integer or String? How can Jackson's schema generation be informed about it? I mean something like this:
"id" : {
"type" : ["string", "integer"]
"required" : true
}
But so far the things that I've attempted with Jackson are not working. I have the following method that at runtime will create a JsonSchema for the input type:
public String getJsonSchema(Class<Message> type) throws IOException{
ObjectMapper m = new ObjectMapper();
JsonSchemaGenerator generator = new JsonSchemaGenerator(m);
JsonSchema schema = generator.generateSchema(type);
return m.writerWithDefaultPrettyPrinter().writeValueAsString(schema);
}
Message.java is something like this (I removed the setters to keep the code shorter):
public class Message {
private String name;
private Long id;
#JsonProperty(value = "name", required = false)
#JsonPropertyDescription("User provided name.")
public String getName() {
return name;
}
#JsonProperty(value = "id", required = true)
public Long getId() {
return id;
}
}
The output for the previous bean is this:
{
"type" : "object",
"id" : "urn:jsonschema:spring:integration:quickstart:Message",
"properties" : {
"name" : {
"type" : "string",
"description" : "User provided name."
},
"id" : {
"type" : "integer",
"required" : true
}
}
}
The question is, what annotation or configuration option is required to make "null" one of the possible types for name?