Let's say that I have a simple data class in Java:
public class Person {
private final String name;
private final int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
int String getAge() {
return age;
}
}
Note: in practice, I use Immutables to generate this, but am showing a POJO here for the sake of simplicity.
To document the model of the GET
response, even if the return type is Response
, I can refer to the class in @ApiOperation
:
@GET
@ApiOperation(response = Person.class)
public Response getPerson() {
return Response.ok(new Person("Julien", 28)).build();
}
Based on that, Swagger will document this correctly:
Model:
Person { name (string), age (number) }
Example value:
{ "name": "string", "age": 0 }
To document the model of the POST
body, I use the class directly in the code, Swagger finds it and documents it as desired:
@POST
@ApiOperation(response = Person.class)
public Response addPerson(Person newPerson) {
return Response.ok(store.insert(newPerson)).build();
}
I want to support partial updates as well. I cannot use the POJO itself, because all fields are mandatory in the POJO, I rely on that to get safe checks and clear error messages when invalid JSON is sent to e.g. the POST
method.
In my actual use-case, my datamodel contains maps. I want users to be able to specify a certain key in the update and set the value to null
, to delete elements from an existing maps.
I chose to support PATCH
requests where the body is typed as a plain JsonNode
. That allows any JSON to be received by my server and I can apply the updates as I wish.
@PATCH
@Path("/{name}")
@ApiOperation(response = Person.class)
public Response updatePerson(@PathParam("name") String name, JsonNode update) {
return Response.ok(store.update(name, update)).build();
}
I'm happy with the result, except that Swagger now documents the partial update's model with the properties of the JsonNode
Java object:
Model:
JsonNode { array (boolean, optional), null (boolean, optional), number (boolean, optional), float (boolean, optional), pojo (boolean, optional), valueNode (boolean, optional), containerNode (boolean, optional), object (boolean, optional), missingNode (boolean, optional), nodeType (string, optional) = ['ARRAY', 'BINARY', 'BOOLEAN', 'MISSING', 'NULL', 'NUMBER', 'OBJECT', 'POJO', 'STRING'], integralNumber (boolean, optional), floatingPointNumber (boolean, optional), short (boolean, optional), int (boolean, optional), long (boolean, optional), double (boolean, optional), bigDecimal (boolean, optional), bigInteger (boolean, optional), textual (boolean, optional), boolean (boolean, optional), binary (boolean, optional) }
Example value:
{ "array": true, "null": true, "number": true, "float": true, "pojo": true, "valueNode": true, "containerNode": true, "object": true, "missingNode": true, "nodeType": "ARRAY", "integralNumber": true, "floatingPointNumber": true, "short": true, "int": true, "long": true, "double": true, "bigDecimal": true, "bigInteger": true, "textual": true, "boolean": true, "binary": true }
I would like to specify in my code that the model is like Person
, so that the example given in the Swagger UI is more relevant. I did try @ApiImplicitParams
:
@PATCH
@Path("/{name}")
@ApiOperation(response = Person.class)
@ApiImplicitParams({
@ApiImplicitParam(name = "update", dataTypeClass = Person.class)
})
public Response updatePerson(@PathParam("name") String name, JsonNode update) {
return Response.ok(store.update(name, update)).build();
}
That did not make any difference. Swagger still documents JsonNode
itself. The documentation for @ApiImplicitParams
mentions:
While ApiParam is bound to a JAX-RS parameter, method or field, this allows you to manually define a parameter in a fine-tuned manner. This is the only way to define parameters when using Servlets or other non-JAX-RS environments.
Since I am using JAX-RS, this might mean that I cannot use @ApiImplicitParams
, but @ApiParam
does not provide anything to override the class.
How can I specify manually the datamodel of a JAX-RS parameter that is detected by Swagger automatically?
Adding this answer to make it somewhat generic for better understanding of @ApiImplicitParams
.
You would have to use @ApiImplicitParams
for wrapping the parameters as you wish to save in the documentation.
@ApiImplicitParam
has many not-so-obvious benefits, like to pass in extra header param without adding it as a method param or in your case wrap the the params so as to make them some sense.
For your problem, you would have to use @ApiImplicitParam
along with paramType = "body"
as you want to make changes in the body, similarly paramType = "head"
if you wanted changes in header.
You can also control the mandatory fields in @ApiImplicitParam
with property required = true/false
.
As stated earlier, you can pass a param without having it in the method param, you can control its value using the property value = "required value"
.
You can also control the allowable values in @ApiImplicitParam
using comma seperated values. Eg.allowableValues = "no-cache, no-store"
.
I was on the right track with @ApiImplicitParam
and I think I got it working the way I want, now:
@PATCH
@Path("/{name}")
@ApiOperation(response = Person.class)
@ApiImplicitParams({
@ApiImplicitParam(paramType = "body", dataTypeClass = ExchangeConfiguration.class)
})
public Response updatePerson(@PathParam("name") String name, @ApiParam(hidden = true) JsonNode update) {
return Response.ok(store.update(name, update)).build();
}
Configuring a name was not mandatory, but paramType
seems to be required. Using paramType = "body"
makes Swagger document the implicit parameter correctly. That would cause the body to be documented twice; we can hide the automatically generated version with the wrong model, using @ApiParam(hidden = true)
.
With the code above, the documentation looks exactly as I wanted, and the code continues to behave correctly.
Thanks all for the help!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With