Skip to content

Generated Types

This page documents what type definitions in TypeSpec are generated as in emitted libraries

The namespace for models, enums, and unions will follow the namespace they belong to. You can use @clientNamespace to override it if needed.

namespace Service;
model Foo {
prop: string;
}
from service.models import Foo
foo = Foo(prop="hello")
namespace Service;
namespace Container {
model Foo {
prop: string;
}
}
from service.container.models import Foo
foo = Foo(prop="hello")
namespace Service;
@clientNamespace("Service.Container")
model Foo {
prop: string;
}
from service.container.models import Foo
foo = Foo(prop="hello")

The @clientDoc decorator lets you add documentation that will appear in generated client libraries. This is useful for providing additional context, examples, or implementation notes that are only relevant for SDK consumers.

@doc("This internal model is the base class for shapes")
@clientDoc(
"Base class for all shape objects that can be drawn on a canvas",
DocumentationMode.replace
)
model Shape {
@doc("The coordinates of the shape")
@clientDoc(
"The x,y coordinates where the shape will be positioned on the canvas",
DocumentationMode.replace
)
position: Point;
}
@doc("A two-dimensional point")
@clientDoc("Represents a position in a 2D coordinate system", DocumentationMode.replace)
model Point {
@doc("X coordinate")
@clientDoc("The horizontal position (increases moving right)", DocumentationMode.replace)
x: float32;
@doc("Y coordinate")
@clientDoc("The vertical position (increases moving down)", DocumentationMode.replace)
y: float32;
}
class Point(_model_base.Model):
"""Represents a position in a 2D coordinate system.
:param x: The horizontal position (increases moving right)
:type x: float
:param y: The vertical position (increases moving down)
:type y: float
"""
x: float = rest_field()
"""The horizontal position (increases moving right)."""
y: float = rest_field()
"""The vertical position (increases moving down)."""
class Shape(_model_base.Model):
"""Base class for all shape objects that can be drawn on a canvas.
:param position: The x,y coordinates where the shape will be positioned on the canvas
:type position: Point
"""
position: Point = rest_field()
"""The x,y coordinates where the shape will be positioned on the canvas."""

The @clientDoc decorator can be used on everything that the @doc decorator can be applied to.

When both @doc and @clientDoc are present, TCGC outputs the final documentation depending on the mode that you called @clientDoc with. If called with replace, @clientDoc replaces @doc completely. If called with append, @clientDoc is appended onto @doc.

model Properties {
name: string;
}
model Foo {
@flattenProperty
prop: Properties;
}
{
"kind": "model",
"name": "Foo",
"properties": [
{
"kind": "property",
"name": "prop",
"serializedName": "prop",
"flatten": true,
"optional": false,
"type": {
"kind": "model",
"name": "Properties",
"properties": [
{
"kind": "property",
"name": "name",
"serializedName": "name",
"flatten": false,
"optional": false,
"type": {
"kind": "string",
"encode": "string"
}
}
]
}
}
]
}

Recommend usage:

model Animal {
name: string;
kind: string;
...Record<unknown>;
}

Other usages:

