swagger ui for Map attribute in Jax-Rs Jersey application - swagger

How to specify the swagger documentation annotation for generating model schema for Map attribute in a Java bean?
//groovy code
class Foo {
int id
String description
Map<String, Bar> targets
//few other attributes
}
For targets attribute, swaggerui shows 1 line but does not give the model schema for Bar class.
targets (Map[string,Bar])
How do I get Bar class Model schema as well in UI?

Related

How to sort the Schemas on Swagger-ui SpringDoc open ui

I want to sort my Schemas generated for my Entity classes, DTO classes in Springdoc UI.
I am able to sort the tags and operations using the below configuration in yml file but my schemas are not in the sorted order.
springdoc:
swagger-ui:
disable-swagger-default-url: true
tags-sorter: alpha
operations-sorter: alpha
doc-expansion: none
How could I sort my schemas.
Thanks.
You can have full control of the schemas order using OpenApiCustomiser.
This is a sample code that you can customize using Comparators, depending on the sorting logic you want:
#Bean
public OpenApiCustomiser sortSchemasAlphabetically() {
return openApi -> {
Map<String, Schema> schemas = openApi.getComponents().getSchemas();
openApi.getComponents().setSchemas(new TreeMap<>(schemas));
};
}
If you are interested on the sorting on the swagger-ui, not on the server side, then you can log a feature request on the swagger-ui project.

Create one code client / flatten controllers with NSwag and AutoREST

I'm trying to create a code wrapper for an api with NSwag and Autorest.
Previously I was using Swashbuckle to generate the swagger file. It generated the swagger file with operationIds in the format actionMethod. This resulted in Autorest generating a code client that was 1-deep. All of the actions were on the top-level class.
For various reasons, I needed to change swagger generation to NSwag. This generates operationIds in the format controller_actionMethod. This results in AutoRest creating a composite class that exposes separate classes with actions for each controller.
How can either
Change how NSwag generates the operationIds
Change how Autorest maps operationIds
Note: I know I can manually change the swagger.json, but I'd like to keep a consistent automated process for generating the code client.
There doesn't appear to be any readily available settings, but you can hook into the generation process of NSwag
https://github.com/RicoSuter/NSwag/wiki/Document-Processors-and-Operation-Processors#operation-processors
The operation processor
class FlattenOperationsProcessor: IOperationProcessor
{
public async Task<bool> ProcessAsync(OperationProcessorContext context)
{
context.OperationDescription.Operation.OperationId = $"{context.MethodInfo.Name}";
return true;
}
}
Then add it in Startup.cs
document.OperationProcessors.Add(new FlattenOperationsProcessor());
Not sure if this was available when the question was asked, but here is a pretty easy way to do it:
services.AddSwaggerGen(c =>
{
...
c.CustomOperationIds(d => d.ActionDescriptor is ControllerActionDescriptor controllerActionDescriptor ? controllerActionDescriptor.MethodInfo.Name : d.ActionDescriptor.AttributeRouteInfo?.Name);
});
The same can also be set via c.SwaggerGeneratorOptions.OperationIdSelector
Note that ActionDescriptor.AttributeRouteInfo?.Name is the default I used from the source code here

Grails json views : opaque view selection - picks out default template in another directory

