LateApexEarlySpeed.Json.Schema 4.2.0

dotnet add package LateApexEarlySpeed.Json.Schema --version 4.2.0
                    
NuGet\Install-Package LateApexEarlySpeed.Json.Schema -Version 4.2.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="LateApexEarlySpeed.Json.Schema" Version="4.2.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="LateApexEarlySpeed.Json.Schema" Version="4.2.0" />
                    
Directory.Packages.props
<PackageReference Include="LateApexEarlySpeed.Json.Schema" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add LateApexEarlySpeed.Json.Schema --version 4.2.0
                    
#r "nuget: LateApexEarlySpeed.Json.Schema, 4.2.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package LateApexEarlySpeed.Json.Schema@4.2.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=LateApexEarlySpeed.Json.Schema&version=4.2.0
                    
Install as a Cake Addin
#tool nuget:?package=LateApexEarlySpeed.Json.Schema&version=4.2.0
                    
Install as a Cake Tool

Lateapexearlyspeed.Json.Schema

This is a high performance Json schema .Net implementation library based on Json schema, support draft7, draft2019 and draft2020 (most commonly used, stable, LTS and latest versions), and supports opt-in annotation collection.

This library also supports fluent validation and validator generation from your class code.

More schema validation options like case-insensitive property names matching, dialect (schema version) selection, Regex cache, output format and so on, please check wiki.


The json validation functionalities have passed official json schema test-suite for draft7, draft2019 and draft2020 (except cases about limitation listed below). Annotation collection is also tested against the official JSON Schema annotation test suite.

High performance - this .Net library has good performance compared with existing more popular and excellent .Net implementations in common cases by BenchmarkDotnet result. Update: there is a blog article to demonstrate performance advantage of this library comparing with other existing popular implementations based on official Json Schema test suite.

Some Benchmark result:

12th Gen Intel Core i7-12800H, 1 CPU, 20 logical and 14 physical cores

[Host] : .NET 6.0.25 (6.0.2523.51912), X64 RyuJIT AVX2

DefaultJob : .NET 6.0.25 (6.0.2523.51912), X64 RyuJIT AVX2

Valid data case: | Method | Mean | Error | StdDev | Gen0 | Gen1 | Allocated | |----------------------------------- |------------:|----------:|----------:|--------:|-------:|----------:| | ValidateByPopularSTJBasedValidator | 29.80 us | 0.584 us | 0.573 us | 4.4556 | 0.2441 | 55.1 KB | | ValidateByThisValidator | 15.99 us | 0.305 us | 0.300 us | 1.9531 | - | 24.2 KB |

Invalid data case: | Method | Mean | Error | StdDev | Median | Gen0 | Gen1 | Allocated | |----------------------------------- |------------:|----------:|-----------:|------------:|--------:|-------:|----------:| | ValidateByPopularSTJBasedValidator | 65.04 us | 2.530 us | 7.341 us | 66.87 us | 4.5776 | 0.1221 | 56.42 KB | | ValidateByThisValidator | 15.47 us | 1.160 us | 3.421 us | 17.14 us | 1.4954 | - | 18.45 KB |

Note: "STJ" means "System.Text.Json" which is built-in json package in .net sdk, this library is also based on it.

Note: the benchmark schema below uses draft 2020-12 specific keywords like prefixItems for demonstration. You can still select validation dialect by setting JsonValidatorOptions.DefaultDialect.

Benchmark Schema:

