I am trying to create a proper, REST API, and document it with Swagger (2.0).
So, I have an API call that is a query, ie, it makes no changes and doesn't create anything (idempotent and safe). But it requires passing in a complex JSON parameter (list of items, 2 or 3 sets of addresses, etc). So I'm doing a GET with a parameter thats URL encoded JSON. That seems like the proper way to do it.
I see so often API's like this where they do it as a POST for this reason, but that's an incorrect use of the POST verb.
I'm seeing lots of swagger API's that do this...
I can't figure out if there's a way to do a proper rest API with Swagger, using a JSON parameter. You can define the parameter as a string, of course, and pass your encoded JSON into it, but then the swagger tooling doesn't understand that there's a schema/definition for it.
Is swagger not able to properly document this kind of call?
To answer your question, yes you may pass JSON in the URI as part of a GET request (provided you URL-encode).
OpenAPI 2.0 does not support objects in query strings, it only supports primitive values and arrays of primitives. The most you can do is define your parameter as type: string
, add an example
of a JSON value, and use description
to document the JSON object structure.
swagger: '2.0'
...
paths:
/something:
get:
parameters:
- in: query
name: params
required: true
description: A JSON object with the `id` and `name` properties
type: string
example: '{"id":4,"name":"foo"}'
JSON in query string can be described using OpenAPI 3.x. In OAS 3, query parameters can be primitives, arrays as well as objects, and you can specify how these parameters should be serialized – flattened into key=value
pairs, encoded as a JSON string, and so on.
For query parameters that contain a JSON string, use the content
keyword to define a schema
for the JSON data:
openapi: 3.0.1
...
paths:
/something:
get:
parameters:
- in: query
name: params
required: true
# Parameter is an object that should be serialized as JSON
content:
application/json:
schema:
type: object
properties:
id:
type: integer
name:
type: string
This corresponds to the following GET request (before URL encoding):
GET /something?params={"id":4,"name":"foo"}
or after URL encoding:
GET /something?params=%7B%22id%3A4%2C%22name%22%3A%22foo%22%7D
Note for Swagger UI users:
Parameters with content
are supported in Swagger UI 3.23.8+ and Swagger Editor 3.6.34+.
Workaround for earlier versions of UI/Editor:
Define the parameter as just type: string
and add an example
of the JSON data. You lose the ability to describe the JSON schema for the query string, but "try it out" will work.
parameters:
- in: query
name: params
required: true
schema:
type: string # <-------
example: '{"id":4,"name":"foo"}' # <-------
For .Net and Swashbuckle (tested on 3.0) I have a generic class JsonModelBinder that implements IModelBinder interface. The class is used like this:
public IActionResult SomeAction(
[FromRoute] int id,
[FromQuery][ModelBinder(BinderType = typeof(JsonModelBinder<SomeModel>))] SomeModelquery query) => {}
I have created Operation filter that does the following:
As a result in Swagger I have a text field where I can insert json and test requests
public class JsonModelBinderOperationFilter : IOperationFilter
{
public void Apply(Operation operation, OperationFilterContext context)
{
if (operation.Parameters == null || context.ApiDescription.HttpMethod != HttpMethod.Get.ToString())
return;
//Find json parameters
var jsonGetParameters = context.ApiDescription.ActionDescriptor.Parameters.Cast<ControllerParameterDescriptor>()
.Where(p => p.ParameterInfo.CustomAttributes.Any(c => c.AttributeType == typeof(ModelBinderAttribute) && c.NamedArguments.Any(IsJsonModelBinderType))).ToArray();
if (jsonGetParameters.Length > 0)
{
//Select parameters names created by Swagger from json parameters
var removeParamNames = new HashSet<string>(context.ApiDescription.ParameterDescriptions.Where(d => jsonGetParameters.Any(p => p.Name == d.ParameterDescriptor.Name)).Select(p => p.Name));
//Create new Swagger parameters from json parameters
var newParams = jsonGetParameters.Select(p => new NonBodyParameter()
{
In = "query",
Name = p.Name,
Type = "string",
Description = "Json representation of " + p.ParameterType.Name
});
//Remove wrong parameters and add new parameters
operation.Parameters = operation.Parameters.Where(p => p.In != "query" || !removeParamNames.Contains(p.Name)).Concat(newParams).ToList();
}
}
private static bool IsJsonModelBinderType(CustomAttributeNamedArgument arg)
{
var t = arg.TypedValue.Value as Type;
return t != null && t.GetGenericTypeDefinition().IsAssignableFrom(typeof(JsonModelBinder<>));
}
}
Notes:
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