using System.Collections;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.ComponentModel;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
using Newtonsoft.Json.Schema;
using Newtonsoft.Json.Schema.Generation;
internal class CustomNameRuleValidator : JsonValidator
const string CustomRulePropertyName = "customNameRule";
public override bool CanValidate(JSchema schema) =>
schema.ExtensionData.ContainsKey(CustomRulePropertyName);
public override void Validate(JToken value, JsonValidatorContext context)
var customRuleValue = (string)context.Schema.ExtensionData[CustomRulePropertyName];
if (value.Type != JTokenType.String || !((string)value).StartsWith(customRuleValue))
context.RaiseError($"Property value {value.ToString(Formatting.None)} does not begin with {customRuleValue}");
public static void Test()
var customNameRuleSchemaJson =
"description":"A configuration setting value",
"customNameRule" : "Setting",
"required": ["name", "value"],
Test(customNameRuleSchemaJson);
public static void Test(string schemaJson)
var settingJson = @"{ 'name': 123 /* Was 'AA' */, 'value': 'XYZ' }";
var setting = JToken.Parse(settingJson);
var readerSettings = new JSchemaReaderSettings
Validators = [new CustomNameRuleValidator()]
var schema = JSchema.Parse(schemaJson, readerSettings);
var isValid = setting.IsValid(schema, out IList<string> errors);
Console.WriteLine(setting.ToString(Formatting.None));
Console.WriteLine(schema);
"IsValid={0}, Errors:\r\n{1}",
errors != null ? string.Join("\r\n", errors) : string.Empty);
public static void Main()
Console.WriteLine("Environment version: {0} ({1}), {2}", System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription , Environment.Version, Environment.OSVersion);
Console.WriteLine("Json.NET version: " + typeof(JsonSerializer).Assembly.FullName);
Console.WriteLine("Json.NET Schema version: " + typeof(Newtonsoft.Json.Schema.JSchema).Assembly.FullName);
Console.WriteLine("Failed with unhandled exception: ");
Rather than using C# inheritance to combine a custom validation rule with built-in rules, you could use the [composition](https:
For instance, adapting an example from your [other question](https:
{"name":"AA","value":"XYZ"}
"description": "A configuration setting value",
"required": [ "name", "value" ]
And you would like to insert some custom rules for the value of the `"name"` property such as ensuring that the value of `"name"` starts with some prefix specified in the schema such as `"Setting"`<sup>[1]</sup>. To do this, introduce the following `JsonValidator` which will validate that a value whose schema object includes a `"customNameRule"` property has the required prefix:
internal class CustomNameRuleValidator : JsonValidator
const string CustomRulePropertyName = "customNameRule";
public override bool CanValidate(JSchema schema) =>
schema.ExtensionData.ContainsKey(CustomRulePropertyName);
public override void Validate(JToken value, JsonValidatorContext context)
var customRuleValue = (string)context.Schema.ExtensionData[CustomRulePropertyName];
if (value.Type != JTokenType.String || !((string)value).StartsWith(customRuleValue))
context.RaiseError($"Property value {value.ToString(Formatting.None)} does not begin with {customRuleValue}");
Then modify the schema by wrapping the existing schema for `"name"` in an `allOf` and appending a schema with the `"customNameRule"` property:
"description":"A configuration setting value",
"required":[ "name", "value" ]
And now you can validate against both the standard and custom rules by adding your `JsonValidator` to [`JSchemaReaderSettings.Validators`](https:
var readerSettings = new JSchemaReaderSettings
Validators = [new CustomNameRuleValidator()]
var schema = JSchema.Parse(schemaJson, readerSettings);
var isValid = setting.IsValid(schema, out IList<string> errors);
1. You can use the [`JSchema.ExtensionData`](https:
2. In [`CanValidate(JSchema schema)`](https:
Demo fiddle [here](https:
<sup>[1]</sup> I know this could be done using builtin functionality, it's a synthetic example since your question does not include a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example)