{
  "$id": "http://main",
  "type": "object",

  "additionalProperties": false,
  "patternProperties": {
    "propB*lean": {
      "type": "boolean"
    }
  },
  "dependentRequired": {
    "propNull": [ "propBoolean", "propArray" ]
  },
  "dependentSchemas": {
    "propNull": {
      "type": "object"
    }
  },
  "propertyNames": true,
  "required": [ "propNull", "propBoolean" ],
  "maxProperties": 100,
  "minProperties": 0,
  "properties": {
    "propNull": {
      "type": "null"
    },
    "propBoolean": {
      "type": "boolean",
      "allOf": [
        true,
        { "type": "boolean" }
      ]
    },
    "propArray": {
      "type": "array",
      "anyOf": [ false, true ],
      "contains": { "type": "integer" },
      "maxContains": 100,
      "minContains": 2,
      "maxItems": 100,
      "minItems": 1,
      "prefixItems": [
        { "type": "integer" }
      ],
      "items": { "type": "integer" },
      "uniqueItems": true
    },
    "propNumber": {
      "type": "number",
      "if": {
        "const": 1.5
      },
      "then": true,
      "else": true,
      "enum": [ 1.5, 0, 1 ]
    },
    "propString": {
      "type": "string",
      "maxLength": 100,
      "minLength": 0,
      "not": false,
      "pattern": "abcde"
    },
    "propInteger": {
      "$ref": "#/$defs/typeIsInteger",
      "exclusiveMaximum": 100,
      "exclusiveMinimum": 0,
      "maximum": 100,
      "minimum": 0,
      "multipleOf": 0.5,
      "oneOf": [ true, false ]
    }
  },
  "$defs": {
    "typeIsInteger": { "$ref": "http://inside#/$defs/typeIsInteger" },
    "toTestAnchor": {
      "$anchor": "test-anchor"
    },
    "toTestAnotherResourceRef": {
      "$id": "http://inside",
      "$defs": {
        "typeIsInteger": { "type": "integer" }
      }
    }
  }
}

Valid benchmark data:

{
  "propNull": null,
  "propBoolean": true,
  "propArray": [ 1, 2, 3, 4, 5 ],
  "propNumber": 1.5,
  "propString": "abcde",
  "propInteger": 1
}

Invalid benchmark data:

{
  "propNull": null,
  "propBoolean": true,
  "propArray": [ 1, 2, 3, 4, 4 ], // Two '4', duplicated
  "propNumber": 1.5,
  "propString": "abcde",
  "propInteger": 1
}

Basic Usage

Install-Package LateApexEarlySpeed.Json.Schema
string jsonSchema = File.ReadAllText("schema.json");
string instance = File.ReadAllText("instance.json");

var jsonValidator = new JsonValidator(jsonSchema);
ValidationResult validationResult = jsonValidator.Validate(instance);

if (validationResult.IsValid)
{
    Console.WriteLine("good");
}
else
{
    Console.WriteLine($"Failed keyword: {validationResult.Keyword}");
    Console.WriteLine($"ResultCode: {validationResult.ResultCode}");
    Console.WriteLine($"Error message: {validationResult.ErrorMessage}");
    Console.WriteLine($"Failed instance location: {validationResult.InstanceLocation}");
    Console.WriteLine($"Failed relative keyword location: {validationResult.RelativeKeywordLocation}");
    Console.WriteLine($"Failed schema resource base uri: {validationResult.SchemaResourceBaseUri}");
}

Dialect Selection Example

using LateApexEarlySpeed.Json.Schema.Keywords;

var jsonValidator = new JsonValidator(jsonSchema, new JsonValidatorOptions
{
    DefaultDialect = DialectKind.Draft7
});

ValidationResult validationResult = jsonValidator.Validate(instance);

Output Information

When validation failed, you can check detailed error information by:

  • IsValid: As summary indicator for passed validation or failed validation.

  • ResultCode: The specific error type when validation failed.

  • ErrorMessage: the specific wording for human readable message

  • Keyword: current keyword when validation failed

  • InstanceLocation: The location of the JSON value within the instance being validated. The value is a JSON Pointer.

  • RelativeKeywordLocation: The relative location of the validating keyword that follows the validation path. The value is a JSON Pointer, and it includes any by-reference applicators such as "$ref" or "$dynamicRef". Eg:

    /properties/width/$ref/minimum
    
  • SubSchemaRefFullUri: The absolute, dereferenced location of the validating keyword when validation failed. The value is a full URI using the canonical URI of the relevant schema resource with a JSON Pointer fragment, and it doesn't include by-reference applicators such as "$ref" or "$dynamicRef" as non-terminal path components. Eg:

    https://example.com/schemas/common#/$defs/count/minimum
    
  • SchemaResourceBaseUri: The absolute base URI of referenced json schema resource when validation failed. Eg:

    https://example.com/schemas/common
    

Annotation Output

