Skip to content
Last update: July 16, 2024

Type Inheritance Support in Swagger API

The Virto Сommerce platform offers enhanced support for exposing derived types in Swagger API descriptions. Traditionally, Swagger would only expose the base type, leaving derived types absent from the JSON API definition. This limitation often required additional API methods to explicitly handle derived types, complicating the development process.

Problem

In the example below, only the BaseObject is exposed in Swagger. DerivedObject and AnotherDerivedObject will be missing from the JSON with the API definition, and the only way to expose them is to add another API method that explicitly accepts or returns one of these derived types.

On the other hand, we don't want to enable polymorphism globally, as this might break existing API (for example, any vc-module-customer action that works with a Member class).

// Models definition in ...Core assembly

public abstract class BaseObject
{
    // ...
}

public class DerivedObject : BaseObject
{
    // ...
}

public class AnotherDerivedObject : BaseObject
{
    // ...
}

// Controller method that returns these models
public ActionResult<IList<BaseObject>> GetObjects()
{
    var result = new[] 
    {
        new DerivedObject(),
        new AnotherDerivedObject()
    };

    return Ok(result);
}

Solution

Since the creation of VC Platform v3, polymorphism support in Swashbuckle has been significantly improved. Now we can actually use it in action, and the resulting document will be suitable for AutoRest (for example, to generate API clients for the Frontend Application). An example mentioned above can be reworked like this to expose derived models:

using Swashbuckle.AspNetCore.Annotations;

[SwaggerSubType(typeof(DerivedObject)]
[SwaggerSubType(typeof(AnotherDerivedObject)]
public abstract class BaseObject
{
    // ...
}

public class DerivedObject : BaseObject
{
    // ...
}

public class AnotherDerivedObject : BaseObject
{
    // ...
}

This will expose BaseObject, DerivedObject, and AnotherDerivedObject in the Swagger API description, even though the GetObjects() method still only has the base type in its signature, and it won't break any other API.

Readmore Enrich Polymorphic Base Classes with Discriminator Metadata

Limitations

This approach works and does not break existing clients generated by AutoRest. However, it has the following limitations:

  • If BaseObject has any abstract or virtual properties that are overridden in derived types, Swashbuckle will include those properties in both BaseObject and the derived types. However, AutoRest does not understand this and throws an error like FATAL: System.InvalidOperationException: Incompatible property types found for property 'someVirtualProperty' in schema inheritance chain. One solution would be to avoid using abstract and virtual properties for such types.
  • This approach only works if all derived types are in the same module - it won't allow to extend this list from other modules. To work around this, we may need to write a custom subtype selector - its code would be based on the existing implementation in Swashbuckle, but would use AbstractTypeFactory instead of custom attributes to find descendant types. However, this may require extending TypeInfo so that we can explicitly specify which types can be exposed in the Swagger API.