model Animal extends Record<unknown> {
name: string;
kind: string;
}
model Animal is Record<unknown> {
name: string;
kind: string;
}
{
"kind": "model",
"name": "Animal",
"properties": [
{
"kind": "property",
"name": "name",
"serializedName": "name",
"optional": false,
"type": {
"kind": "string",
"encode": "string"
}
},
{
"kind": "property",
"name": "kind",
"serializedName": "kind",
"optional": false,
"type": {
"kind": "string",
"encode": "string"
}
}
],
"additionalProperties": {
"kind": "any"
}
}
model AnimalProperty {
category: string;
value: unknown;
}
model Animal {
name: string;
kind: string;
...Record<AnimalProperty>;
}
{
"kind": "model",
"name": "Animal",
"properties": [
{
"kind": "property",
"name": "name",
"serializedName": "name",
"optional": false,
"type": {
"kind": "string",
"encode": "string"
}
},
{
"kind": "property",
"name": "kind",
"serializedName": "kind",
"optional": false,
"type": {
"kind": "string",
"encode": "string"
}
}
],
"additionalProperties": {
"kind": "model",
"name": "AnimalProperty",
"properties": [
{
"kind": "property",
"name": "category",
"serializedName": "category",
"optional": false,
"type": {
"kind": "string",
"encode": "string"
}
},
{
"kind": "property",
"name": "value",
"serializedName": "value",
"optional": false,
"type": {
"kind": "any"
}
}
],
"additionalProperties": undefined
}
}
model Animal {
name: string;
kind: string;
...Record<string | int32>;
}
model Animal {
name: string;
kind: string;
...Record<string>;
...Record<int32>;
}
{
"kind": "model",
"name": "Animal",
"properties": [
{
"kind": "property",
"name": "name",
"serializedName": "name",
"optional": false,
"type": {
"kind": "string",
"encode": "string"
}
},
{
"kind": "property",
"name": "kind",
"serializedName": "kind",
"optional": false,
"type": {
"kind": "string",
"encode": "string"
}
}
],
"additionalProperties": {
"kind": "union",
"name": "AnimalAdditionalProperty",
"generatedName": true,
"values": [
{
"kind": "string",
"encode": "string"
},
{
"kind": "int32"
}
]
}
}
model Animal {
name: string;
kind: string;
...Record<string | null>;
}
{
"kind": "model",
"name": "Animal",
"properties": [
{
"kind": "property",
"name": "name",
"serializedName": "name",
"optional": false,
"type": {
"kind": "string",
"encode": "string"
}
},
{
"kind": "property",
"name": "kind",
"serializedName": "kind",
"optional": false,
"type": {
"kind": "string",
"encode": "string"
}
}
],
"additionalProperties": {
"kind": "nullable",
"valueType": {
"kind": "string",
"encode": "string"
}
}
}

TypeSpec uses @discriminator decorator to add a discriminator to a model.

TypeSpec now has two ways to represent a discriminated set.

  1. Use model
@discriminator("kind")
model Cat {
kind: string;
}
model Siamese extends Cat {
kind: "siamese";
}
model Ragdoll extends Cat {
kind: "ragdoll";
}

The type of the discriminator property could be an enum (extensible or fixed):

@discriminator("kind")
model Cat {
kind: CatKind;
}
union CatKind {
string,
Siamese: "siamese",
Ragdoll: "ragdoll",
}
model Siamese extends Cat {
kind: CatKind.Siamese;
}
model Ragdoll extends Cat {
kind: CatKind.Ragdoll;
}
  1. Use union
@discriminator("kind")
union Cat {
Siamese,
Ragdoll,
}
model Siamese {}
model Ragdoll {}

TCGC currently only supports the discriminated set based on models, discriminated union is not supported yet.

This is a brief structure of the models in a discriminated set in the output of TCGC.

{
"models": [
{
"kind": "model",
"name": "Cat",
"properties": [
{
"kind": "property",
"name": "kind",
"type": {
"kind": "string"
},
"discriminator": true
}
],
"discriminatorProperty": {
// the same instance of the property in the properties list here
},
"discriminatedSubtype": {
"siamese": {
"kind": "model",
"name": "Siamese",
"properties": [],
"discriminatorValue": "siamese"
},
"ragdoll": {
"kind": "model",
"name": "Ragdoll",
"properties": [],
"discriminatorValue": "ragdoll"
}
}
},
{
// the same instance of the model Siamese as we have above in `discriminatedSubtype` property
},
{
// the same instance of the model Ragdoll as we have above in `discriminatedSubtype` property
}
]
}

TypeSpec uses | null to represent nullable types. Nullability is handled differently in languages, but emitter authors will find information about nullability by inspecting the type of a property.

model Foo {
basicNullableProperty: string | null;
modelNullableProperty: Bar | null;
unionNullableProperty: Bar | Baz | null;
enumNullableProperty: LR | null;
}
model Bar {
prop: string;
}
model Baz {
prop: int32;
}
enum LR {
left,
right,
}