Annotation collection is opt-in. To collect annotations, enable JsonValidatorOptions.CollectAnnotations when creating the JsonValidator, and validate with OutputFormat.List:

var jsonValidator = new JsonValidator(jsonSchema, new JsonValidatorOptions
{
  CollectAnnotations = true
});

ValidationResult validationResult = jsonValidator.Validate(instance, new JsonSchemaOptions
{
  OutputFormat = OutputFormat.List
});

foreach (var annotation in validationResult.Annotations)
{
  Console.WriteLine($"{annotation.Keyword}: {annotation.Value}");
}

Annotations are reported from ValidationResult.Annotations only for successful schema applications and only when the annotation keyword applies to the instance type.

Annotation output includes Keyword, Value, and the same location/context fields described above for validation errors: InstanceLocation, RelativeKeywordLocation, SubSchemaRefFullUri, and SchemaResourceBaseUri.

For more details, see Annotation Support.

Performance Tips

Reuse instantiated JsonValidator instances (which basically represent json schema) to validate incoming json instance data if possible in your cases, to gain better performance.

External json schema document reference support

Besides of internal sub schema resource reference (inside current json schema document) support automatically, implementation supports external schema document reference support by:

  • local schema text
var jsonValidator = new JsonValidator(jsonSchema);
string externalJsonSchema = File.ReadAllText("schema2.json");
jsonValidator.AddExternalDocument(externalJsonSchema);
ValidationResult validationResult = jsonValidator.Validate(instance);
  • remote schema url (library will retrieve actual schema content by access network)
var jsonValidator = new JsonValidator(jsonSchema);
await jsonValidator.AddHttpDocumentAsync(new Uri("http://this-is-json-schema-document"));
ValidationResult validationResult = jsonValidator.Validate(instance);
  • dynamic registration during validation — provide an IExternalSchemaDocumentPopulator implementation to resolve and register an external schema automatically at the moment the validator first encounters an unregistered $ref target:
var jsonValidator = new JsonValidator(jsonSchema)
{
    ExternalSchemaDocumentPopulator = new FileSystemSchemaPopulator()
};
ValidationResult validationResult = jsonValidator.Validate(instance);

// Sample IExternalSchemaDocumentPopulator implementation
public class FileSystemSchemaPopulator : IExternalSchemaDocumentPopulator
{
    // Called at validation time when a $ref target is not yet registered
    public void Populate(Uri baseUri, ExternalSchemaRegistry externalSchemaRegistry)
    {
        string filePath = baseUri.LocalPath;
        if (File.Exists(filePath))
        {
            externalSchemaRegistry.Register(File.ReadAllText(filePath));
        }
    }
}

The populator is called only once per base URI — the result is cached in the validator's global resource registry.

Custom keyword support

Besides of standard keywords defined in json schema specification, library supports to create custom keyword for additional validation requirement. Eg:

{
  "type": "object",
  "properties": {
    "prop1": {
      "customKeyword": "Expected value"
    }
  }
}
ValidationKeywordRegistry.Global.AddKeyword<CustomKeyword>();
[Keyword("customKeyword")] // It is your custom keyword name
[JsonConverter(typeof(CustomKeywordJsonConverter))] // Use 'CustomKeywordJsonConverter' to deserialize to 'CustomKeyword' instance out from json schema text
internal class CustomKeyword : KeywordBase
{
    private readonly string _customValue; // Simple example value

    public CustomKeyword(string customValue)
    {
        _customValue = customValue;
    }

    // Do your custom validation work here
    protected override ValidationResult ValidateCore(JsonInstanceElement instance, JsonSchemaOptions options)
    {
        if (instance.ValueKind != JsonValueKind.String)
        {
            return ValidationResult.ValidResult;
        }

        return instance.GetString() == _customValue
            ? ValidationResult.ValidResult
            : ValidationResult.CreateFailedResult(ResultCode.UnexpectedValue, "It is not my expected value.", options.ValidationPathStack, Name, instance.Location);
    }
}
internal class CustomKeywordJsonConverter : JsonConverter<CustomKeyword>
{
    // Library will input json value of your custom keyword: "customKeyword" to this method.
    public override CustomKeyword? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        // Briefly: 
        return new CustomKeyword(reader.GetString()!);
    }

    public override void Write(Utf8JsonWriter writer, CustomKeyword value, JsonSerializerOptions options)
    {
        throw new NotImplementedException();
    }
}

