JsonSchemaGenerator
Generates a JsonSchema from a specified Type.
JSON Schema validation has been moved to its own package. See https://www.newtonsoft.com/jsonschema for more details.using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
using Newtonsoft.Json.Utilities;
using System;
using System.Collections.Generic;
using System.ComponentModel;
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.")]
    public class JsonSchemaGenerator
    {
        private class TypeSchema
        {
            public Type Type { get; }
            public JsonSchema Schema { get; }
            public TypeSchema(Type type, JsonSchema schema)
            {
                ValidationUtils.ArgumentNotNull(type, "type");
                ValidationUtils.ArgumentNotNull(schema, "schema");
                Type = type;
                Schema = schema;
            }
        }
        private IContractResolver _contractResolver;
        private JsonSchemaResolver _resolver;
        private readonly IList<TypeSchema> _stack = new List<TypeSchema>();
        private JsonSchema _currentSchema;
        public UndefinedSchemaIdHandling UndefinedSchemaIdHandling { get; set; }
        public IContractResolver ContractResolver {
            get {
                if (_contractResolver == null)
                    return DefaultContractResolver.Instance;
                return _contractResolver;
            }
            set {
                _contractResolver = value;
            }
        }
        private JsonSchema CurrentSchema => _currentSchema;
        private void Push(TypeSchema typeSchema)
        {
            _currentSchema = typeSchema.Schema;
            _stack.Add(typeSchema);
            _resolver.LoadedSchemas.Add(typeSchema.Schema);
        }
        private TypeSchema Pop()
        {
            TypeSchema result = _stack[_stack.Count - 1];
            _stack.RemoveAt(_stack.Count - 1);
            TypeSchema typeSchema = _stack.LastOrDefault();
            if (typeSchema != null) {
                _currentSchema = typeSchema.Schema;
                return result;
            }
            _currentSchema = null;
            return result;
        }
        public JsonSchema Generate(Type type)
        {
            return Generate(type, new JsonSchemaResolver(), false);
        }
        public JsonSchema Generate(Type type, JsonSchemaResolver resolver)
        {
            return Generate(type, resolver, false);
        }
        public JsonSchema Generate(Type type, bool rootSchemaNullable)
        {
            return Generate(type, new JsonSchemaResolver(), rootSchemaNullable);
        }
        public JsonSchema Generate(Type type, JsonSchemaResolver resolver, bool rootSchemaNullable)
        {
            ValidationUtils.ArgumentNotNull(type, "type");
            ValidationUtils.ArgumentNotNull(resolver, "resolver");
            _resolver = resolver;
            return GenerateInternal(type, (!rootSchemaNullable) ? Required.Always : Required.Default, false);
        }
        private string GetTitle(Type type)
        {
            JsonContainerAttribute cachedAttribute = JsonTypeReflector.GetCachedAttribute<JsonContainerAttribute>(type);
            if (!StringUtils.IsNullOrEmpty(cachedAttribute?.Title))
                return cachedAttribute.Title;
            return null;
        }
        private string GetDescription(Type type)
        {
            JsonContainerAttribute cachedAttribute = JsonTypeReflector.GetCachedAttribute<JsonContainerAttribute>(type);
            if (!StringUtils.IsNullOrEmpty(cachedAttribute?.Description))
                return cachedAttribute.Description;
            return ReflectionUtils.GetAttribute<DescriptionAttribute>(type)?.Description;
        }
        private string GetTypeId(Type type, bool explicitOnly)
        {
            JsonContainerAttribute cachedAttribute = JsonTypeReflector.GetCachedAttribute<JsonContainerAttribute>(type);
            if (!StringUtils.IsNullOrEmpty(cachedAttribute?.Id))
                return cachedAttribute.Id;
            if (!explicitOnly) {
                switch (UndefinedSchemaIdHandling) {
                case UndefinedSchemaIdHandling.UseTypeName:
                    return type.FullName;
                case UndefinedSchemaIdHandling.UseAssemblyQualifiedName:
                    return type.AssemblyQualifiedName;
                default:
                    return null;
                }
            }
            return null;
        }
        private JsonSchema GenerateInternal(Type type, Required valueRequired, bool required)
        {
            ValidationUtils.ArgumentNotNull(type, "type");
            string typeId = GetTypeId(type, false);
            string typeId2 = GetTypeId(type, true);
            if (!StringUtils.IsNullOrEmpty(typeId)) {
                JsonSchema schema = _resolver.GetSchema(typeId);
                if (schema != null) {
                    if (valueRequired != Required.Always && !HasFlag(schema.Type, JsonSchemaType.Null))
                        schema.Type |= JsonSchemaType.Null;
                    if (required) {
                        bool? required2 = schema.Required;
                        bool flag = true;
                        if (!((required2.GetValueOrDefault() == flag) & required2.HasValue))
                            schema.Required = true;
                    }
                    return schema;
                }
            }
            if (_stack.Any((TypeSchema tc) => tc.Type == type))
                throw new JsonException("Unresolved circular reference for type '{0}'. Explicitly define an Id for the type using a JsonObject/JsonArray attribute or automatically generate a type Id using the UndefinedSchemaIdHandling property.".FormatWith(CultureInfo.InvariantCulture, type));
            JsonContract jsonContract = ContractResolver.ResolveContract(type);
            JsonConverter obj = jsonContract.Converter ?? jsonContract.InternalConverter;
            Push(new TypeSchema(type, new JsonSchema()));
            if (typeId2 != null)
                CurrentSchema.Id = typeId2;
            if (required)
                CurrentSchema.Required = true;
            CurrentSchema.Title = GetTitle(type);
            CurrentSchema.Description = GetDescription(type);
            if (obj == null) {
                switch (jsonContract.ContractType) {
                case JsonContractType.Object:
                    CurrentSchema.Type = AddNullType(JsonSchemaType.Object, valueRequired);
                    CurrentSchema.Id = GetTypeId(type, false);
                    GenerateObjectSchema(type, (JsonObjectContract)jsonContract);
                    break;
                case JsonContractType.Array: {
                    CurrentSchema.Type = AddNullType(JsonSchemaType.Array, valueRequired);
                    CurrentSchema.Id = GetTypeId(type, false);
                    bool flag2 = JsonTypeReflector.GetCachedAttribute<JsonArrayAttribute>(type)?.AllowNullItems ?? true;
                    Type collectionItemType = ReflectionUtils.GetCollectionItemType(type);
                    if (collectionItemType != (Type)null) {
                        CurrentSchema.Items = new List<JsonSchema>();
                        CurrentSchema.Items.Add(GenerateInternal(collectionItemType, (!flag2) ? Required.Always : Required.Default, false));
                    }
                    break;
                }
                case JsonContractType.Primitive: {
                    CurrentSchema.Type = GetJsonSchemaType(type, valueRequired);
                    JsonSchemaType? type2 = CurrentSchema.Type;
                    JsonSchemaType jsonSchemaType = JsonSchemaType.Integer;
                    if (((type2.GetValueOrDefault() == jsonSchemaType) & type2.HasValue) && type.IsEnum() && !type.IsDefined(typeof(FlagsAttribute), true)) {
                        CurrentSchema.Enum = new List<JToken>();
                        EnumInfo enumValuesAndNames = EnumUtils.GetEnumValuesAndNames(type);
                        for (int i = 0; i < enumValuesAndNames.Names.Length; i++) {
                            ulong value = enumValuesAndNames.Values[i];
                            JToken item = JToken.FromObject(Enum.ToObject(type, value));
                            CurrentSchema.Enum.Add(item);
                        }
                    }
                    break;
                }
                case JsonContractType.String: {
                    JsonSchemaType value2 = (!ReflectionUtils.IsNullable(jsonContract.UnderlyingType)) ? JsonSchemaType.String : AddNullType(JsonSchemaType.String, valueRequired);
                    CurrentSchema.Type = value2;
                    break;
                }
                case JsonContractType.Dictionary:
                    CurrentSchema.Type = AddNullType(JsonSchemaType.Object, valueRequired);
                    ReflectionUtils.GetDictionaryKeyValueTypes(type, out Type keyType, out Type valueType);
                    if (keyType != (Type)null && ContractResolver.ResolveContract(keyType).ContractType == JsonContractType.Primitive)
                        CurrentSchema.AdditionalProperties = GenerateInternal(valueType, Required.Default, false);
                    break;
                case JsonContractType.Serializable:
                    CurrentSchema.Type = AddNullType(JsonSchemaType.Object, valueRequired);
                    CurrentSchema.Id = GetTypeId(type, false);
                    GenerateISerializableContract(type, (JsonISerializableContract)jsonContract);
                    break;
                case JsonContractType.Dynamic:
                case JsonContractType.Linq:
                    CurrentSchema.Type = JsonSchemaType.Any;
                    break;
                default:
                    throw new JsonException("Unexpected contract type: {0}".FormatWith(CultureInfo.InvariantCulture, jsonContract));
                }
            } else
                CurrentSchema.Type = JsonSchemaType.Any;
            return Pop().Schema;
        }
        private JsonSchemaType AddNullType(JsonSchemaType type, Required valueRequired)
        {
            if (valueRequired != Required.Always)
                return type | JsonSchemaType.Null;
            return type;
        }
        private bool HasFlag(DefaultValueHandling value, DefaultValueHandling flag)
        {
            return (value & flag) == flag;
        }
        private void GenerateObjectSchema(Type type, JsonObjectContract contract)
        {
            CurrentSchema.Properties = new Dictionary<string, JsonSchema>();
            foreach (JsonProperty property in contract.Properties) {
                if (!property.Ignored) {
                    NullValueHandling? nullValueHandling = property.NullValueHandling;
                    NullValueHandling nullValueHandling2 = NullValueHandling.Ignore;
                    bool flag = ((nullValueHandling.GetValueOrDefault() == nullValueHandling2) & nullValueHandling.HasValue) || HasFlag(property.DefaultValueHandling.GetValueOrDefault(), DefaultValueHandling.Ignore) || property.ShouldSerialize != null || property.GetIsSpecified != null;
                    JsonSchema jsonSchema = GenerateInternal(property.PropertyType, property.Required, !flag);
                    if (property.DefaultValue != null)
                        jsonSchema.Default = JToken.FromObject(property.DefaultValue);
                    CurrentSchema.Properties.Add(property.PropertyName, jsonSchema);
                }
            }
            if (type.IsSealed())
                CurrentSchema.AllowAdditionalProperties = false;
        }
        private void GenerateISerializableContract(Type type, JsonISerializableContract contract)
        {
            CurrentSchema.AllowAdditionalProperties = true;
        }
        internal static bool HasFlag(JsonSchemaType? value, JsonSchemaType flag)
        {
            if (!value.HasValue)
                return true;
            JsonSchemaType? nullable = value & flag;
            if ((nullable.GetValueOrDefault() == flag) & nullable.HasValue)
                return true;
            if (flag == JsonSchemaType.Integer) {
                nullable = (JsonSchemaType?)((int?)value & 2);
                JsonSchemaType jsonSchemaType = JsonSchemaType.Float;
                if ((nullable.GetValueOrDefault() == jsonSchemaType) & nullable.HasValue)
                    return true;
            }
            return false;
        }
        private JsonSchemaType GetJsonSchemaType(Type type, Required valueRequired)
        {
            JsonSchemaType jsonSchemaType = JsonSchemaType.None;
            if (valueRequired != Required.Always && ReflectionUtils.IsNullable(type)) {
                jsonSchemaType = JsonSchemaType.Null;
                if (ReflectionUtils.IsNullableType(type))
                    type = Nullable.GetUnderlyingType(type);
            }
            PrimitiveTypeCode typeCode = ConvertUtils.GetTypeCode(type);
            switch (typeCode) {
            case PrimitiveTypeCode.Empty:
            case PrimitiveTypeCode.Object:
                return jsonSchemaType | JsonSchemaType.String;
            case PrimitiveTypeCode.DBNull:
                return jsonSchemaType | JsonSchemaType.Null;
            case PrimitiveTypeCode.Boolean:
                return jsonSchemaType | JsonSchemaType.Boolean;
            case PrimitiveTypeCode.Char:
                return jsonSchemaType | JsonSchemaType.String;
            case PrimitiveTypeCode.SByte:
            case PrimitiveTypeCode.Int16:
            case PrimitiveTypeCode.UInt16:
            case PrimitiveTypeCode.Int32:
            case PrimitiveTypeCode.Byte:
            case PrimitiveTypeCode.UInt32:
            case PrimitiveTypeCode.Int64:
            case PrimitiveTypeCode.UInt64:
            case PrimitiveTypeCode.BigInteger:
                return jsonSchemaType | JsonSchemaType.Integer;
            case PrimitiveTypeCode.Single:
            case PrimitiveTypeCode.Double:
            case PrimitiveTypeCode.Decimal:
                return jsonSchemaType | JsonSchemaType.Float;
            case PrimitiveTypeCode.DateTime:
            case PrimitiveTypeCode.DateTimeOffset:
                return jsonSchemaType | JsonSchemaType.String;
            case PrimitiveTypeCode.Guid:
            case PrimitiveTypeCode.TimeSpan:
            case PrimitiveTypeCode.Uri:
            case PrimitiveTypeCode.String:
            case PrimitiveTypeCode.Bytes:
                return jsonSchemaType | JsonSchemaType.String;
            default:
                throw new JsonException("Unexpected type code '{0}' for type '{1}'.".FormatWith(CultureInfo.InvariantCulture, typeCode, type));
            }
        }
    }
}