A nullable type has kind nullable and property valueType. The kind of the type tells you the property is nullable, while the valueType tells you the underlying type you want to generate.

{
"kind": "model",
"name": "Foo",
"properties": [
{
"kind": "property",
"name": "basicNullableProperty",
"serializedName": "basicNullableProperty",
"optional": false,
"type": {
"kind": "nullable",
"valueType": {
"kind": "string",
"encode": "string"
}
}
},
{
"kind": "property",
"name": "modelNullableProperty",
"serializedName": "modelNullableProperty",
"optional": false,
"type": {
"kind": "nullable",
"valueType": {
"kind": "model",
"name": "Bar",
"properties": [
{
"kind": "property",
"name": "prop",
"serializedName": "prop",
"optional": false,
"type": {
"kind": "string",
"encode": "string"
}
}
]
}
}
},
{
"kind": "property",
"name": "unionNullableProperty",
"serializedName": "unionNullableProperty",
"optional": false,
"type": {
"kind": "nullable",
"valueType": {
"kind": "union",
"values": [
{
"kind": "model",
"name": "Bar",
"properties": [
{
"kind": "property",
"name": "prop",
"serializedName": "prop",
"optional": false,
"type": {
"kind": "string",
"encode": "string"
}
}
]
},
{
"kind": "model",
"name": "Baz",
"properties": [
{
"kind": "property",
"name": "prop",
"serializedName": "prop",
"optional": false,
"type": {
"kind": "int32",
"encode": "int32"
}
}
]
}
]
}
}
},
{
"kind": "property",
"name": "enumNullableProperty",
"serializedName": "enumNullableProperty",
"optional": false,
"type": {
"kind": "nullable",
"valueType": {
"kind": "enum",
"name": "LR",
"generatedName": false,
"valueType": {
"kind": "string"
},
"values": [
{
"kind": "enumvalue",
"name": "left",
"value": "left"
},
{
"kind": "enumvalue",
"name": "right",
"value": "right"
}
],
"isFixed": true,
"isUnionAsEnum": false
}
}
}
]
}

All emitters will generate their version of a closed enum.

union LR {
left: "left",
right: "right",
}
{
"kind": "enum",
"name": "LR",
"generatedName": false,
"valueType": {
"kind": "string"
},
"values": [
{
"kind": "enumvalue",
"name": "left",
"value": "left"
},
{
"kind": "enumvalue",
"name": "right",
"value": "right"
}
],
"isFixed": true,
"isUnionAsEnum": true
}

This is union defined inline at point of usage.

model Widget {
horizontal: "left" | "right";
}
{
"kind": "enum",
"name": "WidgetHorizontals",
"generatedName": true,
"valueType": {
"kind": "string"
},
"values": [
{
"kind": "enumvalue",
"name": "left",
"value": "left"
},
{
"kind": "enumvalue",
"name": "right",
"value": "right"
}
],
"isFixed": true,
"isUnionAsEnum": true
}

Union of basic type and literals of that type

Section titled “Union of basic type and literals of that type”

Each language will generate their version of an open enum.

union Colors {
string,
red: "red",
blue: "blue",
}
{
"kind": "enum",
"name": "Colors",
"generatedName": false,
"valueType": {
"kind": "string"
},
"values": [
{
"kind": "enumvalue",
"name": "red",
"value": "red"
},
{
"kind": "enumvalue",
"name": "blue",
"value": "blue"
}
],
"isFixed": false,
"isUnionAsEnum": true
}

Inline union of basic type and literals of that type

Section titled “Inline union of basic type and literals of that type”

This is union defined inline at point of usage which include the base type as an option.

model Widget {
color: "red" | "blue" | string;
}
{
"kind": "enum",
"name": "WidgetColors",
"generatedName": true,
"valueType": {
"kind": "string"
},
"values": [
{
"kind": "enumvalue",
"name": "red",
"value": "red"
},
{
"kind": "enumvalue",
"name": "blue",
"value": "blue"
}
],
"isFixed": false,
"isUnionAsEnum": true
}

