Skip to content

Instantly share code, notes, and snippets.

@leedm777
Last active December 13, 2021 08:19
Show Gist options
  • Save leedm777/5730877 to your computer and use it in GitHub Desktop.
Save leedm777/5730877 to your computer and use it in GitHub Desktop.
Type variance in Swagger models

This document describes how to extend Swagger data models to allow the types of fields to vary.

Given that Swagger models need to map cleanly to a statically typed object model, a subclassing approach seems like it would be a good fit.

Inheritance

Inheritance allows a model (the derived type) to inherit all of the properties of another (the base type). This allows for a more compact representation of models, since common sets of fields may be extracted into a base type. This also allows the Swagger data model to match more closely to what an object model might look like.

Inheritance is transitive, meaning that if the base type inherits from another model, the derived type inherits those properties, too.

To declare an inheritance relationship between two models, a field extends is added to the derived type's model definition, which gives the id of the base type.

Only single inheritance is supported; meaning at most one extends field may be declared on a given model.

The derived type MUST NOT redefine any properties defined in any of its base types.

The base type named in an extends field SHOULD be defined within the same API declaration.

The extends relationship between models MUST NOT be cyclic.

Example

This example defines four models, with a fairly simple inheritance relationship between them.

         +--- Foo <--- Bar
         |
Base <---+
         |
         +--- Bam

Base is the base type, with a single property baseProp. Foo derives from Base, so it inherits the property baseProp, in addition to defining its own property fooProp. Bar derives from Foo, and inherits both baseProp and fooProp. Finally, Bam also derives from Base, so it inherits baseProp.

"models": {
  "Base": {
    "id": "Base",
    "properties": { "baseProp": { "type": "string" } }
  },
  "Foo": {
    "id": "Foo",
    "extends": "Base",
    "properties": { "fooProp": { "type": "string"} }
  },
  "Bar": {
    "id": "Bar",
    "extends": "Foo",
    "properties": { "barProp": { "type": "string"} }
  },
  "Bam": {
    "id": "Bam",
    "extends": "Base",
    "properties": { "bamProp": { "type": "string"} }
  }
}

Here are some example objects that would conform to this model:

Base - { "baseProp": "base" }
Foo  - { "baseProp": "base", "fooProp": "foo" }
Bar  - { "baseProp": "base", "fooProp": "foo", "barProp": "bar" }
Bam  - { "baseProp": "base", "bamProp": "bam" }

Polymorphism

In the above examples, there is no practical way for the client to determine the specific type of a received object. The ability to do so is very useful in being able to treat types polymorphically. If such a polymorphic base type is declared as the responseClass of an operation, the consumer can determine the type of object they are receiving.

To declare that a type may be treated polymorphically, a field discriminator is added to the type's model definition, which gives the id of the property to be used to identify the type of an object. The named field MUST be of type string, and MUST be required. The allowableValues for the property will be implicitly be the list of ids of all derived subtypes.

At most one discriminator field may be declared on a given model. Since the field is inherited by subtypes, they MUST NOT declare their own discriminator.

Subtypes are defined using the extends field, as described in the Inheritance section above.

Example

This example defines two classes to simply demonstrate the use of the discriminator field.

Animal <--- Cat

Animal is the base type, with two properties: id and dtype, with dtype identified as the discriminator. Cat extends Animal, and adds a name property.

"models": {
  "Animal": {
    "id": "Animal",
    "discriminator": "dtype",
    "properties": {
      "id": { "type": "long", },
      "dtype": { "type": "string", "required": true }
    }
  },
  "Cat": {
    "id": "Cat",
    "extends": Animal"
    "properties": {
      "name": { "type": "string" }
    }
  }
}

Since Animal declares a discriminator field, it may be treated polymorphically. If an operation's responseClass is Animal, it may safely return Animals or Cats.

Some valid objects that conforms to the Animal model:

{ "dtype": "Animal", "id": 8675309 }
{ "dtype": "Cat", "id": 3141593, "name": "Fluffy" }
@fehguy
Copy link

fehguy commented Jun 27, 2013

OK this has been pushed

@efuquen
Copy link

efuquen commented Aug 7, 2013

I've been looking for documentation on the extends and discriminator property, this is exactly what I've been looking for. Might I suggest this gets moved to a page on the wiki?

@frozenspider
Copy link

I also spend quite a while looking for this. Why there are little to no documentation on this? Also, why can't I make this work in definitions section of .yml file?

@ialmetwally
Copy link

is this feature implemented in the swagger codegen?

@himanshuy
Copy link

I do not think so. swagger codegen doesn't support polymorphic objects.

@natke
Copy link

natke commented Sep 4, 2016

How does the discriminator value get mapped to the actual subType?

e.g. if discriminator equals "type" and subTypes are {Cat.class, Dog.class}, where in the model does it map type=cat to Cat.class and type=dog to Dog.class?

@vglushonkov
Copy link

Please clarify if this is supported in Swagger 2.0, Swagger Editor does not understand "extends" field

@dobegor
Copy link

dobegor commented Feb 22, 2017

@fehguy, please tell us if this is supported in Swagger 2.0 because ^ (see above).
Thanks.

@pacey
Copy link

pacey commented Mar 8, 2017

How can polymorphic definitions be used in request and response definitions? If I have an endpoint for POST /v1/animals and I want to send a cat or a dog, how can this be described in the request definition? If I just reference the animal definition then I get warning that the cat and dog models are never used, which makes sense.

Secondly, if I want to be able to search all animals by GET /v1/animals this will return both cats and dogs. How can this be described in the response definition? Again, If I just reference the animal model then the cat and dog models aren't used.

@cimarron-pistoncloud
Copy link

I have the same question as @pacey.

@jeffersonchoi
Copy link

Same question as @pacey and @cimarron-pistoncloud.

@Berathiel
Copy link

I have the same question as @pacey, @cimarron-pistoncloud and @jeffersonchoi.
Any news on the issue?

@guillemdc
Copy link

Same question here as @Berathiel, @pacey, @cimarron-pistoncloud and @jeffersonchoi.

@dbaltor
Copy link

dbaltor commented May 15, 2018

I'm afraid that can only be done with Swagger 3.0 OneOf keyword in the request/response schema definition as per below:

https://swagger.io/docs/specification/data-models/inheritance-and-polymorphism/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment