CLI Code Generation
Overview
corvusjson is the .NET global tool that generates strongly-typed C# models from JSON Schema files. It produces the same output as the Roslyn incremental source generator, but runs ahead of time from the command line — making it suitable for CI/CD pipelines, pre-generation workflows, and scenarios where you need to inspect or version-control the generated code.
The tool supports all major JSON Schema drafts (Draft 4, 6, 7, 2019-09, and 2020-12), OpenAPI 3.0, and YAML input.
Tip: If you only need validation and annotation collection without the full type system, use
--codeGenerationMode SchemaEvaluationOnlyto generate a standalone schema evaluator.
Installation
dotnet tool install --global Corvus.Json.Cli
Or as a local tool:
dotnet new tool-manifest
dotnet tool install Corvus.Json.Cli
Legacy shim: The previous package
Corvus.Json.CodeGenerator(command:generatejsonschematypes) still works but defaults to the V4 engine and displays a deprecation warning. New projects should useCorvus.Json.Cli.
Quick Start
Generate types from a schema file:
corvusjson jsonschema Schemas/person.json \
--rootNamespace MyApp.Models \
--outputPath Generated/
This reads person.json, generates one or more C# files into Generated/, and uses MyApp.Models as the root namespace.
Commands
generate (Default)
Generate C# types from a single schema file. This is the default command when using the jsonschema subcommand — you can omit the command name.
corvusjson jsonschema <schemaFile> [OPTIONS]
Required arguments:
<schemaFile>— Path to the JSON Schema file--rootNamespace— Root namespace for generated types
Common options:
| Option | Default | Description |
|---|---|---|
--outputPath |
— | Directory for generated .cs files |
--outputRootTypeName |
Derived | Override the .NET type name for the root type. If omitted, the name is derived automatically using naming heuristics (see Naming Heuristics below). |
--rootPath |
— | JSON Pointer to the root element (e.g., #/definitions/Person) |
--rebaseToRootPath |
false |
Rebase the document as if rooted at --rootPath |
--useSchema |
Auto-detect | Fallback schema draft: Draft4, Draft6, Draft7, Draft201909, Draft202012, OpenApi30 |
--assertFormat |
true |
Enforce the format keyword as a validation assertion (global default). Takes a value: pass --assertFormat false for annotation-only output (effective where the schema's vocabulary already treats format as an annotation, e.g. 2020-12; for drafts whose vocabulary asserts format, such as draft-04/06/07, use --formatMode disable). |
--formatMode |
— | Format assertion mode overrides as comma-separated entries, where mode is assert, disable, or warning. Use format=mode for one format (e.g. date-time=disable,time=warning), or a bare mode (equivalently *=mode) to set the default for all formats — e.g. --formatMode disable for annotation-only output across every draft, which overrides even a vocabulary that asserts format. Repeatable. A per-format override takes precedence over the * default, --assertFormat, and the vocabulary. Warning mode applies to string formats only. |
--optionalAsNullable |
None |
How to handle optional properties: None or NullOrUndefined |
--useImplicitOperatorString |
false |
Use implicit (vs explicit) conversion to string |
--unions |
true |
Make each oneOf/anyOf type a C# union as well as giving it Match: with the C# 15 compiler (the .NET 11 SDK or later, any target framework except .NET Framework) switch and is patterns over the branch types work. Pass --unions false to leave the union members out. |
--regexEngine |
Ecma262 |
The engine the generated regular expressions are written for, by the V5 and V4 engines alike. Ecma262 writes Corvus.Regex.Ecma262's compiled matchers, with the compiled regular expression as data, so nothing is parsed or built when one is first used; a pattern-property field is that library's Regex. DotNet writes .NET's System.Text.RegularExpressions, as earlier versions did, for code that depends on those fields being .NET's Regex. |
--regexEdition |
each dialect's | The edition of ECMA-262 whose grammar every pattern is read with: Current or Es2020. By default draft 2019-09 and draft 2020-12 (and OpenAPI 3.1) read patterns with the grammar of ECMAScript 2020, which they cite, and the other dialects with today's. Ecma262 only. |
--regexUnicodeVersion |
Default |
The version of Unicode every pattern is read with: Default (V8's, 17), V13, V17, V18 or Latest. Ecma262 only. |
--regexMatchTimeout |
1000 |
The limit, in milliseconds, on one match of a regular expression in generated code, or -1 for none. A match that runs for longer throws RegexMatchTimeoutException. A pattern proved to match in time proportional to the text has no limit whatever this says. Both engines, V5 and V4. The value is fixed when the code is generated: change it and regenerate. |
--disableOptionalNamingHeuristics |
false |
Disable all optional naming heuristics at once (see Naming Heuristics) |
--disableNamingHeuristic |
— | Disable a specific naming heuristic by name (repeatable; see Naming Heuristics) |
--useUnixLineEndings |
false |
Use Unix line endings (\n) instead of Windows (\r\n) in generated files |
--yaml |
false |
Enable YAML schema support |
--addExplicitUsings |
false |
Include explicit using statements for standard implicit usings |
--engine |
V5 |
Code generation engine: V5 (Corvus.Text.Json) or V4 (legacy Corvus.Json.ExtendedTypes) |
--codeGenerationMode |
TypeGeneration |
TypeGeneration (types only), SchemaEvaluationOnly (standalone evaluator only), or Both |
--outputMapFile |
— | Write a JSON map of all generated files |
--force |
false |
Regenerate even when the output folder's lock file says nothing changed, and apply this run's options to everything the folder was generated from even when they differ from the lock's (see Lock file and regeneration) |
Examples:
# Generate with a custom root type name
corvusjson jsonschema Schemas/person.json \
--rootNamespace MyApp.Models \
--outputPath Generated/ \
--outputRootTypeName Person
# Generate from a specific definition within a schema
corvusjson jsonschema Schemas/api.json \
--rootNamespace MyApp.Models \
--outputPath Generated/ \
--rootPath "#/definitions/Address" \
--rebaseToRootPath
# Treat optional properties as nullable
corvusjson jsonschema Schemas/config.json \
--rootNamespace MyApp.Config \
--outputPath Generated/ \
--optionalAsNullable NullOrUndefined
config
Generate types from a configuration file that specifies multiple schemas and shared settings:
corvusjson config myconfig.json [--engine V5]
Example configuration file
{
"rootNamespace": "MyApp.Models",
"outputPath": "Generated/",
"useSchema": "Draft202012",
"assertFormat": true,
"formatMode": { "date-time": "disable", "time": "warning" },
"optionalAsNullable": "None",
"typesToGenerate": [
{
"schemaFile": "Schemas/person.json",
"outputRootTypeName": "Person"
},
{
"schemaFile": "Schemas/order.json",
"outputRootTypeName": "Order",
"outputRootNamespace": "MyApp.Models.Orders"
}
],
"additionalFiles": [
{
"canonicalUri": "https://example.com/schemas/address.json",
"contentPath": "Schemas/address.json"
}
],
"namedTypes": [
{
"reference": "https://example.com/schemas/person.json#/$defs/Name",
"dotnetTypeName": "PersonName",
"dotnetNamespace": "MyApp.Models"
}
],
"namespaces": {
"https://example.com/schemas/": "MyApp.ExternalModels"
}
}
The config command is ideal when you have multiple schemas to generate from, shared $ref dependencies, or you want to explicitly name generated types.
Root-level properties
| Property | Required | Default | Description |
|---|---|---|---|
rootNamespace |
Yes | — | The default .NET namespace for all generated types. Individual schemas can override this with outputRootNamespace. |
typesToGenerate |
Yes | — | Array of schemas to process. Each entry specifies a schema file and optional overrides (see below). |
outputPath |
No | — | Directory where generated .cs files are written. |
outputMapFile |
No | — | Path for a JSON manifest listing every generated file, useful for build systems that need to track outputs. |
useSchema |
No | Draft202012 |
Fallback schema draft when vocabulary analysis cannot determine the draft from the $schema keyword. Values: Draft4, Draft6, Draft7, Draft201909, Draft202012, OpenApi30. |
assertFormat |
No | true |
When true, the format keyword is enforced as a validation assertion. When false, format is treated as an annotation only (per the JSON Schema specification). |
unions |
No | true |
When true, a oneOf/anyOf type is also a C# union (switch and is patterns over its branch types with the C# 15 compiler, the .NET 11 SDK or later, any target framework except .NET Framework). Set to false to leave the union members out. |
formatMode |
No | — | A dictionary of format assertion mode overrides. Keys are format names (e.g. date-time); values are assert, disable, or warning. The wildcard key * sets the default for all formats (e.g. { "*": "disable" } for annotation-only output across every draft). A specific per-format override takes precedence over the * default, assertFormat, and the vocabulary's format-assertion behaviour. assert validates and fails on a non-conformant value; disable makes the format annotation-only; warning validates but always succeeds, emitting a WARNING annotation on a mismatch. Warning mode applies to string formats only; for a numeric format it falls back to assert. |
optionalAsNullable |
No | None |
Controls how optional properties are represented in generated types. None: optional properties use the same type as required properties — you check for Undefined explicitly. NullOrUndefined: optional properties generate as .NET nullable types (T?), and both JSON null and missing properties map to C# null. |
additionalFiles |
No | — | Pre-load external schema files so $ref references can resolve without network access (see below). |
namedTypes |
No | — | Explicitly assign .NET names to specific schema definitions that would otherwise get auto-generated names (see below). |
namespaces |
No | — | A dictionary mapping schema base URIs to .NET namespaces. Any schema whose canonical URI starts with a given key is generated into the corresponding namespace. For example, "https://example.com/schemas/": "MyApp.External" places all schemas under that URI prefix into MyApp.External. |
disableOptionalNameHeuristics |
No | false |
When true, disables all optional naming heuristics at once. Cannot be combined with disabledNamingHeuristics. |
disabledNamingHeuristics |
No | — | An array of specific naming heuristic names to disable. Use corvusjson listNameHeuristics to see available names. Cannot be combined with disableOptionalNameHeuristics. |
useImplicitOperatorString |
No | false |
When true, conversion operators to string are implicit rather than explicit. Use with care — implicit conversions can cause unintended string allocations. |
useUnixLineEndings |
No | false |
When true, generated files use Unix line endings (\n) instead of Windows (\r\n). |
supportYaml |
No | false |
When true, enables YAML support. Schema and document files can be YAML, JSON, or a mixture. |
addExplicitUsings |
No | false |
When true, generated files include using statements for standard implicit usings. Enable this if your project does not use implicit usings. |
typesToGenerate entries
Each entry in typesToGenerate specifies one schema to process:
| Property | Required | Default | Description |
|---|---|---|---|
schemaFile |
Yes | — | Path to the JSON Schema file to process, relative to the config file. |
outputRootTypeName |
No | Derived | The .NET type name for the root type generated from this schema. If omitted, the name is derived automatically using the standard naming heuristics — typically from the schema's title keyword, $id URI fragment, or the schema file name (see Naming Heuristics). |
outputRootNamespace |
No | rootNamespace |
Override the .NET namespace for this schema's root type. Useful when different schemas should go into different namespaces. |
rootPath |
No | — | A JSON Pointer within the schema document to use as the root type. For example, #/$defs/Address generates types starting from the Address definition rather than the document root. |
rebaseToRootPath |
No | false |
When true (and rootPath is set), the document is rebased as if the element at rootPath were the document root. This affects how $ref references within the extracted subtree are resolved. |
additionalFiles entries
Pre-load schema files that other schemas reference via $ref. This avoids network calls during generation and ensures deterministic, reproducible builds — particularly important in CI/CD environments:
| Property | Required | Description |
|---|---|---|
canonicalUri |
Yes | The canonical URI that other schemas use in their $ref to reference this file. Must match the URI used in $ref exactly. |
contentPath |
Yes | Local file path to the schema, relative to the config file. |
For example, if person.json contains "$ref": "https://example.com/schemas/address.json", add an entry with that URI pointing to your local copy of the address schema.
namedTypes entries
By default, the generator derives .NET type names from the schema structure. Use namedTypes to override specific types with explicit names — useful when the auto-generated name is awkward or when you need a type to live in a specific namespace:
| Property | Required | Description |
|---|---|---|
reference |
Yes | The fully-qualified schema reference (base URI + JSON Pointer fragment), e.g. https://example.com/schemas/person.json#/$defs/Name. |
dotnetTypeName |
Yes | The .NET type name to use, e.g. PersonName. |
dotnetNamespace |
No | When set, the type is generated directly in this namespace rather than as a nested type of its containing schema type. |
validateDocument
Validate a JSON or YAML document against a schema:
corvusjson validateDocument Schemas/person.json data.json
Output includes pass/fail status for each validation rule, with file path, line, column, and the schema evaluation location for failures.
listNameHeuristics
List all available naming heuristics. Some are optional and can be disabled with --disableNamingHeuristic:
corvusjson listNameHeuristics
version
Display the tool version and build information:
corvusjson version
Source Generator vs. CLI Tool
Both produce identical output — the same readonly struct types with the same property accessors, validation, serialization, and implicit conversions. The difference is when and how they run:
| Aspect | Source Generator | CLI Tool |
|---|---|---|
| When | At build time, automatically | On demand, from the command line |
| Triggered by | [JsonSchemaTypeGenerator] attribute |
Explicit corvusjson jsonschema invocation |
| Output location | obj/ (not checked in) |
Any directory (can be checked in) |
| IDE integration | Full IntelliSense as you type | IntelliSense after generation + build |
| Best for | Most projects | CI pipelines, inspecting output, multi-schema configs |
When to Use the CLI Tool
- You want to inspect or version-control the generated code
- You have a multi-schema configuration file with shared settings
- You need to pre-generate types in a CI/CD pipeline before the build
- You want to validate documents against schemas from the command line
- You are migrating from V4 and want to compare V4 vs V5 output
When to Use the Source Generator
- You want zero-configuration code generation at build time
- You prefer generated code to stay out of source control
- You want instant IntelliSense as you edit schema files
Generated Output
The tool generates readonly struct types that are thin wrappers over pooled JSON data. For a schema like:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": ["name"],
"properties": {
"name": { "type": "string", "minLength": 1 },
"age": { "type": "integer", "format": "int32", "minimum": 0 }
}
}
The tool generates:
- Type-safe property accessors:
person.Namereturns a strongly-typed string value - Validation:
person.EvaluateSchema()validates against the full schema. Apatternwhose match runs for longer than 1 second (or the limit--regexMatchTimeoutsets) throwsRegexMatchTimeoutException(see SchemaEvaluator.md). ApatternorpatternPropertiespattern that is not a valid ECMA-262 regular expression stops the generation with an error that names it (see RuntimeEvaluator.md). A pattern over a limit of size stops it with an error of its own, which says that the pattern is too large. All three hold for the V4 engine too: its types throw the exception fromValidate(). V4 generated code carries the same compiled matchers by default (V4's MSBuild property isCorvusJsonSchemaRegexEngine), andJsonPropertyName.IsMatchandValidate.MatchesPatterninCorvus.Json.ExtendedTypesmatch with them - Implicit conversions:
(string)person.Nameextracts the .NET value - Mutable builder:
person.CreateBuilder(workspace)creates a mutable copy - Immutable document factory:
Person.Create(...)builds a self-containedParsedJsonDocument<Person>in a single pass, with no workspace and no serialization round trip - Pattern matching:
Match()methods foroneOf/anyOfdiscriminated unions, plusMatchEvery()onanyOfcompositions to visit every matching subschema, threading an accumulator through the calls - C# unions (.NET 11 SDK or later, any target framework except .NET Framework): a
oneOf/anyOftype is also a C# union, soshape switch { Shape.Circle c => ..., Shape.Square s => ..., null => ... }andshape is Shape.Circle cwork, exhaustively and without boxing. Turn it off with--unions false(CLI),<CorvusTextJsonUnions>false</CorvusTextJsonUnions>(source generator) or"unions": false(config file) - Regular expressions: a pattern the runtime evaluator matches with a regular expression, and a
patternPropertiesname a type's helpers match, use Corvus.Regex.Ecma262, which answers as ECMAScript does over UTF-8 with no transcoding. The generator writes each distinct regular expression once, as the library's compiled matcher with the compiled regular expression as data, inCorvusJsonSchemaRegularExpressions.csbeside the program, so none is parsed or built at run time. A folder generated before the option existed is told so when regenerated: pass--forceto take the new default, or--regexEngine DotNet(CLI),<CorvusTextJsonRegexEngine>DotNet</CorvusTextJsonRegexEngine>(source generator) or"regexEngine": "DotNet"(config file) to keep .NET's - Serialization:
WriteTo(Utf8JsonWriter)for zero-allocation output - Schema provenance:
Person.JsonSchema.SchemaDocumentis the schema document the type was generated from, relative to the generation base (for exampleperson.json), andPerson.JsonSchema.SchemaLocationis the JSON Pointer to the schema within that document (for example/$defs/person). Together they form the referenceperson.json#/$defs/person. A schema inside a$idsub-resource still reports the containing document and the pointer from its root. Validation results and annotations report the same pointer shape.
Generated members that mirror a JsonElement member (TryGetProperty, the indexers, EnumerateObject, ParseValue, Clone, Freeze, the equality operators and the mutable partial's SetProperty and RemoveProperty, among others) carry /// <inheritdoc cref="global::Corvus.Text.Json.JsonElement..."/> instead of a copy of the runtime's documentation. IntelliSense and documentation tools resolve the reference from the Corvus.Text.Json.xml file shipped in the package, so the text you see is the runtime's; a consuming project's own XML documentation file carries the inheritdoc element as written. Members that have no runtime counterpart (property accessors, the Create and Build factories, schema constants) keep their own documentation, and non-public members carry none.
Analyzers and the generated folder
Every generated file starts with // <auto-generated/>, so analyzers that skip generated code (StyleCop, Roslynator, the Corvus analyzers and most of the .NET SDK's NetAnalyzers) never execute on it. A few analyzers analyse generated code regardless (in the .NET 10 SDK: CA2252, CA1418, CA1420, CA1421, and the Roslyn meta-analyzers that come with a Microsoft.CodeAnalysis reference); -p:ReportAnalyzer=true -v:d on a build shows what each costs. Because the output is checked in, an .editorconfig section over the folder both hides every analyzer diagnostic there and stops those analyzers executing on those files, without touching compiler diagnostics:
[Generated/**.cs]
dotnet_analyzer_diagnostic.severity = none
dotnet_diagnostic.CA2252.severity = none
dotnet_diagnostic.CA1418.severity = none
dotnet_diagnostic.CA1420.severity = none
dotnet_diagnostic.CA1421.severity = none
Do not add generated_code = true to such a section for files that lack the header: it also switches off the project's nullable context for them. A project that holds nothing but generated code can set <RunAnalyzers>false</RunAnalyzers> instead.
Schema Draft Support
| Draft | $schema URI |
--useSchema Value |
|---|---|---|
| Draft 4 | http://json-schema.org/draft-04/schema |
Draft4 |
| Draft 6 | http://json-schema.org/draft-06/schema |
Draft6 |
| Draft 7 | http://json-schema.org/draft-07/schema |
Draft7 |
| Draft 2019-09 | https://json-schema.org/draft/2019-09/schema |
Draft201909 |
| Draft 2020-12 | https://json-schema.org/draft/2020-12/schema |
Draft202012 |
| OpenAPI 3.0 | (custom vocabulary) | OpenApi30 |
The tool auto-detects the schema draft from the $schema keyword. Use --useSchema only as a fallback when the keyword is missing.
Lock file and regeneration
Every jsonschema run, and every run of a generation specification file, leaves a corvusjson-jsonschema.lock in its output folder. The lock records the folder's whole generation specification (every schema generated into the folder with its settings, and the options they share), the hash of every local schema and additional file, the generator version, and the files written.
The generated types in a folder share one CorvusJsonSchemaProgram, and each type refers to its entry in that program. A run into a folder that already holds generated code therefore adds its schemas to the specification the lock recorded and regenerates the whole set as one program. Ten runs with ten schemas into one folder give one consistent program; without the lock, each run would overwrite the program the earlier runs' types were wired to.
- A run whose schemas and options are already in the lock, with unchanged schema content, is skipped ("Up to date"). Pass
--forceto regenerate anyway. - A run whose options differ from the lock's (root namespace,
--nativeEnumsand the rest, the engine, the code generation mode) fails and names the options. Use the same options, pass--forceto apply the run's options to everything the folder was generated from, or delete the lock and the generated files to start the folder afresh. A schema with different per-schema settings (--outputRootTypeName,--rootPath) is not a conflict: the new settings replace the old ones for that schema. - Files an earlier run wrote that the run no longer generates (a renamed root type, for example) are deleted. Files the lock never listed are never touched.
- Local paths are recorded relative to the output folder, so the folder can move together with its schemas. Files reached only through
$refare not hashed; when one of them changes, pass--force. - The lock is restored from a backup if a run fails.
Output Map File
Use --outputMapFile to generate a JSON manifest of all generated files:
corvusjson jsonschema Schemas/person.json \
--rootNamespace MyApp.Models \
--outputPath Generated/ \
--outputMapFile generated.map.json
This is useful for build systems that need to track which files were generated.
Naming Heuristics
When --outputRootTypeName is not specified, the tool applies a chain of naming heuristics to derive an idiomatic C# type name from the schema. Heuristics are tried in priority order (lowest number first) and the first match wins. The same heuristics are also applied to nested types, sub-schemas, and property types throughout the generated code.
How the root type name is determined
The tool tries these sources in order:
- Explicit name (
--outputRootTypeNameoroutputRootTypeNamein config) — used as-is, no heuristics applied - Custom keywords (priority 100) — the
titlekeyword or the last segment of the$idURI - Schema reference (priority 1000) — the last segment of the JSON Pointer fragment (e.g.,
#/$defs/Address→Address) or the schema file name without extension (e.g.,person.json→Person) - Documentation (priority 1500, optional) — the schema
descriptionif it is short (under 64 characters) and unique within its parent - Fallback — a name derived from the file path
For example, given this schema in Schemas/person.json:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Person",
"type": "object",
"properties": {
"name": { "type": "string" }
}
}
The root type name is Person (from the title keyword). If title were absent, it would be Person (from the file name person.json).
Full heuristic list
Heuristics marked optional can be disabled with --disableOptionalNamingHeuristics (disables all optional) or --disableNamingHeuristic <name> (disables one at a time). Non-optional heuristics are always active.
| Priority | Heuristic | Optional | What it does |
|---|---|---|---|
| 1 | WellKnownTypeNameHeuristic |
No | Maps well-known schema patterns to built-in types (e.g., the "any" schema maps to JsonElement). |
| 2 | SimpleCoreTypeNameHeuristic |
No | Maps simple schemas with a single core type and optional format to global types: JsonString, JsonInt32, JsonUuid, JsonBoolean, etc. These types are shared — no per-schema code is generated. |
| 2 | BuiltIn*TypeNameHeuristic |
No | A family of heuristics (BuiltInStringTypeNameHeuristic, BuiltInNumberTypeNameHeuristic, etc.) that map bare "type": "string", "type": "number", "type": "integer", "type": "boolean", "type": "null", "type": "object", and "type": "array" schemas to their respective global types. |
| 100 | CustomKeywordNameHeuristic |
No | Extracts the name from the title keyword or the last segment of the $id URI. This is the most common source for root type names. |
| 1000 | BaseSchemaNameHeuristic |
No | For root or $defs-level schemas: extracts the name from the JSON reference fragment (e.g., #/$defs/Address → Address) or the schema file name (e.g., address.json → Address). |
| 1500 | DocumentationNameHeuristic |
Yes | Uses the schema description as the type name if it is short enough (under 64 characters) and unique within its parent. |
| 1500 | DefaultValueNameHeuristic |
Yes | For schemas with only a default keyword: generates names like DefaultValueTrue or DefaultValueActive. |
| 1550 | RequiredPropertyNameHeuristic |
Yes | For nested schemas: uses Required + property names, e.g., a schema requiring id and name becomes RequiredIdAndName. |
| 1600 | ConstPropertyNameHeuristic |
Yes | For nested schemas with const properties: uses With + const values, e.g., WithStatusActive. |
| 1600 | SingleTypeArrayNameHeuristic |
Yes | For array schemas: appends Array to the item type name, e.g., an array of Item becomes ItemArray. |
| 10000 | SubschemaNameHeuristic |
No | For inline nested schemas: extracts the name from the JSON reference path segment. |
| 11000 | PathNameHeuristic |
No | Final fallback for nested schemas: extracts the name from the last path segment of the JSON reference. |
Collision resolution
When a derived name collides with its parent type or a sibling, the tool automatically appends a suffix based on the schema kind (Object, Array, String, etc.). For example, if both a parent and child would be named User, the child becomes UserObject.
Disabling heuristics
# Disable a specific heuristic by name
corvusjson jsonschema Schemas/person.json \
--rootNamespace MyApp.Models \
--outputPath Generated/ \
--disableNamingHeuristic DocumentationNameHeuristic
# Disable all optional heuristics at once
corvusjson jsonschema Schemas/person.json \
--rootNamespace MyApp.Models \
--outputPath Generated/ \
--disableOptionalNamingHeuristics
Run corvusjson listNameHeuristics to see all available heuristics and which ones are optional.