<PackageReference Include="NJsonSchema" Version="10.0.9" />

JsonSchemaValidator

public class JsonSchemaValidator
Class to validate a JSON schema against a given JToken.
using Newtonsoft.Json; using Newtonsoft.Json.Linq; using NJsonSchema.Validation.FormatValidators; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.RegularExpressions; namespace NJsonSchema.Validation { public class JsonSchemaValidator { private readonly IEnumerable<IFormatValidator> _formatValidators = new IFormatValidator[12] { new DateTimeFormatValidator(), new DateFormatValidator(), new EmailFormatValidator(), new GuidFormatValidator(), new HostnameFormatValidator(), new IpV4FormatValidator(), new IpV6FormatValidator(), new TimeFormatValidator(), new TimeSpanFormatValidator(), new UriFormatValidator(), new ByteFormatValidator(), new Base64FormatValidator() }; private readonly IDictionary<string, IFormatValidator> _formatValidatorsMap; private static readonly IEnumerable<JsonObjectType> JsonObjectTypes = (from JsonObjectType t in Enum.GetValues(typeof(JsonObjectType)) where t != JsonObjectType.None select t).ToList(); public JsonSchemaValidator() { _formatValidatorsMap = _formatValidators.ToDictionary((IFormatValidator v) => v.Format, (IFormatValidator v) => v); } public ICollection<ValidationError> Validate(string jsonData, JsonSchema schema) { using (StringReader reader = new StringReader(jsonData)) using (JsonTextReader reader2 = new JsonTextReader(reader) { DateParseHandling = DateParseHandling.None }) { JToken token = JToken.ReadFrom(reader2); return Validate(token, schema); } } public ICollection<ValidationError> Validate(JToken token, JsonSchema schema) { return Validate(token, schema.ActualSchema, null, token.Path); } protected virtual ICollection<ValidationError> Validate(JToken token, JsonSchema schema, string propertyName, string propertyPath) { List<ValidationError> list = new List<ValidationError>(); ValidateAnyOf(token, schema, propertyName, propertyPath, list); ValidateAllOf(token, schema, propertyName, propertyPath, list); ValidateOneOf(token, schema, propertyName, propertyPath, list); ValidateNot(token, schema, propertyName, propertyPath, list); ValidateType(token, schema, propertyName, propertyPath, list); ValidateEnum(token, schema, propertyName, propertyPath, list); ValidateProperties(token, schema, propertyName, propertyPath, list); return list; } private void ValidateType(JToken token, JsonSchema schema, string propertyName, string propertyPath, List<ValidationError> errors) { Dictionary<JsonObjectType, ICollection<ValidationError>> dictionary = GetTypes(schema).ToDictionary((Func<JsonObjectType, JsonObjectType>)((JsonObjectType t) => t), (Func<JsonObjectType, ICollection<ValidationError>>)((JsonObjectType t) => new List<ValidationError>())); if (dictionary.Count > 1) { foreach (KeyValuePair<JsonObjectType, ICollection<ValidationError>> item in dictionary) { ValidateArray(token, schema, item.Key, propertyName, propertyPath, (List<ValidationError>)item.Value); ValidateString(token, schema, item.Key, propertyName, propertyPath, (List<ValidationError>)item.Value); ValidateNumber(token, schema, item.Key, propertyName, propertyPath, (List<ValidationError>)item.Value); ValidateInteger(token, schema, item.Key, propertyName, propertyPath, (List<ValidationError>)item.Value); ValidateBoolean(token, schema, item.Key, propertyName, propertyPath, (List<ValidationError>)item.Value); ValidateNull(token, schema, item.Key, propertyName, propertyPath, (List<ValidationError>)item.Value); ValidateObject(token, schema, item.Key, propertyName, propertyPath, (List<ValidationError>)item.Value); } if (dictionary.All((KeyValuePair<JsonObjectType, ICollection<ValidationError>> t) => t.Value.Count > 0)) errors.Add(new MultiTypeValidationError(ValidationErrorKind.NoTypeValidates, propertyName, propertyPath, dictionary, token, schema)); } else { ValidateArray(token, schema, schema.Type, propertyName, propertyPath, errors); ValidateString(token, schema, schema.Type, propertyName, propertyPath, errors); ValidateNumber(token, schema, schema.Type, propertyName, propertyPath, errors); ValidateInteger(token, schema, schema.Type, propertyName, propertyPath, errors); ValidateBoolean(token, schema, schema.Type, propertyName, propertyPath, errors); ValidateNull(token, schema, schema.Type, propertyName, propertyPath, errors); ValidateObject(token, schema, schema.Type, propertyName, propertyPath, errors); } } private IEnumerable<JsonObjectType> GetTypes(JsonSchema schema) { return from t in JsonObjectTypes where schema.Type.HasFlag(t) select t; } private void ValidateAnyOf(JToken token, JsonSchema schema, string propertyName, string propertyPath, List<ValidationError> errors) { if (schema.AnyOf.Count > 0) { Dictionary<JsonSchema, ICollection<ValidationError>> dictionary = schema.AnyOf.ToDictionary((JsonSchema s) => s, (JsonSchema s) => Validate(token, s)); if (dictionary.All((KeyValuePair<JsonSchema, ICollection<ValidationError>> s) => s.Value.Count != 0)) errors.Add(new ChildSchemaValidationError(ValidationErrorKind.NotAnyOf, propertyName, propertyPath, dictionary, token, schema)); } } private void ValidateAllOf(JToken token, JsonSchema schema, string propertyName, string propertyPath, List<ValidationError> errors) { if (schema.AllOf.Count > 0) { Dictionary<JsonSchema, ICollection<ValidationError>> dictionary = schema.AllOf.ToDictionary((JsonSchema s) => s, (JsonSchema s) => Validate(token, s)); if (dictionary.Any((KeyValuePair<JsonSchema, ICollection<ValidationError>> s) => s.Value.Count != 0)) errors.Add(new ChildSchemaValidationError(ValidationErrorKind.NotAllOf, propertyName, propertyPath, dictionary, token, schema)); } } private void ValidateOneOf(JToken token, JsonSchema schema, string propertyName, string propertyPath, List<ValidationError> errors) { if (schema.OneOf.Count > 0) { Dictionary<JsonSchema, ICollection<ValidationError>> dictionary = schema.OneOf.ToDictionary((JsonSchema s) => s, (JsonSchema s) => Validate(token, s)); if (dictionary.Count((KeyValuePair<JsonSchema, ICollection<ValidationError>> s) => s.Value.Count == 0) != 1) errors.Add(new ChildSchemaValidationError(ValidationErrorKind.NotOneOf, propertyName, propertyPath, dictionary, token, schema)); } } private void ValidateNot(JToken token, JsonSchema schema, string propertyName, string propertyPath, List<ValidationError> errors) { if (schema.Not != null && Validate(token, schema.Not).Count == 0) errors.Add(new ValidationError(ValidationErrorKind.ExcludedSchemaValidates, propertyName, propertyPath, token, schema)); } private void ValidateNull(JToken token, JsonSchema schema, JsonObjectType type, string propertyName, string propertyPath, List<ValidationError> errors) { if (type.HasFlag(JsonObjectType.Null) && token != null && token.Type != JTokenType.Null) errors.Add(new ValidationError(ValidationErrorKind.NullExpected, propertyName, propertyPath, token, schema)); } private void ValidateEnum(JToken token, JsonSchema schema, string propertyName, string propertyPath, List<ValidationError> errors) { if (schema.Enumeration.Contains(null)) { JToken jToken = token; if (jToken != null && jToken.Type == JTokenType.Null) return; } if (schema.Enumeration.Count > 0 && schema.Enumeration.All((object v) => v?.ToString() != token?.ToString())) errors.Add(new ValidationError(ValidationErrorKind.NotInEnumeration, propertyName, propertyPath, token, schema)); } private void ValidateString(JToken token, JsonSchema schema, JsonObjectType type, string propertyName, string propertyPath, List<ValidationError> errors) { if (token.Type == JTokenType.String || token.Type == JTokenType.Date || token.Type == JTokenType.Guid || token.Type == JTokenType.TimeSpan || token.Type == JTokenType.Uri) { string text = (token.Type == JTokenType.Date) ? (token as JValue).ToString("yyyy-MM-ddTHH:mm:ssK") : token.Value<string>(); if (text != null) { if (!string.IsNullOrEmpty(schema.Pattern) && !Regex.IsMatch(text, schema.Pattern)) errors.Add(new ValidationError(ValidationErrorKind.PatternMismatch, propertyName, propertyPath, token, schema)); if (schema.MinLength.HasValue) { int length = text.Length; int? minLength = schema.MinLength; if ((length < minLength.GetValueOrDefault()) & minLength.HasValue) errors.Add(new ValidationError(ValidationErrorKind.StringTooShort, propertyName, propertyPath, token, schema)); } if (schema.MaxLength.HasValue) { int length2 = text.Length; int? minLength = schema.MaxLength; if ((length2 > minLength.GetValueOrDefault()) & minLength.HasValue) errors.Add(new ValidationError(ValidationErrorKind.StringTooLong, propertyName, propertyPath, token, schema)); } IFormatValidator value; if (!string.IsNullOrEmpty(schema.Format) && _formatValidatorsMap.TryGetValue(schema.Format, out value) && !value.IsValid(text, token.Type)) { ValidationError item = new ValidationError(value.ValidationErrorKind, propertyName, propertyPath, token, schema); errors.Add(item); } } } else if (type.HasFlag(JsonObjectType.String)) { errors.Add(new ValidationError(ValidationErrorKind.StringExpected, propertyName, propertyPath, token, schema)); } } private void ValidateNumber(JToken token, JsonSchema schema, JsonObjectType type, string propertyName, string propertyPath, List<ValidationError> errors) { if (type.HasFlag(JsonObjectType.Number) && token.Type != JTokenType.Float && token.Type != JTokenType.Integer) errors.Add(new ValidationError(ValidationErrorKind.NumberExpected, propertyName, propertyPath, token, schema)); if (token.Type == JTokenType.Float || token.Type == JTokenType.Integer) try { decimal num = token.Value<decimal>(); if (schema.Minimum.HasValue) { int num2; if (!schema.IsExclusiveMinimum) { decimal d = num; decimal? minimum = schema.Minimum; num2 = (((d < minimum.GetValueOrDefault()) & minimum.HasValue) ? 1 : 0); } else { decimal d2 = num; decimal? minimum = schema.Minimum; num2 = (((d2 <= minimum.GetValueOrDefault()) & minimum.HasValue) ? 1 : 0); } if (num2 != 0) errors.Add(new ValidationError(ValidationErrorKind.NumberTooSmall, propertyName, propertyPath, token, schema)); } if (schema.Maximum.HasValue) { int num3; if (!schema.IsExclusiveMaximum) { decimal d3 = num; decimal? minimum = schema.Maximum; num3 = (((d3 > minimum.GetValueOrDefault()) & minimum.HasValue) ? 1 : 0); } else { decimal d4 = num; decimal? minimum = schema.Maximum; num3 = (((d4 >= minimum.GetValueOrDefault()) & minimum.HasValue) ? 1 : 0); } if (num3 != 0) errors.Add(new ValidationError(ValidationErrorKind.NumberTooBig, propertyName, propertyPath, token, schema)); } if (schema.ExclusiveMinimum.HasValue) { decimal d5 = num; decimal? minimum = schema.ExclusiveMinimum; if ((d5 <= minimum.GetValueOrDefault()) & minimum.HasValue) errors.Add(new ValidationError(ValidationErrorKind.NumberTooSmall, propertyName, propertyPath, token, schema)); } if (schema.ExclusiveMaximum.HasValue) { decimal d6 = num; decimal? minimum = schema.ExclusiveMaximum; if ((d6 >= minimum.GetValueOrDefault()) & minimum.HasValue) errors.Add(new ValidationError(ValidationErrorKind.NumberTooBig, propertyName, propertyPath, token, schema)); } if (schema.MultipleOf.HasValue) { decimal value = num; decimal? multipleOf = schema.MultipleOf; decimal? minimum = (decimal?)value % multipleOf; if (!((minimum.GetValueOrDefault() == default(decimal)) & minimum.HasValue)) errors.Add(new ValidationError(ValidationErrorKind.NumberNotMultipleOf, propertyName, propertyPath, token, schema)); } } catch (OverflowException) { double num4 = token.Value<double>(); if (schema.Minimum.HasValue && (schema.IsExclusiveMinimum ? (num4 <= (double)schema.Minimum.Value) : (num4 < (double)schema.Minimum.Value))) errors.Add(new ValidationError(ValidationErrorKind.NumberTooSmall, propertyName, propertyPath, token, schema)); if (schema.Maximum.HasValue && (schema.IsExclusiveMaximum ? (num4 >= (double)schema.Maximum.Value) : (num4 > (double)schema.Maximum.Value))) errors.Add(new ValidationError(ValidationErrorKind.NumberTooBig, propertyName, propertyPath, token, schema)); if (schema.ExclusiveMinimum.HasValue && num4 <= (double)schema.ExclusiveMinimum.Value) errors.Add(new ValidationError(ValidationErrorKind.NumberTooSmall, propertyName, propertyPath, token, schema)); if (schema.ExclusiveMaximum.HasValue && num4 >= (double)schema.ExclusiveMaximum.Value) errors.Add(new ValidationError(ValidationErrorKind.NumberTooBig, propertyName, propertyPath, token, schema)); if (schema.MultipleOf.HasValue && num4 % (double)schema.MultipleOf.Value != 0) errors.Add(new ValidationError(ValidationErrorKind.NumberNotMultipleOf, propertyName, propertyPath, token, schema)); } } private void ValidateInteger(JToken token, JsonSchema schema, JsonObjectType type, string propertyName, string propertyPath, List<ValidationError> errors) { if (type.HasFlag(JsonObjectType.Integer) && token.Type != JTokenType.Integer) errors.Add(new ValidationError(ValidationErrorKind.IntegerExpected, propertyName, propertyPath, token, schema)); } private void ValidateBoolean(JToken token, JsonSchema schema, JsonObjectType type, string propertyName, string propertyPath, List<ValidationError> errors) { if (type.HasFlag(JsonObjectType.Boolean) && token.Type != JTokenType.Boolean) errors.Add(new ValidationError(ValidationErrorKind.BooleanExpected, propertyName, propertyPath, token, schema)); } private void ValidateObject(JToken token, JsonSchema schema, JsonObjectType type, string propertyName, string propertyPath, List<ValidationError> errors) { if (type.HasFlag(JsonObjectType.Object) && !(token is JObject)) errors.Add(new ValidationError(ValidationErrorKind.ObjectExpected, propertyName, propertyPath, token, schema)); } private void ValidateProperties(JToken token, JsonSchema schema, string propertyName, string propertyPath, List<ValidationError> errors) { JObject jObject = token as JObject; if (jObject != null || !schema.Type.HasFlag(JsonObjectType.Null)) { foreach (KeyValuePair<string, JsonSchemaProperty> property in schema.Properties) { string propertyPath2 = GetPropertyPath(propertyPath, property.Key); JProperty jProperty = jObject?.Property(property.Key); if (jProperty != null) { ICollection<ValidationError> collection = Validate(jProperty.Value, property.Value.ActualSchema, property.Key, propertyPath2); errors.AddRange(collection); } else if (property.Value.IsRequired) { errors.Add(new ValidationError(ValidationErrorKind.PropertyRequired, property.Key, propertyPath2, token, schema)); } } foreach (string requiredProperty in schema.RequiredProperties) { if (!schema.Properties.ContainsKey(requiredProperty)) { string propertyPath3 = GetPropertyPath(propertyPath, requiredProperty); if (jObject?.Property(requiredProperty) == null) errors.Add(new ValidationError(ValidationErrorKind.PropertyRequired, requiredProperty, propertyPath3, token, schema)); } } if (jObject != null) { List<JProperty> list = jObject.Properties().ToList(); ValidateMaxProperties(token, list, schema, propertyName, propertyPath, errors); ValidateMinProperties(token, list, schema, propertyName, propertyPath, errors); List<JProperty> additionalProperties = (from p in list where !schema.Properties.ContainsKey(p.Name) select p).ToList(); ValidatePatternProperties(additionalProperties, schema, errors); ValidateAdditionalProperties(token, additionalProperties, schema, propertyName, propertyPath, errors); } } } private string GetPropertyPath(string propertyPath, string propertyName) { if (string.IsNullOrEmpty(propertyPath)) return propertyName; return propertyPath + "." + propertyName; } private void ValidateMaxProperties(JToken token, IList<JProperty> properties, JsonSchema schema, string propertyName, string propertyPath, List<ValidationError> errors) { if (schema.MaxProperties > 0 && properties.Count() > schema.MaxProperties) errors.Add(new ValidationError(ValidationErrorKind.TooManyProperties, propertyName, propertyPath, token, schema)); } private void ValidateMinProperties(JToken token, IList<JProperty> properties, JsonSchema schema, string propertyName, string propertyPath, List<ValidationError> errors) { if (schema.MinProperties > 0 && properties.Count() < schema.MinProperties) errors.Add(new ValidationError(ValidationErrorKind.TooFewProperties, propertyName, propertyPath, token, schema)); } private void ValidatePatternProperties(List<JProperty> additionalProperties, JsonSchema schema, List<ValidationError> errors) { JProperty[] array = additionalProperties.ToArray(); foreach (JProperty property in array) { KeyValuePair<string, JsonSchemaProperty> keyValuePair = schema.PatternProperties.FirstOrDefault((KeyValuePair<string, JsonSchemaProperty> p) => Regex.IsMatch(property.Name, p.Key)); if (keyValuePair.Value != null) { ChildSchemaValidationError childSchemaValidationError = TryCreateChildSchemaError(property.Value, keyValuePair.Value, ValidationErrorKind.AdditionalPropertiesNotValid, property.Name, property.Path); if (childSchemaValidationError != null) errors.Add(childSchemaValidationError); additionalProperties.Remove(property); } } } private void ValidateAdditionalProperties(JToken token, List<JProperty> additionalProperties, JsonSchema schema, string propertyName, string propertyPath, List<ValidationError> errors) { List<JProperty>.Enumerator enumerator; if (schema.AdditionalPropertiesSchema != null) { enumerator = additionalProperties.GetEnumerator(); try { while (enumerator.MoveNext()) { JProperty current = enumerator.Current; ChildSchemaValidationError childSchemaValidationError = TryCreateChildSchemaError(current.Value, schema.AdditionalPropertiesSchema, ValidationErrorKind.AdditionalPropertiesNotValid, current.Name, current.Path); if (childSchemaValidationError != null) errors.Add(childSchemaValidationError); } } finally { ((IDisposable)enumerator).Dispose(); } } else if (!schema.AllowAdditionalProperties && additionalProperties.Any()) { enumerator = additionalProperties.GetEnumerator(); try { while (enumerator.MoveNext()) { JProperty current2 = enumerator.Current; string propertyPath2 = (!string.IsNullOrEmpty(propertyPath)) ? (propertyPath + "." + current2.Name) : current2.Name; errors.Add(new ValidationError(ValidationErrorKind.NoAdditionalPropertiesAllowed, current2.Name, propertyPath2, current2, schema)); } } finally { ((IDisposable)enumerator).Dispose(); } } } private void ValidateArray(JToken token, JsonSchema schema, JsonObjectType type, string propertyName, string propertyPath, List<ValidationError> errors) { JArray jArray = token as JArray; if (jArray != null) { if (schema.MinItems > 0 && jArray.Count < schema.MinItems) errors.Add(new ValidationError(ValidationErrorKind.TooFewItems, propertyName, propertyPath, token, schema)); if (schema.MaxItems > 0 && jArray.Count > schema.MaxItems) errors.Add(new ValidationError(ValidationErrorKind.TooManyItems, propertyName, propertyPath, token, schema)); if (schema.UniqueItems && jArray.Count != (from a in jArray select a.ToString()).Distinct().Count()) errors.Add(new ValidationError(ValidationErrorKind.ItemsNotUnique, propertyName, propertyPath, token, schema)); for (int i = 0; i < jArray.Count; i++) { JToken jToken = jArray[i]; string text = $"""{i}"""; string path = (!string.IsNullOrEmpty(propertyPath)) ? (propertyPath + text) : text; if (schema.Item != null) { ChildSchemaValidationError childSchemaValidationError = TryCreateChildSchemaError(jToken, schema.Item, ValidationErrorKind.ArrayItemNotValid, text, path); if (childSchemaValidationError != null) errors.Add(childSchemaValidationError); } ValidateAdditionalItems(jToken, schema, i, propertyPath, errors); } } else if (type.HasFlag(JsonObjectType.Array)) { errors.Add(new ValidationError(ValidationErrorKind.ArrayExpected, propertyName, propertyPath, token, schema)); } } private void ValidateAdditionalItems(JToken item, JsonSchema schema, int index, string propertyPath, List<ValidationError> errors) { if (schema.Items.Count > 0) { string text = $"""{index}"""; if (schema.Items.Count > index) { ChildSchemaValidationError childSchemaValidationError = TryCreateChildSchemaError(item, schema.Items.ElementAt(index), ValidationErrorKind.ArrayItemNotValid, text, propertyPath + text); if (childSchemaValidationError != null) errors.Add(childSchemaValidationError); } else if (schema.AdditionalItemsSchema != null) { ChildSchemaValidationError childSchemaValidationError2 = TryCreateChildSchemaError(item, schema.AdditionalItemsSchema, ValidationErrorKind.AdditionalItemNotValid, text, propertyPath + text); if (childSchemaValidationError2 != null) errors.Add(childSchemaValidationError2); } else if (!schema.AllowAdditionalItems) { errors.Add(new ValidationError(ValidationErrorKind.TooManyItemsInTuple, text, propertyPath + text, item, schema)); } } } private ChildSchemaValidationError TryCreateChildSchemaError(JToken token, JsonSchema schema, ValidationErrorKind errorKind, string property, string path) { ICollection<ValidationError> collection = Validate(token, schema.ActualSchema, null, path); if (collection.Count == 0) return null; Dictionary<JsonSchema, ICollection<ValidationError>> dictionary = new Dictionary<JsonSchema, ICollection<ValidationError>>(); dictionary.Add(schema, collection); return new ChildSchemaValidationError(errorKind, property, path, dictionary, token, schema); } } }