<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />

JsonSchemaBuilder

using Newtonsoft.Json.Linq; using Newtonsoft.Json.Utilities; using System; using System.Collections.Generic; using System.Globalization; using System.Linq; namespace Newtonsoft.Json.Schema { [Obsolete("JSON Schema validation has been moved to its own package. See https://www.newtonsoft.com/jsonschema for more details.")] internal class JsonSchemaBuilder { private readonly IList<JsonSchema> _stack; private readonly JsonSchemaResolver _resolver; private readonly IDictionary<string, JsonSchema> _documentSchemas; private JsonSchema _currentSchema; private JObject _rootSchema; private JsonSchema CurrentSchema => _currentSchema; public JsonSchemaBuilder(JsonSchemaResolver resolver) { _stack = new List<JsonSchema>(); _documentSchemas = new Dictionary<string, JsonSchema>(); _resolver = resolver; } private void Push(JsonSchema value) { _currentSchema = value; _stack.Add(value); _resolver.LoadedSchemas.Add(value); _documentSchemas.Add(value.Location, value); } private JsonSchema Pop() { JsonSchema currentSchema = _currentSchema; _stack.RemoveAt(_stack.Count - 1); _currentSchema = _stack.LastOrDefault(); return currentSchema; } internal JsonSchema Read(JsonReader reader) { JToken jToken = JToken.ReadFrom(reader); _rootSchema = (jToken as JObject); JsonSchema jsonSchema = BuildSchema(jToken); ResolveReferences(jsonSchema); return jsonSchema; } private string UnescapeReference(string reference) { return StringUtils.Replace(StringUtils.Replace(Uri.UnescapeDataString(reference), "~1", "/"), "~0", "~"); } private JsonSchema ResolveReferences(JsonSchema schema) { if (schema.DeferredReference != null) { string text = schema.DeferredReference; bool flag = text.StartsWith("#", StringComparison.Ordinal); if (flag) text = UnescapeReference(text); JsonSchema jsonSchema = _resolver.GetSchema(text); if (jsonSchema == null) { if (flag) { string[] array = schema.DeferredReference.TrimStart(new char[1] { '#' }).Split(new char[1] { '/' }, StringSplitOptions.RemoveEmptyEntries); JToken jToken = _rootSchema; string[] array2 = array; foreach (string reference in array2) { string text2 = UnescapeReference(reference); if (jToken.Type == JTokenType.Object) jToken = jToken[text2]; else if (jToken.Type == JTokenType.Array || jToken.Type == JTokenType.Constructor) { int result; jToken = ((!int.TryParse(text2, out result) || result < 0 || result >= jToken.Count()) ? null : jToken[result]); } if (jToken == null) break; } if (jToken != null) jsonSchema = BuildSchema(jToken); } if (jsonSchema == null) throw new JsonException("Could not resolve schema reference '{0}'.".FormatWith(CultureInfo.InvariantCulture, schema.DeferredReference)); } schema = jsonSchema; } if (schema.ReferencesResolved) return schema; schema.ReferencesResolved = true; if (schema.Extends != null) { for (int j = 0; j < schema.Extends.Count; j++) { schema.Extends[j] = ResolveReferences(schema.Extends[j]); } } if (schema.Items != null) { for (int k = 0; k < schema.Items.Count; k++) { schema.Items[k] = ResolveReferences(schema.Items[k]); } } if (schema.AdditionalItems != null) schema.AdditionalItems = ResolveReferences(schema.AdditionalItems); List<KeyValuePair<string, JsonSchema>>.Enumerator enumerator; if (schema.PatternProperties != null) { enumerator = schema.PatternProperties.ToList().GetEnumerator(); try { while (enumerator.MoveNext()) { KeyValuePair<string, JsonSchema> current = enumerator.Current; schema.PatternProperties[current.Key] = ResolveReferences(current.Value); } } finally { ((IDisposable)enumerator).Dispose(); } } if (schema.Properties != null) { enumerator = schema.Properties.ToList().GetEnumerator(); try { while (enumerator.MoveNext()) { KeyValuePair<string, JsonSchema> current2 = enumerator.Current; schema.Properties[current2.Key] = ResolveReferences(current2.Value); } } finally { ((IDisposable)enumerator).Dispose(); } } if (schema.AdditionalProperties != null) schema.AdditionalProperties = ResolveReferences(schema.AdditionalProperties); return schema; } private JsonSchema BuildSchema(JToken token) { JObject jObject = token as JObject; if (jObject == null) throw JsonException.Create(token, token.Path, "Expected object while parsing schema object, got {0}.".FormatWith(CultureInfo.InvariantCulture, token.Type)); if (jObject.TryGetValue("$ref", out JToken value)) return new JsonSchema { DeferredReference = (string)value }; string path = token.Path; path = StringUtils.Replace(path, ".", "/"); path = StringUtils.Replace(path, "[", "/"); path = StringUtils.Replace(path, "]", string.Empty); if (!StringUtils.IsNullOrEmpty(path)) path = "/" + path; path = "#" + path; if (_documentSchemas.TryGetValue(path, out JsonSchema value2)) return value2; Push(new JsonSchema { Location = path }); ProcessSchemaProperties(jObject); return Pop(); } private void ProcessSchemaProperties(JObject schemaObject) { foreach (KeyValuePair<string, JToken> item in schemaObject) { string key = item.Key; if (key != null) { switch (key.Length) { case 4: switch (key[0]) { case 't': if (key == "type") CurrentSchema.Type = ProcessType(item.Value); break; case 'e': if (key == "enum") ProcessEnum(item.Value); break; } break; case 5: switch (key[0]) { case 't': if (key == "title") CurrentSchema.Title = (string)item.Value; break; case 'i': if (key == "items") ProcessItems(item.Value); break; } break; case 11: switch (key[1]) { case 'e': if (key == "description") CurrentSchema.Description = (string)item.Value; break; case 'i': if (key == "divisibleBy") CurrentSchema.DivisibleBy = (double)item.Value; break; case 'n': if (key == "uniqueItems") CurrentSchema.UniqueItems = (bool)item.Value; break; } break; case 8: switch (key[2]) { case 'q': if (!(key == "required")) { if (key == "requires") CurrentSchema.Requires = (string)item.Value; } else CurrentSchema.Required = (bool)item.Value; break; case 'x': if (key == "maxItems") CurrentSchema.MaximumItems = (int)item.Value; break; case 'n': if (key == "minItems") CurrentSchema.MinimumItems = (int)item.Value; break; case 's': if (key == "disallow") CurrentSchema.Disallow = ProcessType(item.Value); break; case 'a': if (key == "readonly") CurrentSchema.ReadOnly = (bool)item.Value; break; } break; case 7: switch (key[0]) { case 'm': if (!(key == "minimum")) { if (key == "maximum") CurrentSchema.Maximum = (double)item.Value; } else CurrentSchema.Minimum = (double)item.Value; break; case 'd': if (key == "default") CurrentSchema.Default = item.Value.DeepClone(); break; case 'p': if (key == "pattern") CurrentSchema.Pattern = (string)item.Value; break; case 'e': if (key == "extends") ProcessExtends(item.Value); break; } break; case 16: switch (key[10]) { case 'i': if (key == "exclusiveMinimum") CurrentSchema.ExclusiveMinimum = (bool)item.Value; break; case 'a': if (key == "exclusiveMaximum") CurrentSchema.ExclusiveMaximum = (bool)item.Value; break; } break; case 9: switch (key[1]) { case 'a': if (key == "maxLength") CurrentSchema.MaximumLength = (int)item.Value; break; case 'i': if (key == "minLength") CurrentSchema.MinimumLength = (int)item.Value; break; } break; case 6: switch (key[0]) { case 'h': if (key == "hidden") CurrentSchema.Hidden = (bool)item.Value; break; case 'f': if (key == "format") CurrentSchema.Format = (string)item.Value; break; } break; case 2: if (key == "id") CurrentSchema.Id = (string)item.Value; break; case 10: if (key == "properties") CurrentSchema.Properties = ProcessProperties(item.Value); break; case 20: if (key == "additionalProperties") ProcessAdditionalProperties(item.Value); break; case 15: if (key == "additionalItems") ProcessAdditionalItems(item.Value); break; case 17: if (key == "patternProperties") CurrentSchema.PatternProperties = ProcessProperties(item.Value); break; } } } } private void ProcessExtends(JToken token) { IList<JsonSchema> list = new List<JsonSchema>(); if (token.Type == JTokenType.Array) { foreach (JToken item in (IEnumerable<JToken>)token) { list.Add(BuildSchema(item)); } } else { JsonSchema jsonSchema = BuildSchema(token); if (jsonSchema != null) list.Add(jsonSchema); } if (list.Count > 0) CurrentSchema.Extends = list; } private void ProcessEnum(JToken token) { if (token.Type != JTokenType.Array) throw JsonException.Create(token, token.Path, "Expected Array token while parsing enum values, got {0}.".FormatWith(CultureInfo.InvariantCulture, token.Type)); CurrentSchema.Enum = new List<JToken>(); foreach (JToken item in (IEnumerable<JToken>)token) { CurrentSchema.Enum.Add(item.DeepClone()); } } private void ProcessAdditionalProperties(JToken token) { if (token.Type == JTokenType.Boolean) CurrentSchema.AllowAdditionalProperties = (bool)token; else CurrentSchema.AdditionalProperties = BuildSchema(token); } private void ProcessAdditionalItems(JToken token) { if (token.Type == JTokenType.Boolean) CurrentSchema.AllowAdditionalItems = (bool)token; else CurrentSchema.AdditionalItems = BuildSchema(token); } private IDictionary<string, JsonSchema> ProcessProperties(JToken token) { IDictionary<string, JsonSchema> dictionary = new Dictionary<string, JsonSchema>(); if (token.Type != JTokenType.Object) throw JsonException.Create(token, token.Path, "Expected Object token while parsing schema properties, got {0}.".FormatWith(CultureInfo.InvariantCulture, token.Type)); foreach (JProperty item in (IEnumerable<JToken>)token) { if (dictionary.ContainsKey(item.Name)) throw new JsonException("Property {0} has already been defined in schema.".FormatWith(CultureInfo.InvariantCulture, item.Name)); dictionary.Add(item.Name, BuildSchema(item.Value)); } return dictionary; } private void ProcessItems(JToken token) { CurrentSchema.Items = new List<JsonSchema>(); switch (token.Type) { case JTokenType.Object: CurrentSchema.Items.Add(BuildSchema(token)); CurrentSchema.PositionalItemsValidation = false; break; case JTokenType.Array: CurrentSchema.PositionalItemsValidation = true; foreach (JToken item in (IEnumerable<JToken>)token) { CurrentSchema.Items.Add(BuildSchema(item)); } break; default: throw JsonException.Create(token, token.Path, "Expected array or JSON schema object, got {0}.".FormatWith(CultureInfo.InvariantCulture, token.Type)); } } private JsonSchemaType? ProcessType(JToken token) { switch (token.Type) { case JTokenType.Array: { JsonSchemaType? nullable = JsonSchemaType.None; { foreach (JToken item in (IEnumerable<JToken>)token) { if (item.Type != JTokenType.String) throw JsonException.Create(item, item.Path, "Expected JSON schema type string token, got {0}.".FormatWith(CultureInfo.InvariantCulture, token.Type)); nullable |= MapType((string)item); } return nullable; } } case JTokenType.String: return MapType((string)token); default: throw JsonException.Create(token, token.Path, "Expected array or JSON schema type string token, got {0}.".FormatWith(CultureInfo.InvariantCulture, token.Type)); } } internal static JsonSchemaType MapType(string type) { if (!JsonSchemaConstants.JsonSchemaTypeMapping.TryGetValue(type, out JsonSchemaType value)) throw new JsonException("Invalid JSON schema type: {0}".FormatWith(CultureInfo.InvariantCulture, type)); return value; } internal static string MapType(JsonSchemaType type) { return JsonSchemaConstants.JsonSchemaTypeMapping.Single((KeyValuePair<string, JsonSchemaType> kv) => kv.Value == type).Key; } } }