Union of other union/enum, basic type and literals of that type

Section titled “Union of other union/enum, basic type and literals of that type”
import "@azure-tools/typespec-azure-resource-manager";
union ProvisioningState {
string,
"InProgress",
Azure.ResourceManager.ResourceProvisioningState,
}

For union of other union or enum. TCGC will do the flatten according to the flag.

With flatten-union-as-enum flagged true:

{
"kind": "enum",
"name": "ProvisioningState",
"generatedName": false,
"valueType": {
"kind": "string"
},
"values": [
{
"kind": "enumvalue",
"name": "InProgress",
"value": "InProgress"
},
{
"kind": "enumvalue",
"name": "Succeeded",
"value": "Succeeded"
},
{
"kind": "enumvalue",
"name": "Failed",
"value": "Failed"
},
{
"kind": "enumvalue",
"name": "Canceled",
"value": "Canceled"
}
],
"isFixed": false,
"isUnionAsEnum": true
}

With flatten-union-as-enum flagged false:

{
"kind": "union",
"name": "ProvisioningState",
"generatedName": false,
"values": [
{
"kind": "string"
},
{
"kind": "constant",
"value": "InProgress",
"valueType": {
"kind": "string"
}
},
{
"kind": "enum",
"name": "ResourceProvisioningState",
"generatedName": false,
"valueType": {
"kind": "string"
},
"values": [
{
"kind": "enumvalue",
"name": "Succeeded",
"value": "Succeeded"
},
{
"kind": "enumvalue",
"name": "Failed",
"value": "Failed"
},
{
"kind": "enumvalue",
"name": "Canceled",
"value": "Canceled"
}
],
"isFixed": true,
"isUnionAsEnum": false
}
]
}

Union of other unions of literals with same type

Section titled “Union of other unions of literals with same type”
union LR {
left: "left",
right: "right",
}
union UD {
up: "up",
down: "down",
}
union Orientation {
LR,
UD,
}

For union of other union or enum. TCGC will do the flatten according to the flag.

With flatten-union-as-enum flagged true:

{
"kind": "enum",
"name": "Orientation",
"generatedName": false,
"valueType": {
"kind": "string"
},
"values": [
{
"kind": "enumvalue",
"name": "left",
"value": "left"
},
{
"kind": "enumvalue",
"name": "right",
"value": "right"
},
{
"kind": "enumvalue",
"name": "up",
"value": "up"
},
{
"kind": "enumvalue",
"name": "down",
"value": "down"
}
],
"isFixed": true,
"isUnionAsEnum": true
}

With flatten-union-as-enum flagged false:

{
"kind": "union",
"name": "Orientation",
"generatedName": false,
"values": [
{
"kind": "enum",
"name": "LR",
"generatedName": false,
"valueType": {
"kind": "string"
},
"values": [
{
"kind": "enumvalue",
"name": "left",
"value": "left"
},
{
"kind": "enumvalue",
"name": "right",
"value": "right"
}
],
"isFixed": true,
"isUnionAsEnum": true
},
{
"kind": "enum",
"name": "UD",
"generatedName": false,
"valueType": {
"kind": "string"
},
"values": [
{
"kind": "enumvalue",
"name": "up",
"value": "up"
},
{
"kind": "enumvalue",
"name": "down",
"value": "down"
}
],
"isFixed": true,
"isUnionAsEnum": true
}
]
}

Inline union of other unions of literals with same type

Section titled “Inline union of other unions of literals with same type”
union LR {
left: "left",
right: "right",
}
union UD {
up: "up",
down: "down",
}
model Widget {
orientation: LR | UD;
}

For union of other union or enum. TCGC will do the flatten according to the flag.

With flatten-union-as-enum flagged true:

