Skip to content

Type Mapping

C# → TypeScript → OpenAPI

C#TypeScriptOpenAPI JSON Schema
stringstringtype: string
Guidstringtype: string, format: uuid
intnumbertype: integer, format: int32
longnumbertype: integer, format: int64
doublenumbertype: number, format: double
floatnumbertype: number, format: float
decimalnumbertype: number, format: decimal
uint, ulongnumbertype: integer
boolbooleantype: boolean
DateTimestringtype: string, format: date-time
DateTimeOffsetstringtype: string, format: date-time
DateOnlystringtype: string, format: date
TimeOnlystringtype: string, format: time
Uristringtype: string, format: uri
IFormFileFiletype: string, format: binary (mapped as scalar primitive)
T? (nullable value/ref)T | nullnullable: true
List<T>, T[], IEnumerable<T>, IReadOnlyList<T>T[]type: array, items: {...}
Dictionary<string, T>, IReadOnlyDictionary<string, T>Record<string, T>type: object, additionalProperties: {...}
sealed recordtype { ... } (transitive discovery, including project references)type: object, properties: {...}
enum (with JsonStringEnumConverter)type Status = "A" | "B"type: string, enum: [...]
PagedResult<T> (generic record)PagedResult<T>Monomorphised: PagedResult_TaskDto + x-rivet-generic
JsonElement, JsonNodeunknown{}
JsonObjectRecord<string, unknown>type: object
JsonArrayunknown[]type: array
Email(string Value) (single-property VO)string & { readonly __brand: "Email" }$ref to component schema with x-rivet-brand

Note

The OpenAPI emitter preserves format metadata from the intermediate type model. int emits as type: integer, format: int32, DateTime as type: string, format: date-time, etc. This enables lossless round-trips for most primitive types. Branded value objects are emitted as component schemas with x-rivet-brand so they survive round-trips.

Cross-project type discovery

Types referenced by contracts or endpoints are discovered transitively — even from project references. If your domain types live in a separate .csproj that your API project references, Rivet walks them automatically. No [RivetType] attribute needed on transitively-discovered types. NuGet and framework types are not walked.

Value objects

Single-property records with a property named Value are emitted as branded types:

csharp
// C#
public sealed record Email(string Value);
public sealed record Uprn(string Value);
public sealed record Quantity(int Value);
typescript
// TypeScript — branded primitives, nominal type safety
export type Email = string & { readonly __brand: "Email" };
export type Uprn = string & { readonly __brand: "Uprn" };
export type Quantity = number & { readonly __brand: "Quantity" };

Multi-property records are emitted as regular object types: Money(decimal Amount, string Currency) becomes { amount: number; currency: string }.

OpenAPI → C# (import direction)

JSON SchemaC#
stringstring
string + format: date-timeDateTime
string + format: dateDateOnly
string + format: guid / uuidGuid
string + format: email, uri, etc.Branded value object
string + enum: [...]enum
integer + format: int32int
integer (no format)long (safe default to avoid narrowing)
integer + format: int64long
number / number + format: doubledouble
number + format: floatfloat
number + format: decimaldecimal
booleanbool
Any type + x-rivet-csharp-typeExact C# type (uint, DateTimeOffset, short, etc.)
Property with deprecated: true[Obsolete] attribute
Property with description[RivetDescription] attribute
Property with default[RivetDefault] attribute (JSON literal)
Property with example[RivetExample] attribute (JSON literal)
Property with readOnly: true[RivetReadOnly] attribute
Property with writeOnly: true[RivetWriteOnly] attribute
Property with constraints (minLength, pattern, etc.)[RivetConstraints] attribute
Property not in required array[RivetOptional] attribute
String property with custom format[RivetFormat] attribute
Schema with description[RivetDescription] on the record
enum without explicit type (all string values)enum (type inferred from values)
Enum members with non-PascalCase names[JsonStringEnumMemberName] to preserve original
array + itemsList<T>
object + non-empty propertiessealed record
object + additionalPropertiesDictionary<string, T>
object with no propertiesDictionary<string, JsonElement> (no record generated)
$refNamed type reference
Nullable (nullable: true)T?

Note: Empty properties: {} is treated the same as absent properties — the OpenAPI library does not distinguish them. Both produce Dictionary<string, JsonElement>.

Generic type monomorphisation

In the OpenAPI spec, generic types are monomorphised — each concrete instantiation becomes its own schema with an _ delimiter:

  • PagedResult<TaskDto>PagedResult_TaskDto
  • PagedResult<MemberDto>PagedResult_MemberDto

In TypeScript, the generic is preserved as PagedResult<T> with a type parameter.

When emitted by Rivet, each monomorphised schema carries an x-rivet-generic extension. The importer uses this to reconstruct the generic template — so PagedResult_TaskDto + PagedResult_MemberDto import back as a single PagedResult<T> record. See OpenAPI Round-Trips for details.

Vendor extensions reference

See Vendor Extensions for the full x-rivet-* extension spec.