Generate a JSON Schema from a C# class by serializing a representative instance of that class into strict JSON and pasting the result into the JSON Schema Generator, which infers a Draft 2020-12 schema locally in the browser without reflection or runtime metadata. The tool does not parse .cs files, read attributes, or consult generic constraints; it reads JSON values and maps each observed primitive, array, and object to its corresponding schema keyword, so the C# class acts only as the blueprint for the sample you feed in. Once generated, properties seen in every merged object sample land in the required array, null values add null to the type union, mixed numeric types widen from integer to number, and incompatible observed types produce deterministic anyOf branches. Property names are preserved exactly, including punctuation and prototype-shaped spellings, while additionalProperties is intentionally left unspecified because one sample cannot prove that unseen keys are forbidden. Output is deterministic for a given input, so the same JSON always produces the same schema text suitable for review and version control.

The C# Class to JSON Schema Workflow
C# developers reach for JSON Schema for several distinct reasons: contract-first API design with OpenAPI, runtime validation in ASP.NET request pipelines, integration tests against third-party payloads, and code generation from schema down to records or DTOs. The natural mental model is therefore class → schema, but the JSON Schema Generator inverts that direction and works only from a JSON sample. That inversion is not a limitation to fight; it is the most reliable starting point, because a JSON payload is the ground truth of what your service actually emits or accepts on the wire. Annotations like [Required], [JsonPropertyName], and nullable reference types may or may not be honored by your serializer, and reflection-based schema generators can drift between Roslyn versions. By contrast, the generated schema describes the exact shape the wire sees, so closing the gap between the C# model and the contract becomes a matter of matching the sample, not auditing the toolchain. If you build inside Visual Studio, a companion walk-through on setting up a JSON Schema in Visual Studio from a sample shows how the same Draft 2020-12 artifact plugs into IntelliSense and editor validation.
How the Generator Maps JSON to a C#-Shaped Schema
The inference is deliberately literal. JSON strings, safe integers, other numbers, Booleans, and null map to their corresponding JSON Schema types, and arrays use items inferred from every observed element. When you serialize a List<int> with values [1, 2, 3], the generator sees three integers and emits type: array with items: { type: integer }. An empty array, however, produces an empty items schema because no element constraint can be justified from zero observations; this is a deliberate choice that surfaces uncertainty rather than fabricating a guess. The following table summarizes the mapping the generator applies between common C# property types and the schema keywords it produces from the serialized sample.
| C# Property Type | Sample JSON Value | Inferred Schema Keyword |
|---|---|---|
| string | "hello" | type: "string" |
| int (safe) | 42 | type: "integer" |
| long or large decimal | 9999999999 | type: "integer" |
| bool | true | type: "boolean" |
| DateTime (string form) | "2025-01-15T00:00:00Z" | type: "string" |
| Nullable<T> with null observed | null | adds "null" to type array, keeps properties or items |
| List<int> | [1, 2, 3] | type: "array", items: { type: "integer" } |
| List<T> (empty) | [] | type: "array", items: {} |
| Custom class | { "id": 1, "name": "Ada" } | type: "object", properties, required (when present in every merged sample) |
| List<object> mixed types | [1, "two"] | items: { anyOf: [{type: "integer"}, {type: "string"}] } |
Nested objects and arrays are inferred recursively, which means a Customer class containing an Address class and a List<Order> produces a fully expanded schema tree in a single pass. Mixed compatible numbers, for example one integer and one double in the same array, widen integer to number, while incompatible observed types become deterministic anyOf branches. The generator never invents a closed-object policy, so the resulting schema treats unknown keys as acceptable until your authoritative contract says otherwise.
Generate the Schema Step by Step
- Serialize a representative instance of your C# class to strict JSON using System.Text.Json, Newtonsoft.Json, or your preferred serializer with the production naming policy and null-handling settings.
- Paste the resulting JSON into the JSON Schema Generator input area and optionally enter a concise schema title in the title field.
- Click generate and review the output for required fields, nullable properties, array items, anyOf branches, and any missing keys you expected to see in the sample.
- Copy the generated Draft 2020-12 schema text into your editor, repository, or schema registry of choice.
- Run both valid and invalid instances through a Draft 2020-12-capable production validator configured exactly as your runtime will use it.
The whole process runs locally in the browser: your JSON is parsed in the current tab, the page does not upload data, run a validator, or contact a schema registry, so proprietary payloads can be inspected during development without leaving your machine.
Required Fields, Nullable Properties, and anyOf Branches
Three patterns show up repeatedly in C#-derived schemas and are worth understanding before you trust the output. Required fields come only from presence: a property observed in every merged object sample lands in the required array, while a property missing from at least one object is left out of required. Nullable properties show up as null combined with a typed value, which adds null to the type array while retaining the original properties or items schema. For example, a C# string? that serialized to "hello" stays type: string, while one that serialized to null becomes type: ["string", "null"]. anyOf branches appear when the generator observes incompatible types in the same array slot or merged object position, and duplicate equivalent branches are removed so the output stays reviewable. C# polymorphism through abstract base classes or interfaces tends to surface here as anyOf, because each concrete type produces a different observed shape and the merger has no way to know which subtype is canonical.
What the Generator Refuses to Infer
This is structural sample inference, not schema discovery. The generator does not guess format, pattern, enum, const, minimum, maximum, multipleOf, lengths, uniqueness, descriptions, defaults, examples, deprecation, readOnly, writeOnly, content encoding, references, anchors, IDs, conditional logic, unevaluated properties, or business meaning. Every one of those keywords must be added from authoritative requirements or domain documentation. A small sample is insufficient evidence for a semantic constraint: an enum cannot be inferred from one value, a regex cannot be inferred from one string, and a range cannot be inferred from one number. The same reasoning explains why additionalProperties is absent: one sample cannot prove that unobserved keys are forbidden, so the generated object stays open until your contract explicitly closes it. Add these keywords manually, then re-validate against the exact Draft 2020-12-capable validator you ship to production, because format may be annotation-only depending on the vocabulary and validator configuration you chose. The dialect URI at https://json-schema.org/draft/2020-12 sets the contract, but each library interprets edge-case keywords slightly differently.
Limits, Errors, and Validator Choice
The generator enforces four hard limits: 500,000 input characters, 50,000 values, 40 nesting levels, and 1,000,000 output characters. Boundary failures emit no partial schema, which means a malformed or oversized input produces an explicit error instead of a silently truncated contract you might ship. Strict JSON parsing rules apply throughout: comments, trailing commas, NaN, Infinity, undefined, BigInt, and JavaScript literals all fail, and JavaScript parses the sample number before inference so only safe integers become type integer. Precision-sensitive decimals and identifiers may need to be carried as strings plus application-level validation. Once the schema is copied, test both positive and negative instances with the exact validator, configuration, and vocabulary used in production. The Draft 2020-12 dialect declaration referenced in the JSON Schema core specification is not a guarantee that every library interprets every keyword identically, so contract drift between tools is a real failure mode you can only catch by testing.
For a deeper look, see Check Generated Rust Structs After JSON Conversion.