{
"kind": "enum",
"name": "WidgetOrientations",
"generatedName": true,
"valueType": {
"kind": "string"
},
"values": [
{
"kind": "enumvalue",
"name": "left",
"value": "left"
},
{
"kind": "enumvalue",
"name": "right",
"value": "right"
},
{
"kind": "enumvalue",
"name": "up",
"value": "up"
},
{
"kind": "enumvalue",
"name": "down",
"value": "down"
}
],
"isFixed": true,
"isUnionAsEnum": true
}

With flatten-union-as-enum flagged false:

{
"kind": "union",
"name": "WidgetOrientations",
"generatedName": true,
"values": [
{
"kind": "enum",
"name": "LR",
"generatedName": false,
"valueType": {
"kind": "string"
},
"values": [
{
"kind": "enumvalue",
"name": "left",
"value": "left"
},
{
"kind": "enumvalue",
"name": "right",
"value": "right"
}
],
"isFixed": true,
"isUnionAsEnum": true
},
{
"kind": "enum",
"name": "UD",
"generatedName": false,
"valueType": {
"kind": "string"
},
"values": [
{
"kind": "enumvalue",
"name": "up",
"value": "up"
},
{
"kind": "enumvalue",
"name": "down",
"value": "down"
}
],
"isFixed": true,
"isUnionAsEnum": true
}
]
}

These are unions where the values don’t share same type.

model Shirt {
sizing: 32 | 34 | int32 | "small" | "medium" | string;
}
{
"kind": "union",
"name": "ShirtSizings",
"generatedName": true,
"values": [
{
"kind": "constant",
"value": 32,
"valueType": {
"kind": "int32"
}
},
{
"kind": "constant",
"value": 34,
"valueType": {
"kind": "int32"
}
},
{
"kind": "constant",
"value": "small",
"valueType": {
"kind": "string"
}
},
{
"kind": "constant",
"value": "medium",
"valueType": {
"kind": "string"
}
},
{
"kind": "string"
}
]
}

Standard enums will be generated as closed enums.

enum LR {
left,
right,
}
{
"kind": "enum",
"name": "LR",
"generatedName": false,
"valueType": {
"kind": "string"
},
"values": [
{
"kind": "enumvalue",
"name": "left",
"value": "left"
},
{
"kind": "enumvalue",
"name": "right",
"value": "right"
}
],
"isFixed": true,
"isUnionAsEnum": false
}
@versioned(Versions)
@service
namespace Service;
enum Versions {
v1,
v2,
}
{
"kind": "enum",
"name": "Versions",
"generatedName": false,
"valueType": {
"kind": "string"
},
"values": [
{
"kind": "enumvalue",
"name": "v1",
"value": "v1"
},
{
"kind": "enumvalue",
"name": "v2",
"value": "v2"
}
],
"isFixed": true,
"isUnionAsEnum": false,
"usage": 8
}

Spreading enums will return the resultant enum as a new single closed enum.

enum LR {
left,
right,
}
enum UD {
up,
down,
}
enum Orientation {
...LR,
...UD,
}
{
"kind": "enum",
"name": "Orientation",
"generatedName": false,
"valueType": {
"kind": "string"
},
"values": [
{
"kind": "enumvalue",
"name": "left",
"value": "left"
},
{
"kind": "enumvalue",
"name": "right",
"value": "right"
},
{
"kind": "enumvalue",
"name": "up",
"value": "up"
},
{
"kind": "enumvalue",
"name": "down",
"value": "down"
}
],
"isFixed": true,
"isUnionAsEnum": false
}

We will take the @encode decorator into account, determining how we serialize inputted scalars to send over the wire.

model Test {
@encode(DateTimeKnownEncoding.rfc3339)
prop: utcDateTime;
}
{
"kind": "property",
"name": "prop",
"type": {
"kind": "utcDateTime",
"encode": "rfc3339",
"wireType": {
"kind": "string"
}
}
}

When you specify an encoding type, say that you want to encode an integer as a string, that will also be represented in our generated SDKs.

model Test {
@encode(string)
prop: int64;
}
{
"kind": "property",
"name": "prop",
"type": {
"kind": "int64",
"encode": "string",
"wireType": {
"kind": "string"
}
}
}