Hi,
When using Swagger UI I am unable to get the Example Value to properly generate an example that contains the derived classes in the example.
[JsonConverter(typeof(EntityCustomJsonConverter))]
[KnownType(typeof(CompanyRequestModel))]
[KnownType(typeof(PersonRequestModel))]
public abstract class PartyBaseRequestModel : BaseObjectRequestModel
{
public EntityEnums.Parties.Role[] Roles { get; set; }
}
This BaseEntityCustomJsonConverter
public sealed class EntityCustomJsonConverter : BaseEntityCustomJsonConverter<BaseObjectRequestModel>
{
private const string EntityTypeKey = "EntityType";
protected override BaseObjectRequestModel Create(Type objectType, JObject jsonObject)
{
if (!jsonObject.TryGetValue(EntityTypeKey, StringComparison.InvariantCultureIgnoreCase, out var entityType))
{
return default;
}
switch (entityType.ToString().ToLowerInvariant())
{
case EntityTypes.Parties.Person:
return jsonObject.ToObject<PersonRequestModel>();
case EntityTypes.Parties.Company:
return jsonObject.ToObject<CompanyRequestModel>();
default:
throw new NotSupportedException();
}
}
}
``` public abstract class BaseEntityCustomJsonConverter
{
protected abstract T Create(Type objectType, JObject jsonObject);
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var jsonObject = JObject.Load(reader);
var target = Create(objectType, jsonObject);
if (target != null)
{
serializer.Populate(jsonObject.CreateReader(), target);
}
return target;
}
public override bool CanConvert(Type objectType)
{
return typeof(T).GetTypeInfo().IsAssignableFrom(objectType.GetTypeInfo());
}
}
```
Image of partial Model Example:

Swagger UI see's my inherited class in the Model's according:


What I would actually like to see in Swagger UI is this: (This image was taken from a YAML file created by me for another API that we will create, hence the different properties - What I like achieve does not change though)

Notice how "nodes" property contains an AnyOf with a list of models. How can I achieve this through code?
I am thinking either a ISchemaProcessor or IDocumentProcessor is needed but I could not find clear examples or help on how to achieve this.
I've taken a look at this. Even with the example given on the page; I don't get my expected outcome.
Give me a couple minutes and I'll show you.
As you see, the example value only contains the properties for the base class:


[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
// GET api/values/5
[HttpGet("{id}")]
public IActionResult Get(Container incoming)
{
return Ok();
}
}
public class Container
{
public Animal[] Animals { get; set; }
}
[JsonConverter(typeof(JsonInheritanceConverter), "discriminator")]
[KnownType(typeof(Dog))]
[KnownType(typeof(Cat))]
public class Animal
{
public string Foo { get; set; }
}
public class Dog : Animal
{
public string Bar { get; set; }
}
public class Cat : Animal
{
public string Meow { get; set; }
}
Startup.cs
app.UseSwaggerUi3WithApiExplorer(settings =>
{
settings.GeneratorSettings.DefaultEnumHandling = EnumHandling.String;
settings.GeneratorSettings.AllowReferencesWithProperties = true;
settings.GeneratorSettings.Title = "Screening API v1";
settings.SwaggerRoute = "/swagger/v1/swagger.json";
settings.SwaggerUiRoute = "/swagger";
});
I've updated my code example. The parameter on the controller is now set to Container, and I've made sure that Animal is now an array. I would expect to see the result in my previous image about anyOf.
swagger.json:
{
"x-generator": "NSwag v11.19.0.0 (NJsonSchema v9.10.72.0 (Newtonsoft.Json v11.0.0.0))",
"swagger": "2.0",
"info": {
"title": "Screening API v1",
"version": "1.0.0"
},
"host": "localhost:44379",
"schemes": [
"https"
],
"consumes": [
"application/json-patch+json",
"application/json",
"text/json",
"application/*+json"
],
"paths": {
"/api/Values/{id}": {
"get": {
"tags": [
"Values"
],
"operationId": "Values_Get",
"consumes": [
"application/json-patch+json",
"application/json",
"text/json",
"application/*+json"
],
"parameters": [
{
"name": "incoming",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/Container"
},
"x-nullable": true
},
{
"type": "string",
"name": "id",
"in": "path",
"required": true,
"x-nullable": false
}
],
"responses": {
"200": {
"x-nullable": true,
"description": "",
"schema": {
"type": "file"
}
}
}
}
}
},
"definitions": {
"Container": {
"type": "object",
"additionalProperties": false,
"properties": {
"Animals": {
"type": "array",
"items": {
"$ref": "#/definitions/Animal"
}
}
}
},
"Animal": {
"type": "object",
"discriminator": "discriminator",
"additionalProperties": false,
"required": [
"discriminator"
],
"properties": {
"Foo": {
"type": "string"
},
"discriminator": {
"type": "string"
}
}
},
"Dog": {
"type": "object",
"additionalProperties": false,
"properties": {
"Bar": {
"type": "string"
}
},
"allOf": [
{
"$ref": "#/definitions/Animal"
}
]
},
"Cat": {
"type": "object",
"additionalProperties": false,
"properties": {
"Meow": {
"type": "string"
}
},
"allOf": [
{
"$ref": "#/definitions/Animal"
}
]
}
}
}
Related PR: https://github.com/RSuter/NJsonSchema/pull/733
Related PR: RSuter/NJsonSchema#733
Edit: I think I can make something work after looking into NJsonSchema.Tests.Generation.InheritanceTests specifically with the FlattenInheritanceHierarchy set to true.
What I believe I need to achieve is use a processor to read the EntityType property if it exists, resolve the type by the value of the string, then generate a schema based off that type that contain the flatten properties from the inherited object. Finally, the schema then needs to be replaced.
Does all this seem achievable? What processor should I use - IDocumentProcessor or ISchemaProcessor?
So I'm making progress thanks for the selection of tests you have on Inheritance! 馃憤
In my UI I now see the following:
JSON representation shows, but is missing information I assume :)

