Polymorph Rest Api


Polymorph REST API — sample overview

Small, focused sample that shows how to build and consume a REST API whose responses are polymorphic: a single endpoint returns a collection of shapes, where each shape can be one of several concrete types (Circle, Rectangle, Triangle, …) that all derive from a common ShapeBase class.

Purpose: demonstrate an end-to-end, real-world pattern for polymorphic JSON payloads — from native System.Text.Json serialization on the server, through OpenAPI document generation, to a strongly-typed, build-time-generated C# client that deserializes the polymorphic response back into the correct derived type.

Why polymorphism, and why this is not API versioning

It’s tempting to conflate “the shape of a resource changes over time” with “the API needs a new version”. This sample deliberately keeps those concerns separate:

  • API versioning solves breaking changes to a contract — a new required field, a removed endpoint, a different authentication model, etc. It usually means clients must opt in to a new version to get the new behavior.
  • Polymorphism solves a narrower, additive problem: the same endpoint, the same contract, can return one of several known shapes of data, and — importantly — new shapes can be added later without breaking existing clients, as long as those clients know how to fall back gracefully for a discriminator they don’t recognize yet.

In other words, polymorphism here is a supplement to versioning, not an alternative to it. You can (and often should) use both: version the API when the contract changes, and use polymorphic types when the data naturally comes in multiple related shapes.

What the sample demonstrates

  • PolymorphRestApi.ShapeApi — an ASP.NET Core minimal API that exposes GET /shapes, returning a mix of Circle, Rectangle, and Triangle shapes as ShapeBase[]. Polymorphic serialization is expressed natively with [JsonPolymorphic] / [JsonDerivedType] attributes, including UnknownDerivedTypeHandling = FallBackToBaseType so that new/unrecognized shapes don’t break serialization on the server either.
  • A custom IOpenApiSchemaTransformer that rewrites the OpenAPI schema Microsoft’s OpenAPI generator produces (which flattens polymorphic types into anyOf) into the classic discriminator + allOf shape — so that OpenAPI-based code generators can recognize the inheritance relationship instead of generating flat, unrelated sibling types.
  • PolymorphTestClient — a console client that uses NSwag (wired into the build via NSwag.MSBuild) to generate typed C# model classes directly from the OpenAPI document at build time, and Refit to call the API. The generated models preserve real C# inheritance (Circle : ShapeBase, Rectangle : ShapeBase, …).
  • A small, hand-written fallback JsonConverter (implemented in ShapeBaseFallbackConverter) on the client that gracefully deserializes any shape discriminator the client doesn’t recognize (for example, a Triangle added to the server after the client’s model was last generated) into the common ShapeBase, rather than throwing — demonstrating forward-compatible handling of an evolving polymorphic contract.
  • PolymorphRestApi.AppHost — a .NET Aspire orchestration project that runs the API and client together for local development, with the Aspire dashboard configured for anonymous access in development.

Takeaway

Polymorphic response types let a single, stable API contract represent a growing family of related data shapes. Combined with server-side fallback handling and client-side tolerance for unknown discriminators, new shapes can be introduced without a version bump — while true breaking changes still belong behind API versioning.

The sample code here only shows polymorphism used in a GET request - but it can be used in POST and PUT as well.

This sample demonstrates polymorphism using Microsoft.AspNetCore.OpenApi and Scalar. NSwag is used purely for generating client model classes and indirectly for validating the generated OpenAPI spec. Microsoft now recommends its built-in OpenAPI support as the default, while Swashbuckle and NSwag remain viable alternatives.

The code can be found at: https://github.com/avantadore/PolymorphRestApi