OrionGuard.SchemaExport
7.0.0
dotnet add package OrionGuard.SchemaExport --version 7.0.0
NuGet\Install-Package OrionGuard.SchemaExport -Version 7.0.0
<PackageReference Include="OrionGuard.SchemaExport" Version="7.0.0" />
<PackageVersion Include="OrionGuard.SchemaExport" Version="7.0.0" />
<PackageReference Include="OrionGuard.SchemaExport" />
paket add OrionGuard.SchemaExport --version 7.0.0
#r "nuget: OrionGuard.SchemaExport, 7.0.0"
#:package OrionGuard.SchemaExport@7.0.0
#addin nuget:?package=OrionGuard.SchemaExport&version=7.0.0
#tool nuget:?package=OrionGuard.SchemaExport&version=7.0.0
OrionGuard.SchemaExport
Exports the validation rules already declared on a model as a JSON Schema document or a TypeScript interface, so the frontend consumes the rules instead of a hand-written copy that drifts.
dotnet add package OrionGuard.SchemaExport
using Moongazing.OrionGuard.Attributes;
using Moongazing.OrionGuard.SchemaExport;
public sealed class CreateUserRequest
{
[NotNull, Email] public string Email { get; set; } = "";
[Length(8, 100)] public string Password { get; set; } = "";
[Range(13, 120)] public int Age { get; set; }
[Positive] public decimal Budget { get; set; }
public Uri? Website { get; set; }
}
public static class ExportContracts
{
public static void Run()
{
File.WriteAllText("create-user.schema.json", JsonSchemaExporter.Export<CreateUserRequest>());
File.WriteAllText("create-user.ts", TypeScriptExporter.Export<CreateUserRequest>());
}
}
create-user.schema.json is an indented JSON Schema draft 2020-12 document:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "CreateUserRequest",
"type": "object",
"properties": {
"email": { "type": "string", "format": "email" },
"password": { "type": "string", "minLength": 8, "maxLength": 100 },
"age": { "type": "integer", "minimum": 13, "maximum": 120 },
"budget": { "type": "number", "exclusiveMinimum": 0 },
"website": { "type": ["string", "null"], "format": "uri" }
},
"required": ["email"]
}
and create-user.ts is types plus documented constraints:
// Generated by OrionGuard.SchemaExport from YourApp.CreateUserRequest.
// Types only: the constraints below are documented here, not enforced here.
export interface CreateUserRequest {
/** required; format: email */
email: string;
/** 8 to 100 characters */
password?: string;
/** between 13 and 120 */
age?: number;
/** greater than 0 */
budget?: number;
/** format: uri */
website?: string | null;
}
The core OrionGuard package comes along as a dependency; nothing else does, because the exporters use System.Text.Json only. JsonSchemaExporter.Export<T>(Utf8JsonWriter) writes the same object into a writer you own, for streaming it or nesting it in a larger document, and TypeScriptExporter.Export<T>() uses \n line endings on every platform so the file is byte-identical wherever it was generated.
Rule mapping
| Attribute | JSON Schema | TypeScript comment |
|---|---|---|
[NotNull] |
adds the member to required |
required |
[NotEmpty] |
required, plus minLength: 1 and pattern: \S on a string, or minItems: 1 on a collection |
required; not blank |
[Length(min, max)] |
minLength, maxLength (minItems, maxItems on a collection) |
min to max characters |
[Range(min, max)] |
minimum, maximum |
between min and max |
[Regex(pattern)] |
pattern, when the expression is also ECMAScript regex |
pattern: ... |
[Email] |
format: email |
format: email |
[Positive] |
exclusiveMinimum: 0 (the numeric form JSON Schema 2020-12 uses) |
greater than 0 |
any other ValidationAttribute |
x-orionguard-unsupported |
// not enforced here: ... |
[NotEmpty] rejects null as well as the empty value, so it makes a member required on its own. On a string it is IsNullOrWhiteSpace, not a length check, so minLength: 1 alone would accept " " and the client would send what the server rejects; the unanchored \S pattern — "holds a non-whitespace character" — closes that gap.
Attributes combine rather than overwrite: two [Length] rules produce the bounds that satisfy both, and two patterns on one member are ANDed under allOf, because pattern holds a single expression.
Type mapping
| CLR | JSON Schema | TypeScript |
|---|---|---|
string, char |
string |
string |
| integral types | integer |
number |
float, double, decimal |
number |
number |
bool |
boolean |
boolean |
Guid |
string, format: uuid |
string |
Uri |
string, format: uri |
string |
DateTime, DateTimeOffset |
string, format: date-time |
string |
DateOnly / TimeOnly |
string, format: date / time |
string |
TimeSpan |
string |
string |
| enum | string plus an enum list of the member names |
a union of the member names |
IEnumerable<T>, T[] |
array with items, nested to any depth |
T[], T[][], … |
byte[] |
string, contentEncoding: base64 |
string |
| dictionary | object |
Record<string, unknown> |
| any other class, record or struct | $ref into $defs |
its own export interface |
object |
unconstrained | unknown |
OrionGuard has no URL attribute, so a Uri member is the only way the exporters know a string holds a URL.
Details that decide what the artifact says
- Members are the public instance properties with a getter — the same surface
AttributeValidatorand the Swagger schema filter see. Indexers and[JsonIgnore]members are skipped. - The serialized name is
[JsonPropertyName]when present, otherwise the camelCase form of the property name. A name that is not a valid TypeScript identifier is written quoted and escaped, or the declaration would not parse. - A member is required only when an OrionGuard rule requires it. A non-nullable CLR type on its own is not enough: it removes the
nullbranch from the type but leaves the member optional. - A nullable member gets
"type": ["x", "null"], a nullable nested type ananyOfwith anullbranch, and element nullability is carried at every level:List<string?>exports(string | null)[],List<string>exportsstring[]. - A property in an assembly compiled without nullable reference types is treated as nullable, because nothing there rules null out — the same for elements of a collection that reaches
IEnumerable<T>without being an array or a single-argument generic. - Enums are exported by name, which is what
JsonStringEnumConverterwrites. A payload that serializes enums as numbers will not match the schema. - Nested types are emitted once under
$defsand referenced; a type that references itself, directly or through a cycle, points back at the document root ("$ref": "#"). Two nested types with the same short name would collide, so the second is published under its namespace-qualified name.
What this does not do
It cannot see a rule written as code. Rules registered on an
AbstractValidator<T>, or built withValidate.For/Validate.Nested/Validate.CrossProperties, are C# delegates, and reflection cannot read a delegate's body. Those rules are invisible to the exporters and no trace of them reaches the artifact — not even a note. If a rule has to reach the frontend, declare it as an attribute.It reports what it cannot express, rather than dropping it. A rule the exporters cannot translate is listed under
x-orionguard-unsupportedon the schema that declares the member, and gets a// not enforced here:comment above the TypeScript member:"x-orionguard-unsupported": [ { "member": "confirmation", "rule": "MatchesPasswordAttribute" } ]That is the honest half of the previous point: an attribute the exporter does not know is named in the artifact, a delegate rule is not.
A .NET-only regex is not exported.
RegexAttributelands in that same list when the expression uses constructs ECMAScript lacks: an inline option ((?i)), a .NET anchor (\A,\Z,\z,\G), a Unicode category (\p{...}), a named or balancing group, an atomic or conditional group, or character-class subtraction ([a-z-[aeiou]]). A JSON Schemapatternis an ECMAScript regex with no flags, so copying one across would make the artifact match what the server rejects. Lookaround, non-capturing groups and ordinary escapes are shared and are copied through. The screen is deliberately pessimistic: it may report a pattern an engine would have handled, which costs a constraint and says so, rather than shipping one that lies.TypeScript output enforces nothing. It is types plus doc comments — no runtime checks, no validator. The JSON Schema is the artifact a validator can run.
It exports one type at a time.
Export<T>()walksTand whatTreferences; there is no assembly sweep and no file layout — you choose what to export and where to write it.Not trimming- or NativeAOT-safe. Both
Export<T>methods are marked[RequiresUnreferencedCode]because they walk public properties, and their property types, with reflection. Under trimming or NativeAOT, root the exported types (withDynamicDependency, for example) or the artifact will silently be missing the members that were trimmed away.
Targets
net8.0, net9.0, net10.0.
With the rest of OrionGuard
OrionGuard (the attributes it reads) · OrionGuard.Swagger (the same constraints on the OpenAPI schemas Swashbuckle generates) · OrionGuard.OpenApi (the opposite direction: a validator from an OpenAPI document)
Documentation
License
MIT. See LICENSE.txt.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- OrionGuard (>= 7.0.0)
-
net8.0
- OrionGuard (>= 7.0.0)
-
net9.0
- OrionGuard (>= 7.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 7.0.0 | 88 | 9/20/2026 |