Model example doesn't show the list of supported derived types:

Custom Schema Processor
public async Task ProcessAsync(SchemaProcessorContext context)
{
if (context.Type.Name is nameof(PartyBaseRequestModel))
{;
foreach (KnownTypeAttribute attribute in context.Type.GetCustomAttributes(typeof(KnownTypeAttribute), true))
{
var schema = await JsonSchema4.FromTypeAsync(attribute.Type, new JsonSchemaGeneratorSettings
{
FlattenInheritanceHierarchy = true
});
context.Schema.AnyOf.Add(schema);
}
//var json = context.Schema.ToJson();
}
}
Just FYI: AnyOf inheritance is not (yet) supported by the generator:
https://github.com/RSuter/NJsonSchema/issues/13
Also the Swagger spec only describes allOf inheritance.
You shouldnt use JsonSchema4.FromTypeAsync in a schema processor but context.SchemaGenerator, so that schemas are correctly added to definitions etc. but then you can't just change the settings (e.g. FlattenInheritanceHierarchy).
It seems that you want to completely customize inheritance handling, maybe you need an own implementation of JsonSchemaGenerator with some overrides...
So, after a bit of toying around, I was able to achieve one thing:
Having JsonSchema validate correctly based off my complex request. See code below:
if (context.Type.Name is nameof(PartyBaseRequestModel) || context.Type.Name is nameof(BaseObjectRequestModel))
{
var attributes = context.Type.GetCustomAttributes(typeof(KnownTypeAttribute), true) as Attribute[];
foreach (var attribute1 in attributes)
{
var attribute = (KnownTypeAttribute) attribute1;
var schema = await context.Generator.GenerateWithReferenceAndNullabilityAsync<JsonSchema4>(attribute.Type, attributes, context.Resolver,
async (p, s) => { p.AdditionalPropertiesSchema = s; });
context.Schema.Definitions.Add(attribute.Type.Name, schema);
}
context.Schema.AllowAdditionalProperties = true;
context.Schema.AllowAdditionalItems = true;
context.Schema.Properties.Clear();
}
Now, how do I proceed with possibly making SwaggerUI generate the proper examples? Would this custom JsonSchemaGenerator be the correct approach?

I think you shouldnt use GenerateWithReferenceAndNullabilityAsync when you add the schema to definitions (in definitions they need to be inline, not nullable or referenced)
We're just about there!

if (context.Type.Name is nameof(PartyBaseRequestModel) || context.Type.Name is nameof(BaseObjectRequestModel))
{
var attributes = context.Type.GetCustomAttributes(typeof(KnownTypeAttribute), true) as Attribute[];
foreach (var attribute1 in attributes)
{
var attribute = (KnownTypeAttribute) attribute1;
var schema = await context.Generator.GenerateWithReferenceAndNullabilityAsync<JsonSchema4>(attribute.Type, attributes, context.Resolver,
async (p, s) =>
{
p.AllowAdditionalProperties = true;
p.AdditionalPropertiesSchema = s.AdditionalPropertiesSchema;
});
schema.AllowAdditionalProperties = true;
context.Schema.AnyOf.Add(schema);
//context.Schema.Definitions.Add(attribute.Type.Name, schema);
}
context.Schema.AllowAdditionalProperties = true;
}
However, I'm still missing the AnyOf or OneOf implementation in the Model example view:

What needs to be implemented for this?
I dont know if Swagger UI even supports anyOf or oneOf. Is that the case?

It should, this was another request I manually constructed in YAML and pasted it in editor.swagger.io
Why are you modifying
p.AllowAdditionalProperties = true;
p.AdditionalPropertiesSchema = s.AdditionalPropertiesSchema;
schema.AllowAdditionalProperties = true;
Try to use
GenerateWithReferenceAsync
And ensure that the AnyOf in the JSON contains only $refs
Well, I feel a bit silly. I never changed my SchemaType to OpenApi 3.0 - Only this supports the anyOf keyword.
With this code:
if (context.Type.Name is nameof(PartyBaseRequestModel) || context.Type.Name is nameof(BaseObjectRequestModel))
{
var attributes = context.Type.GetCustomAttributes(typeof(KnownTypeAttribute), true) as Attribute[];
foreach (var attribute1 in attributes)
{
var attribute = (KnownTypeAttribute) attribute1;
var schema = await context.Generator.GenerateWithReferenceAsync<JsonSchema4>(attribute.Type, attributes, context.Resolver,
async (p, s) => {});
context.Schema.AnyOf.Add(schema);
}
context.Schema.Properties.Clear();
context.Schema.AllowAdditionalProperties = true;
}
and the settings:
settings.GeneratorSettings.FlattenInheritanceHierarchy = true;
settings.GeneratorSettings.SchemaType = SchemaType.OpenApi3;
settings.GeneratorSettings.SchemaProcessors.Add(new KnownTypeSchemaProcessor());
I was able to achieve:

Thanks for support! 馃憤
Most helpful comment
Well, I feel a bit silly. I never changed my SchemaType to OpenApi 3.0 - Only this supports the anyOf keyword.
With this code:
and the settings:
I was able to achieve:

Thanks for support! 馃憤