Skip to content
Purview Dev logo

Documentation section: Serialization

JSON Schema Import

Prerelease. These pages describe 3.0.0-prerelease.2 and can differ from the published release. Read the Live 2.4.2 documentation instead.

Stable ZodSharp Reviewed 2026-11-03 purview-dev/zodsharp Star on GitHub 2 aspnetcore c-sharp csharp dotnet json-schema newtonsoft-json schema source-generator system-text-json validation zero-allocation zod

Import a JSON Schema into a Purview.ZodSharp schema with Z.FromJsonSchema. This API is provided by the JSON integration packages — reference either Purview.ZodSharp.SystemTextJson or Purview.ZodSharp.NewtonsoftJson. The two JSON integrations are mutually exclusive; pick one and import its JSON Schema namespace (ZodSharp.JsonSchema.SystemTextJson or ZodSharp.JsonSchema.NewtonsoftJson).

using ZodSharp;
using ZodSharp.JsonSchema.SystemTextJson; // or ZodSharp.JsonSchema.NewtonsoftJson
var jsonSchemaString = """
{
"type": "object",
"properties": {
"name": { "type": "string", "minLength": 3 },
"email": { "type": "string", "format": "email" }
},
"required": ["name", "email"]
}
""";
var userSchema = Z.FromJsonSchema(jsonSchemaString);
var result = userSchema.Validate(userData);
Signature Notes
IZodSchema<object, object> FromJsonSchema(string jsonSchema) parses the JSON string into a JsonSchemaDefinition, then into a schema
IZodSchema<object, object> FromJsonSchema(JsonSchemaDefinition schema) import from an already-deserialized definition

FromJsonSchemaParser (namespace ZodSharp.JsonSchema.SystemTextJson or ZodSharp.JsonSchema.NewtonsoftJson) maps:

  • type — string / number / integer / boolean / null / object / array.
  • enum → ZodUnion of literals (a single member becomes a literal); const → literal.
  • anyOf / oneOf → ZodUnion; allOf → the intersection of every branch (and the base schema when the schema also declares a type). A type-less branch is any, matching Zod’s fromJSONSchema.
  • String constraints — minLength, maxLength, pattern, and format (email, uri, uuid). The uuid/guid format maps to the versionless .UUID(); a versioned .UUID(UuidVersion.V7) schema exports format: "uuid" plus a version-pinning pattern, and the allOf composition preserves that pattern on import. Multiple pattern constraints are composed under allOf, and every branch is applied.
  • Numeric constraints — minimum, maximum, multipleOf; integer additionally applies .Int().
  • Objects — required and optional fields via Z.Object().Field(...).
  • Arrays — items, minItems, maxItems. A nullable item schema (items.anyOf containing a {"type":"null"} branch) imports as a nullable element, matching z.array(T.nullable()) and the shape the generated exporter emits for an allow-null collection.
  • Records — an object with no properties and an additionalProperties schema imports as a record: every member is validated by the value schema, and a failure is reported under a typed key segment for the member (FullPath.ToDottedString() renders [a]; the legacy Path is ["a"]). additionalProperties: true treats every member as any; false (and an absent value) keeps the object contract.
var schema = Z.FromJsonSchema(
"""
{
"type": "object",
"additionalProperties": { "type": "string", "minLength": 1 }
}
"""
);
schema.Validate(new Dictionary<string, object?> { ["a"] = "x" }); // Success
schema.Validate(new Dictionary<string, object?> { ["a"] = "" }); // Failure at [a] (key segment)

This is the shape the generated exporter writes for a dictionary property, so a generated dictionary schema round-trips through Z.FromJsonSchema in either integration package. The result is a record schema (the Z.Record shape), not an object with fixed properties.

Imported schemas report the same structured errors as the equivalent fluent schemas:

  • Type mismatches describe JSON kinds with Zod’s type names - "Expected string, but got number" - and never name JsonElement, JToken or another CLR type. The expected/received parameters carry the same names.
  • minItems/maxItems report exactly what ZodArray reports: "Array must have at least 2 elements, but got 1", origin array, both bounds with their inclusivity, and category too_small/too_big.
  • Literal mismatches (const, single-member enum) name the expected literal and only the kind of the received value; the value itself is not echoed.
  • Array elements and record values are re-pathed with typed index and key segments ([1], [a]), keeping every field of the inner error (origin, bounds, category, parameters).
  • $ref is supported only for local references (#/..., for example #/$defs/Name). External $ref targets throw NotSupportedException naming the unsupported reference:

    External $ref 'external.json#/$defs/name' is not supported. Only local references ('#/...', for example '#/$defs/Name') can be resolved; inline or pre-resolve external schemas before importing.

    Inline the referenced schema, or move it under the root $defs, before importing. Local references may be cyclic — a reference that is still being resolved becomes a lazy schema.

  • The reader binds the JSON Schema keyword names $schema, $id, $ref, and $defs (plus the draft-07 definitions) through the integration package’s JsonSchemaSerializerOptions, and the same options write them back, so exported definitions round-trip through either package. JsonSchemaDefinition itself carries no serializer annotations, so serialize it with JsonSchemaSerializerOptions to get keyword-compliant output.

Export a TypeScript/Zod schema to JSON Schema (Zod v4+ z.toJSONSchema) and import it on the backend:

import { z } from "zod";
const UserSchema = z.object({ username: z.string().min(3), email: z.string().email() });
const jsonSchema = z.toJSONSchema(UserSchema);
var userSchema = Z.FromJsonSchema(jsonSchemaString);
var result = userSchema.Validate(incomingData);

See Cross-Platform Interop for the repository’s fixture-based verification of this loop.