#5394·NSwag

A custom string `format` silently degrades to `string` with no way to map it

Author: F2XCreated Jul 29, 2026Updated Aug 27, 2026

Is your feature request related to a problem? Please describe.

A custom format on a string schema silently degrades the generated property to string, with no warning and no way to configure the mapping. That makes a custom format unusable in any document shared with an NSwag consumer, even when the format is meaningful to every other generator reading the same file.

CSharpTypeResolver.ResolveString (NJsonSchema, master) is a closed if-chain over JsonFormatStrings, ending in a catch-all:

csharp
if (schema.Format == JsonFormatStrings.Date)     { return Settings.DateType …; }
if (schema.Format == JsonFormatStrings.DateTime) { return Settings.DateTimeType …; }
if (schema.Format == JsonFormatStrings.Time)     { return Settings.TimeType …; }
// … Duration, TimeSpan, Uri, Guid/Uuid, Base64/Byte …
return "string" + nullableReferenceType;   // ← any other format

CSharpGeneratorSettings exposes DateType, DateTimeType, TimeType, TimeSpanType, NumberType… — these let you change which type a known format maps to, but there is no way to add a key. TypeScriptTypeResolver.ResolveString has the same shape, so the limitation is symmetric.

The concrete case. OpenAPI describes a C# DateTime and a DateTimeOffset with the same format: "date-time", though they do not carry the same thing on the wire:

C# serialized
DateTime (Kind = Unspecified) 2024-01-31T00:00:00
DateTimeOffset 2024-01-31T00:00:00+01:00

OpenAPI does not stand in the way of separating them: format is "an open string-valued property, and can have any value", so format: "date-time-offset" is a legal document. But measured on a real service (NSwag 14.7.0, dateTimeType: System.DateTimeOffset, one document generating both a TypeScript and a C# client):

Document carries C# client output
format: "date-time" System.DateTimeOffset
format: "date-time-offset" string — 45 properties degraded

So the moment a producer emits the more precise format, every NSwag-generated .NET client loses its typing — silently, at build time, with no diagnostic. The failure is invisible until someone notices the client's properties became strings.

Worth noting the same collapse affects the generated C# client itself: with dateTimeType: System.DateTimeOffset, the 19 offset-less DateTime properties and the 45 DateTimeOffset ones all land on System.DateTimeOffset, so the client cannot distinguish them either. There is currently no way to express that difference in the document without breaking the client entirely.

Describe the solution you'd like

A configurable format → type map, so an unknown format is a decision rather than a silent fallback. Something like:

jsonc
// nswag.json → codeGenerators.openApiToCSharpClient
"customTypeFormats": {
  "date-time-offset": "System.DateTimeOffset",
  "date-time-utc":    "System.DateTime"
}

consulted in ResolveString before the return "string" fallback. Unconfigured formats keep today's behaviour exactly, so this is additive and breaks nothing.

Two smaller variants, if a new setting is unwelcome:

  1. Make the fallback observable — log a warning when a non-empty format on a string schema matches no known format. That alone would have surfaced the problem at generation time instead of in review.
  2. Make the resolution overridableResolveString is private; Resolve is public override, so a subclass can technically intercept, but there is no documented way to inject a custom CSharpTypeResolver through nswag.json or the CLI. A protected virtual hook plus a settings entry point would let consumers solve this themselves.

Describe alternatives you've considered

  • Keeping the standard format and carrying the distinction in a vendor extension — what we ship today. format: "date-time" stays, and an x-temporal-format: "date-time-offset" rides alongside it; NSwag ignores unknown x- extensions and keeps emitting System.DateTimeOffset, while the other generator reads the extension. It works, and JsonSchema.ExtensionData means the information is already parsed and available should NSwag ever want to read it — but it means every producer has to know NSwag specifically, and pick a non-standard carrier for something format was designed to express.
  • Carrying it in title — worse: title is a display annotation, so the value surfaces as the property label in Swagger UI / Redoc.
  • Post-processing the generated client — rejected; the file is regenerated on every build.
  • A x-nswag-type style per-schema override — would solve it, but couples the document to one generator; a settings-side map keeps the document neutral.

Additional context

Happy to open a PR if the direction is agreeable — the change in ResolveString is small, and the symmetric one in TypeScriptTypeResolver would keep the two generators consistent. Please say which of the three shapes above you would accept before I write it.

Versions: NSwag 14.7.0 (NSwag.ApiDescription.Client, MSBuild target, .NET 10), document generated by Swashbuckle from an ASP.NET Core API.