Per-JsonValidatorOptions custom keyword

Besides registering custom keywords in the process-wide ValidationKeywordRegistry.Global, you can also register them on a specific JsonValidatorOptions instance through its KeywordRegistry. Custom keywords registered this way are isolated to the JsonValidator instances created with that options instance, so different schemas can have their own independent custom keywords:

var options = new JsonValidatorOptions();
options.KeywordRegistry.AddKeyword<CustomKeyword>();

var validator = new JsonValidator(schema, options);

A keyword implementation is resolved per keyword name and dialect. The per-JsonValidatorOptions level KeywordRegistry takes higher precedence than ValidationKeywordRegistry.Global: the global registry is only consulted when the per-options registry has no implementation registered for that keyword name and dialect (even if the same keyword name is registered there for other dialects).

Format support

This library supports following formats currently:

  • uri
  • uri-reference
  • date
  • time
  • date-time
  • email
  • uuid
  • hostname
  • ipv4
  • ipv6
  • json-pointer
  • regex

If require more format, implement a custom FormatValidator, and register it:

public class TestCustomFormatValidator : FormatValidator
{
    public override bool Validate(string content)
    {
        // custom format validation logic here...
    }
}

// register it globally
FormatRegistry.Global.AddFormat("custom_format", () => new TestCustomFormatValidator());

Per-JsonValidatorOptions custom format

Besides registering custom formats in the process-wide FormatRegistry.Global, you can also register them on a specific JsonValidatorOptions instance through its FormatRegistry. Custom formats registered this way are isolated to the JsonValidator instances created with that options instance, so different schemas can have their own independent custom formats:

var options = new JsonValidatorOptions();
options.FormatRegistry.AddFormat("custom_format", () => new TestCustomFormatValidator());

var validator = new JsonValidator(schema, options);

A format validator implementation is resolved per format name. The per-JsonValidatorOptions level FormatRegistry takes higher precedence than FormatRegistry.Global: the global registry is only consulted when the per-options registry has no implementation registered for that specific format name.

Other extension usage doc is to be continued .

Limitation

  • Annotation collection is supported as an opt-in feature.
  • Not support following keywords currently: unevaluatedProperties, unevaluatedItems
  • Not support automatic content-encoded string decoding/validation currently

Issue report

Welcome to raise issue and wishlist, I will try to fix if make sense, thanks !

More doc is to be written

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  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 was computed.  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 was computed.  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. 
.NET Core netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.1 is compatible. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (4)

Showing the top 4 NuGet packages that depend on LateApexEarlySpeed.Json.Schema:

Package Downloads
LateApexEarlySpeed.Xunit.Assertion.Json

Provide fluent json assertion for Xunit

LateApexEarlySpeed.Xunit.V3.Assertion.Json

Provide fluent json assertion for Xunit v3

LateApexEarlySpeed.EntityFrameworkCore.V6.Json.Schema

Provide schema of json column for EntityFramework Core

LateApexEarlySpeed.EntityFrameworkCore.V3.Json.Schema

Provide schema of json column for EntityFramework Core

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
4.2.0 129 9/2/2026
4.1.0 2,567 8/9/2026
4.0.0 2,161 7/22/2026
3.4.0 331 7/16/2026
3.3.0 1,862 6/25/2026
3.2.1 3,088 6/11/2026
3.2.0 5,030 4/20/2026
3.1.0 473 4/3/2026
3.0.3 3,309 3/17/2026
3.0.2 384 2/26/2026
3.0.1 362 2/18/2026
3.0.0 270 2/11/2026
2.1.6 5,192 12/4/2025
2.1.5 765 11/21/2025
2.1.4 50,916 11/7/2025
2.1.3 258 10/22/2025
2.1.2 1,608 10/14/2025
2.1.1 11,486 9/2/2025
2.1.0 7,439 8/17/2025
2.0.2 20,929 6/24/2025
Loading failed