JSON#

Tag typed data in JSON. By convention.

{
  "#type": "Circle",
  "radius": 4
}

When serializing a discriminated union, sum type, or polymorphic type to JSON, use #type as the discriminator property. The value is a logical tag name in PascalCase. Never a fully-qualified class name. Purely optional. Completely compatible. Standard JSON.

Why #type

The need for a type discriminator in JSON is well established. Everyone does it. Nobody agreed on how.

FieldUsed byPurpose
typeeverywherediscriminator, but collides with domain fields
$type.NET, uPicklepolymorphic type discriminator
@typeJSON-LDlinked data node type
__typenameGraphQLconcrete type in union responses

Four ecosystems, same idea, four different names. #type is the simple, neutral answer. # is valid in JSON property names, has no reserved meaning in any specification, and reads naturally as "tag".

Specification

  1. The discriminator property is named #type. The name is fixed and not configurable at the protocol level.
  2. The value of #type is a logical tag name in PascalCase. It is never a runtime class name, fully-qualified path, or namespace-prefixed identifier.
  3. The tag is internally tagged: #type and the variant's fields sit as sibling properties in the same JSON object.
  4. A type that is never part of a discriminated union does not include #type.
  5. A variant may use a custom tag name. The default is the type or variant name.
  6. Producers should place #type as the first property. Consumers must not depend on property order.
{"#type": "Circle", "radius": 4}
{"#type": "Rectangle", "width": 10, "height": 5}
{"#type": "Car", "make": "Toyota"}
{"#type": "Truck", "payloadTons": 5}

Libraries

Reference implementations exist for 12 languages, each wrapping the ecosystem's dominant JSON library. See the project on GitHub.