I am using Grails 3.3.9 and json views 1.2.10.
In another test I have a url mapping like this
"/api/test"(resources:'test')
And I have an empty views/test folder, and I don't have a Test domain object.
But I do have a Device domain entity, and have a views/device/_device.gson template in that directory to help render a device object.
The TestController's show action looks like this
def show (Device device) {
if(device == null) {
render status:404
} else {
respond device}
}
This looks for a Device in domain model and populates into Shows parameter list, and I respond with the device.
There is however no show.gson in the views/test directory (it's empty).
However when you hit the url in the browser "http://localhost:8080/api/test/1", grails renders a response using the "views/device/_device.gson" template.
This is very confusing as it's very hard to figure exactly view view respond is actually using (unless you force it using a view:'xxx' map variable.
How/why is the TestController/show action response return, picking up "views/device/_device.gson" template ?
(Posted answer on behalf of the question author).
Step 1) if you create a new controller (I used grails create-controller <controller>, and then edit the generated controller to extend from RestfulController<domain type> (but don't override any methods). Then Setup the URL mapping for your controller, in my example this was
"/api/org"(resources:'OrgRoleInstance')
for which I have the corresponding domain class 'OrgRoleInstance' in my model.
The create-controller action also generates an empty view package 'views/orgRoleInstance' (no gson files are generated)
a) if you do the run app now and open the url
http://localhost:8080/api/org
then your still get a rendered response (like this) !
[{"id":1,"sites":[],"domains":[],"role":{"enumType":"com.softwood.domain.OrgRoleInstance$OrgRoleType","name":"Service_Provider"},"name":"Vodafone","mags":[{"id":2}]},{"id":2,"sites":[],"domains":[],"role":{"enumType":"com.softwood.domain.OrgRoleInstance$OrgRoleType","name":"Maintainer"},"name":"Cisco","mags":[{"id":1}]},{"id":3,"sites":[],"domains":[],"role":{"enumType":"com.softwood.domain.OrgRoleInstance$OrgRoleType","name":"Supplier"},"name":"Cisco","mags":[]},{"id":4,"sites":[{"id":1},{"id":2}],"domains":[{"id":1}],"role":{"enumType":"com.softwood.domain.OrgRoleInstance$OrgRoleType","name":"Customer"},"name":"Acme","mags":[]}]
Ipso facto there is some 'default framework logic' that tries to render the default implementation of the 'index' (and 'show' etc) action inherited from RestfulController'. This is done even with with no pre-existing gson - no warnings are provided.
Step 2: next stop the app (it's still caching the previous 'default' behaviour).
Now go to the empty view directory and create an 'index.gson' like this with no model
json ([1,2,3])
Then restart the app in your IDE. Now when it starts, it detects that an index.gson exists, and when you call the url 'http://localhost:8080/api/org' what you get is the rendered json for the static list [1,2,3]
step 3: gson views are using static compilation under the covers and very fiddly to get exactly right. However in the case of default RestfulController index action , a list of OrgRoleInstances is selected from the domain model and passed as a lit to the view. if you want the databinding to the view to work, then if your domain object is type T, the you get a List<T> returned.
The internal default in the code base, is if you get List<T> returned to respond then, the data model in the view is assumed to be List<T> <T>List i.e. in my example this would be
List<OrgRoleInstance> orgRoleInstanceList in the gson view model. Now with a revised index.gson (you can edit this without stop/start of server in dev mode)
import com.softwood.domain.OrgRoleInstance
model {
List<OrgRoleInstance> orgRoleInstanceList
}
json {
recordCount orgRoleInstanceList.size()
}
Now when you Get the URL you get a json response with the size of the list:
{"recordCount":4}
Note that if you add an extra variable to your model like this:
import com.softwood.domain.OrgRoleInstance
model {
List<OrgRoleInstance> orgRoleInstanceList
Integer orgRoleInstanceCount
}
json {
recordCount orgRoleInstanceList.size()
size orgRoleInstanceCount
}
and modify the index.gson to print the new Integer variable - then it is not data bound from the default respond action.
Browser response looks like this:
{"recordCount":4,"size":null}
Option 4: - look at invoking a 'template' class. Having set up a model that's expecting a List<T>, the runTime type built via data binding is an instance of "grails.orm.PagedResultList". This is an Iterable type.
This is very confusing to read in the documentation - it's really not clear however if you create a file called _<file>.gson. Then this is treated is a template gson file.
This can have its own model/json, but because all this is statically compiled you have to get the types exactly matching and where necessary declare the 'name' of the model variable when calling the template (either via g.render or implicit tmpl.<file>.
When you call the template from the parent view you can pass either an the List<T> iterable model type or iterate in the parent view and pass each <T> to the template. You have to ensure that the model type you pass to the tmpl is declared as the same type in the tmpl model.
e.g. assuming a List<T> in the parent view but a <T> in the template, you need to invoke the tmpl for each item in the list. e.g.
if you have a tmpl like this "_orgRoleInstance.gson".
If you have the parent view like this (note single <T> declared and model variable named 'org'
import com.softwood.domain.OrgRoleInstance
model {
OrgRoleInstance org
}
json {
id org.id
name org.name
}
Then the parent view "index.gson" you need something like this that invokes the tmpl as many times as you have entries in the list, but I've had to tell the framework that the tmpl model variable name is 'org' by passing a map. This will render as you expect.
import com.softwood.domain.OrgRoleInstance
model {
List<OrgRoleInstance> orgRoleInstanceList
Integer orgRoleInstanceCount
}
orgRoleInstanceList.each { OrgRoleInstance org ->
json tmpl.orgRoleInstance(org:org)
}
If you declare the tmpl variable to be "def org", it still works but as this is statically typed, the variable is passed as static instance of Object (run time type is correct, but you cant just access the properties as the static type is Object), and it gets hard sorting out the casts required to access the properties.
If you want the parent view to pass the model List<T> variable to the tmpl, you can = but you have to ensure that the model variable in the tmpl is List<T> else the data binding doesn't work.
Now in the template you can invoke the json and iterate over the List
e.g. with a modified tmpl like this
import com.softwood.domain.OrgRoleInstance
model {
//OrgRoleInstance org
List<OrgRoleInstance> orgs
}
json {
id orgs.id
name orgs.name
}
and a revised parent view like this to invoke the tmpl:
import com.softwood.domain.OrgRoleInstance
model {
List<OrgRoleInstance> orgRoleInstanceList
Integer orgRoleInstanceCount
}
/*
orgRoleInstanceList.each { OrgRoleInstance org ->
json tmpl.orgRoleInstance(orgs:org)
}*/
//alternate approach
json tmpl.orgRoleInstance (orgs:orgRoleInstanceList)
What get rendered is this:
{"id":[1,2,3,4],"name":["Vodafone","Cisco","Cisco","Acme"]}
You will note that there is a single json clause so it iterates all the ids first and the all the names second.
If this is not what you want you have to iterate over the list to do each in turn
i.e. a modified tmpl like this will iterate over each entry in turn
import com.softwood.domain.OrgRoleInstance
model {
//OrgRoleInstance org
List<OrgRoleInstance> orgs
}
json (orgs) {OrgRoleInstance org ->
id org.id
name org.name
}
and produce this in the browser
[{"id":1,"name":"Vodafone"},{"id":2,"name":"Cisco"},{"id":3,"name":"Cisco"},{"id":4,"name":"Acme"}]
For anyone else who is confused by Grails Views, I hope this shows how this works between url mappings (you can use the Gradle "urlMappingsReport" to see these) and shows what urls are mapped to what actions. You then need gson views created with the same name as these actions, and configure your gson views and any tmpl you create to be aware of the implicit behaviour that the JsonViews applies when processing your code.

Grails - filter declared properties from those that mixin seem to add

I'm trying to create a mixin that transforms objects into some other type using using their properties. The mixin looks something like,
class MyMixin {
MyModel transform() {
MyModel model = new MyModel()
this.properties.each { key, value ->
model.addToList(key, value)
}
return model
}
}
Sample usage,
#Mixin(MyMixin)
class OtherModel {
Integer number
String description
}
However, it seems like some properties are getting added under the hood. When I test the number of entries added to the list, it's more that expected. Instead of just having 2 here, I'm getting 5.
I'm ok with the mixin adding properties in the background, but if that's the case, I want to know a way to check declared properties.
For illustration, checkout the test/mixin branch of my sample project in GitHub.
In particular, see the files:
PropertyMixin.groovy
PropertyMixinSpec.groovy

Make JSON.NET and Serializable attribute work together

I'm using JSON.NET and had some troubles in the past during WEBAPI objects deserialization. After doing some research I've found that the class was marked with [Serializable]. When I removed this the deserialization was just fine.
More detailed information about this can be found here:
Why won't Web API deserialize this but JSON.Net will?
Now it comes to the problem that I use binaryformatter to create a hash value calculated from this object class.
But Binaryformatter requires that the class must be marked as [Serializable].
Could you recommend me any approach to make both things work at the same time?
Found the solution:
First, check that your Newtonsoft.JSON version is greater than 4.5 or just update with NuGET
According to the version notes, both can work together starting from this version using some extra annotations.
http://james.newtonking.com/archive/2012/04/11/json-net-4-5-release-2-serializable-support-and-bug-fixes
"Now if you are serializing types that have the attribute and don’t want the new behaviour, it can either be overridden on a type using the JsonObjectAttribute"
[JsonObject]
[Serializable]
public class Foobar {
Now it is possible to use JSON.NET and, in my case, the binaryformatter with the [Serializable] attribute.
An alternative to specifying JsonObject on each class is to tell web.api to ignore Serialize attributes globally. This can be done by resetting the DefaultContractResolver on the web api JsonFormatter:
config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new DefaultContractResolver();
(using NewtonSoft.Json.Serialization where config is the System.Web.Http.HttpConfiguration)
As of NewtonSoft v4.5 the IgnoreSerializableAttribute property on the DefaultContractResolver is set to true but the web api wrapper, around DefaultContractResolver, has this set to false by default.
I was using a POCO with Serializable attribute. In the first case while Posting Request to a WebApi worked by using the following method:
JsonMediaTypeFormatter f = new JsonMediaTypeFormatter()
{
SerializerSettings = new JsonSerializerSettings()
{
ContractResolver = new DefaultContractResolver()
{
IgnoreSerializableAttribute = true
}
}
};
var result = client.PostAsJsonAsync<IEnumerable<Company>>("company/savecompanies", companies).Result;
//I have truncated the below class for demo purpose
[Serializable]
public class Company
{
public string CompanyName {get;set;}
}
However, when I tried to read the response from WebApi (Which was posted back as JSON), the object was not properly deserialized. There was not error, but property values were null. The below code did not work:
var readObject = result.Content.ReadAsAsync<IEnumerable<Company>>().Result;
I read the documentation as given on Newtonsoft.Json website https://www.newtonsoft.com/json/help/html/SerializationAttributes.htm and found the following and I quote from that site:
Json.NET attributes take precedence over standard .NET serialization
attributes (e.g. if both JsonPropertyAttribute and DataMemberAttribute
are present on a property and both customize the name, the name from
JsonPropertyAttribute will be used).
So, it was clear if Newtonsoft.Json attributes are present before the standard .NET attributes they will take precedence. Hence I could use the same class for two purposes. One, when I want to post to a WebApi, Newtonsoft Json serializer will kick in and Two, when I want to use BinaryFormatter.Serialize() method std .NET Serializable attribute will work.
The same was confirmed with the answer given above by #Javier.
So I modified the Company Class as under:
[JsonObject]
[Serializable]
public class Company
{
public string CompanyName {get;set;}
}
I was able to use the same class for both purposes. And there was no need for the below code:
JsonMediaTypeFormatter f = new JsonMediaTypeFormatter()
{
SerializerSettings = new JsonSerializerSettings()
{
ContractResolver = new DefaultContractResolver()
{
IgnoreSerializableAttribute = true
}
}
};

Resources