JsonSchemaGenerator
Generates a JsonSchema4 object for a given type.
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NJsonSchema.Annotations;
using NJsonSchema.Converters;
using NJsonSchema.Generation.TypeMappers;
using NJsonSchema.Infrastructure;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace NJsonSchema.Generation
{
public class JsonSchemaGenerator
{
private static readonly Dictionary<string, string> DataTypeFormats = new Dictionary<string, string> {
{
"DateTime",
"date-time"
},
{
"Date",
"date"
},
{
"Time",
"time"
},
{
"EmailAddress",
"email"
},
{
"PhoneNumber",
"phone"
},
{
"Url",
"uri"
}
};
public JsonSchemaGeneratorSettings Settings { get; }
public JsonSchemaGenerator(JsonSchemaGeneratorSettings settings)
{
Settings = settings;
}
public JsonSchema4 Generate(Type type)
{
JsonSchemaResolver schemaResolver = new JsonSchemaResolver(Settings);
return Generate<JsonSchema4>(type, null, schemaResolver);
}
public JsonSchema4 Generate(Type type, JsonSchemaResolver schemaResolver)
{
return Generate<JsonSchema4>(type, null, schemaResolver);
}
public virtual TSchemaType Generate<TSchemaType>(Type type, IEnumerable<Attribute> parentAttributes, JsonSchemaResolver schemaResolver) where TSchemaType : JsonSchema4, new
{
TSchemaType val = HandleSpecialTypes<TSchemaType>(type, schemaResolver);
if (val != null)
return val;
val = new TSchemaType();
if (!schemaResolver.Schemas.Any() && ReflectionExtensions.GetTypeInfo(type).IsClass)
val.Title = Settings.SchemaNameGenerator.Generate(type);
ApplyExtensionDataAttributes(val, type, parentAttributes);
JsonObjectTypeDescription jsonObjectTypeDescription = JsonObjectTypeDescription.FromType(type, parentAttributes, Settings.DefaultEnumHandling);
if (jsonObjectTypeDescription.Type.HasFlag(JsonObjectType.Object)) {
if (jsonObjectTypeDescription.IsDictionary) {
jsonObjectTypeDescription.ApplyType(val);
GenerateDictionary(type, val, schemaResolver);
} else {
if (schemaResolver.HasSchema(type, false)) {
val.SchemaReference = schemaResolver.GetSchema(type, false);
return val;
}
if (!(val.GetType() == typeof(JsonSchema4))) {
val.SchemaReference = Generate<JsonSchema4>(type, parentAttributes, schemaResolver);
return val;
}
jsonObjectTypeDescription.ApplyType(val);
val.Description = GetDescription(ReflectionExtensions.GetTypeInfo(type), ReflectionExtensions.GetCustomAttributes(ReflectionExtensions.GetTypeInfo(type), true));
GenerateObject(type, val, schemaResolver);
}
} else if (ReflectionExtensions.GetTypeInfo(type).IsEnum) {
bool isIntegerEnumeration = jsonObjectTypeDescription.Type == JsonObjectType.Integer;
if (schemaResolver.HasSchema(type, isIntegerEnumeration)) {
val.SchemaReference = schemaResolver.GetSchema(type, isIntegerEnumeration);
return val;
}
if (!(val.GetType() == typeof(JsonSchema4))) {
val.SchemaReference = Generate<JsonSchema4>(type, parentAttributes, schemaResolver);
return val;
}
LoadEnumerations(type, val, jsonObjectTypeDescription);
jsonObjectTypeDescription.ApplyType(val);
val.Description = type.GetXmlSummary();
schemaResolver.AddSchema(type, isIntegerEnumeration, val);
} else if (jsonObjectTypeDescription.Type.HasFlag(JsonObjectType.Array)) {
jsonObjectTypeDescription.ApplyType(val);
Type enumerableItemType = type.GetEnumerableItemType();
if (enumerableItemType == (Type)null) {
if (ReflectionExtensions.GetTypeInfo(type).GetCustomAttribute<JsonSchemaAttribute>()?.ArrayItem != (Type)null)
val.Item = GenerateWithReference(schemaResolver, enumerableItemType);
else
val.Item = JsonSchema4.CreateAnySchema();
} else
val.Item = GenerateWithReference(schemaResolver, enumerableItemType);
} else {
jsonObjectTypeDescription.ApplyType(val);
}
return val;
}
private JsonSchema4 GenerateWithReference(JsonSchemaResolver schemaResolver, Type itemType)
{
if (RequiresSchemaReference(itemType, null))
return new JsonSchema4 {
SchemaReference = Generate(itemType, schemaResolver)
};
return Generate(itemType, schemaResolver);
}
private void ApplyExtensionDataAttributes<TSchemaType>(TSchemaType schema, Type type, IEnumerable<Attribute> parentAttributes) where TSchemaType : JsonSchema4, new
{
if (parentAttributes == null) {
JsonSchemaExtensionDataAttribute[] source = ReflectionExtensions.GetTypeInfo(type).GetCustomAttributes<JsonSchemaExtensionDataAttribute>(true).ToArray();
if (source.Any())
schema.ExtensionData = source.ToDictionary((JsonSchemaExtensionDataAttribute a) => a.Property, (JsonSchemaExtensionDataAttribute a) => a.Value);
} else {
JsonSchemaExtensionDataAttribute[] source2 = parentAttributes.OfType<JsonSchemaExtensionDataAttribute>().ToArray();
if (source2.Any())
schema.ExtensionData = source2.ToDictionary((JsonSchemaExtensionDataAttribute a) => a.Property, (JsonSchemaExtensionDataAttribute a) => a.Value);
}
}
private TSchemaType HandleSpecialTypes<TSchemaType>(Type type, JsonSchemaResolver schemaResolver) where TSchemaType : JsonSchema4, new
{
ITypeMapper typeMapper = Settings.TypeMappers.FirstOrDefault((ITypeMapper m) => m.MappedType == type);
if (typeMapper != null) {
TSchemaType schema = typeMapper.GetSchema<TSchemaType>(this, schemaResolver);
if (schema != null)
return schema;
}
if (type == typeof(JObject) || type == typeof(JToken) || type == typeof(object))
return JsonSchema4.CreateAnySchema<TSchemaType>();
return null;
}
private void GenerateDictionary<TSchemaType>(Type type, TSchemaType schema, JsonSchemaResolver schemaResolver) where TSchemaType : JsonSchema4, new
{
Type[] genericTypeArguments = type.GetGenericTypeArguments();
Type type2 = (genericTypeArguments.Length == 2) ? genericTypeArguments[1] : typeof(object);
if (type2 == typeof(object))
schema.AdditionalPropertiesSchema = JsonSchema4.CreateAnySchema();
else if (RequiresSchemaReference(type2, null)) {
schema.AdditionalPropertiesSchema = new JsonSchema4 {
SchemaReference = Generate(type2, schemaResolver)
};
} else {
schema.AdditionalPropertiesSchema = Generate(type2, schemaResolver);
}
schema.AllowAdditionalProperties = true;
}
protected virtual void GenerateObject<TSchemaType>(Type type, TSchemaType schema, JsonSchemaResolver schemaResolver) where TSchemaType : JsonSchema4, new
{
schemaResolver.AddSchema(type, false, schema);
schema.AllowAdditionalProperties = false;
GeneratePropertiesAndInheritance(type, schema, schemaResolver);
if (Settings.GenerateKnownTypes)
GenerateKnownTypes(type, schemaResolver);
}
private void GeneratePropertiesAndInheritance(Type type, JsonSchema4 schema, JsonSchemaResolver schemaResolver)
{
string[] properties = GetTypeProperties(type);
FieldInfo[] fields = ReflectionExtensions.GetTypeInfo(type).GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public);
foreach (PropertyInfo item in ReflectionExtensions.GetTypeInfo(type).GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public).Where(delegate(PropertyInfo p) {
if (!(p.GetGetMethod()?.IsPublic ?? false))
return p.GetSetMethod()?.IsPublic ?? false;
return true;
})
.Where(delegate(PropertyInfo p) {
if (properties != null)
return properties.Contains(p.Name);
return true;
})) {
LoadPropertyOrField(item, item.PropertyType, type, schema, schemaResolver);
}
foreach (FieldInfo item2 in fields.Where(delegate(FieldInfo p) {
if (properties != null)
return properties.Contains(p.Name);
return true;
})) {
LoadPropertyOrField(item2, item2.FieldType, type, schema, schemaResolver);
}
GenerateInheritance(type, schema, schemaResolver);
}
private void GenerateKnownTypes(Type type, JsonSchemaResolver schemaResolver)
{
foreach (dynamic item in from a in ReflectionExtensions.GetCustomAttributes(ReflectionExtensions.GetTypeInfo(type), true)
where a.GetType().Name == "KnownTypeAttribute"
select a) {
dynamic val = JsonObjectTypeDescription.FromType(item.Type, null, Settings.DefaultEnumHandling);
dynamic val2 = val.Type == JsonObjectType.Integer;
if ((!schemaResolver.HasSchema(item.Type, val2)))
this.Generate(item.Type, schemaResolver);
}
}
private void GenerateInheritance(Type type, JsonSchema4 schema, JsonSchemaResolver schemaResolver)
{
GenerateInheritanceDiscriminator(type, schema);
Type baseType = ReflectionExtensions.GetTypeInfo(type).BaseType;
if (baseType != (Type)null && baseType != typeof(object)) {
if (Settings.FlattenInheritanceHierarchy)
GeneratePropertiesAndInheritance(baseType, schema, schemaResolver);
else {
JsonSchema4 item = Generate(baseType, schemaResolver);
schema.AllOf.Add(item);
}
}
}
private void GenerateInheritanceDiscriminator(Type type, JsonSchema4 schema)
{
if (!Settings.FlattenInheritanceHierarchy) {
string text = TryGetInheritanceDiscriminator(ReflectionExtensions.GetTypeInfo(type).GetCustomAttributes(false).OfType<Attribute>());
if (!string.IsNullOrEmpty(text)) {
if (schema.Properties.ContainsKey(text))
throw new InvalidOperationException("The JSON property '" + text + "' is defined multiple times on type '" + type.FullName + "'.");
schema.Discriminator = text;
schema.Properties[text] = new JsonProperty {
Type = JsonObjectType.String,
IsRequired = true
};
}
}
}
private string TryGetInheritanceDiscriminator(IEnumerable<Attribute> typeAttributes)
{
dynamic val = (typeAttributes != null) ? typeAttributes.FirstOrDefault((Attribute a) => a.GetType().Name == "JsonConverterAttribute") : null;
if ((val != null) && ((Type)val.ConverterType).Name == "JsonInheritanceConverter") {
dynamic val2 = val.ConverterParameters != null;
if ((val2 ? false : true) ? val2 : (val2 & (val.ConverterParameters.Length > 0)))
return (string)val.ConverterParameters[0];
return JsonInheritanceConverter.DefaultDiscriminatorName;
}
return null;
}
protected virtual string[] GetTypeProperties(Type type)
{
if (type == typeof(Exception))
return new string[4] {
"InnerException",
"Message",
"Source",
"StackTrace"
};
return null;
}
private void LoadEnumerations(Type type, JsonSchema4 schema, JsonObjectTypeDescription typeDescription)
{
schema.Type = typeDescription.Type;
schema.Enumeration.Clear();
schema.EnumerationNames.Clear();
string[] names = Enum.GetNames(type);
foreach (string text in names) {
if (typeDescription.Type == JsonObjectType.Integer) {
object item = Convert.ChangeType(Enum.Parse(type, text), Enum.GetUnderlyingType(type));
schema.Enumeration.Add(item);
} else {
dynamic val = ReflectionExtensions.GetCustomAttributes(ReflectionExtensions.GetTypeInfo(type).GetDeclaredField(text), true).TryGetIfAssignableTo("System.Runtime.Serialization.EnumMemberAttribute", TypeNameStyle.FullName);
dynamic val2 = val != null;
if ((val2 ? false : true) ? val2 : (val2 & !string.IsNullOrEmpty(val.Value)))
schema.Enumeration.Add((string)val.Value);
else
schema.Enumeration.Add(text);
}
schema.EnumerationNames.Add(text);
}
}
private void LoadPropertyOrField(MemberInfo property, Type propertyType, Type parentType, JsonSchema4 parentSchema, JsonSchemaResolver schemaResolver)
{
Attribute[] array = property.GetCustomAttributes(true).OfType<Attribute>().ToArray();
JsonObjectTypeDescription jsonObjectTypeDescription = JsonObjectTypeDescription.FromType(propertyType, array, Settings.DefaultEnumHandling);
if (!IsPropertyIgnored(parentType, array)) {
if (propertyType.Name == "Nullable`1")
propertyType = propertyType.GetGenericArguments()[0];
bool flag = RequiresSchemaReference(propertyType, array);
JsonProperty jsonProperty;
if (flag) {
JsonSchema4 jsonSchema = Generate<JsonSchema4>(propertyType, array, schemaResolver);
if (Settings.NullHandling == NullHandling.JsonSchema) {
jsonProperty = new JsonProperty();
jsonProperty.OneOf.Add(new JsonSchema4 {
SchemaReference = jsonSchema.ActualSchema
});
} else
jsonProperty = new JsonProperty {
SchemaReference = jsonSchema.ActualSchema
};
} else
jsonProperty = Generate<JsonProperty>(propertyType, array, schemaResolver);
string propertyName = JsonPathUtilities.GetPropertyName(property, Settings.DefaultPropertyNameHandling);
if (parentSchema.Properties.ContainsKey(propertyName))
throw new InvalidOperationException("The JSON property '" + propertyName + "' is defined multiple times on type '" + parentType.FullName + "'.");
parentSchema.Properties.Add(propertyName, jsonProperty);
Attribute attribute = array.TryGetIfAssignableTo("System.ComponentModel.DataAnnotations.RequiredAttribute", TypeNameStyle.FullName);
JsonPropertyAttribute jsonPropertyAttribute = array.OfType<JsonPropertyAttribute>().SingleOrDefault();
bool flag2 = jsonPropertyAttribute != null && (jsonPropertyAttribute.Required == Required.Always || jsonPropertyAttribute.Required == Required.AllowNull);
dynamic val = GetDataMemberAttribute(parentType, array)?.IsRequired == true;
bool flag3 = attribute != null;
dynamic val2 = flag3 ? ((object)flag3) : (flag3 | val);
if ((val2) || ((val2 | flag2) ? true : false))
parentSchema.RequiredProperties.Add(propertyName);
bool flag4 = jsonPropertyAttribute != null && jsonPropertyAttribute.Required == Required.AllowNull;
bool flag5 = !flag3;
val2 = ((!flag5) ? ((object)flag5) : (flag5 & !val));
dynamic val3 = (val2 ? false : true) ? val2 : (val2 & (jsonObjectTypeDescription.IsNullable | flag4));
if (val3) {
if (Settings.NullHandling == NullHandling.JsonSchema) {
if (flag)
jsonProperty.OneOf.Add(new JsonSchema4 {
Type = JsonObjectType.Null
});
else if (jsonProperty.Type == JsonObjectType.None) {
jsonProperty.OneOf.Add(new JsonSchema4 {
Type = JsonObjectType.None
});
jsonProperty.OneOf.Add(new JsonSchema4 {
Type = JsonObjectType.Null
});
} else {
jsonProperty.Type |= JsonObjectType.Null;
}
}
} else if (Settings.NullHandling == NullHandling.Swagger && !parentSchema.RequiredProperties.Contains(propertyName)) {
parentSchema.RequiredProperties.Add(propertyName);
}
dynamic val4 = array.TryGetIfAssignableTo("System.ComponentModel.ReadOnlyAttribute", TypeNameStyle.FullName);
if (val4 != null)
jsonProperty.IsReadOnly = ((byte)val4.IsReadOnly != 0);
jsonProperty.Description = GetDescription(property, array);
ApplyPropertyAnnotations(jsonProperty, parentType, array, jsonObjectTypeDescription);
}
}
private bool RequiresSchemaReference(Type type, IEnumerable<Attribute> parentAttributes)
{
JsonObjectTypeDescription jsonObjectTypeDescription = JsonObjectTypeDescription.FromType(type, parentAttributes, Settings.DefaultEnumHandling);
ITypeMapper typeMapper = Settings.TypeMappers.FirstOrDefault((ITypeMapper m) => m.MappedType == type);
if (typeMapper != null)
return typeMapper.UseReference;
if (!jsonObjectTypeDescription.IsDictionary) {
if (!jsonObjectTypeDescription.Type.HasFlag(JsonObjectType.Object))
return jsonObjectTypeDescription.IsEnum;
return true;
}
return false;
}
private static bool IsPropertyIgnored(Type parentType, Attribute[] propertyAttributes)
{
if (propertyAttributes.Any((Attribute a) => a is JsonIgnoreAttribute))
return true;
bool flag = HasDataContractAttribute(parentType);
dynamic val = (!flag) ? ((object)flag) : (flag & (GetDataMemberAttribute(parentType, propertyAttributes) == null));
if ((val ? false : true) ? val : (val & !propertyAttributes.Any((Attribute a) => a is JsonPropertyAttribute)))
return true;
return false;
}
private static dynamic GetDataMemberAttribute(Type parentType, Attribute[] propertyAttributes)
{
if (!HasDataContractAttribute(parentType))
return null;
return propertyAttributes.FirstOrDefault((Attribute a) => a.GetType().Name == "DataMemberAttribute");
}
private static bool HasDataContractAttribute(Type parentType)
{
return ReflectionExtensions.GetCustomAttributes(ReflectionExtensions.GetTypeInfo(parentType), true).Any((Attribute a) => a.GetType().Name == "DataContractAttribute");
}
public void ApplyPropertyAnnotations(JsonSchema4 jsonProperty, Type parentType, IList<Attribute> attributes, JsonObjectTypeDescription propertyTypeDescription)
{
dynamic val = attributes.TryGetIfAssignableTo("System.ComponentModel.DataAnnotations.DisplayAttribute", TypeNameStyle.FullName);
dynamic val2 = val != null;
if ((val2 ? false : true) ? val2 : (val2 & (val.Name != null)))
jsonProperty.Title = (string)val.Name;
dynamic val3 = attributes.TryGetIfAssignableTo("System.ComponentModel.DefaultValueAttribute", TypeNameStyle.FullName);
val2 = (val3 != null);
if ((val2 ? false : true) ? val2 : (val2 & (val3.Value != null)))
jsonProperty.Default = (object)this.ConvertDefaultValue(parentType, attributes, val3);
dynamic val4 = attributes.TryGetIfAssignableTo("System.ComponentModel.DataAnnotations.RegularExpressionAttribute", TypeNameStyle.FullName);
if (val4 != null)
jsonProperty.Pattern = (string)val4.Pattern;
if (propertyTypeDescription.Type == JsonObjectType.Number || propertyTypeDescription.Type == JsonObjectType.Integer) {
dynamic val5 = attributes.TryGetIfAssignableTo("System.ComponentModel.DataAnnotations.RangeAttribute", TypeNameStyle.FullName);
if (val5 != null) {
val2 = (val5.Minimum != null);
if ((val2 ? false : true) ? val2 : (val2 & (val5.Minimum > -1.7976931348623157E+308)))
jsonProperty.Minimum = (decimal)(double)val5.Minimum;
val2 = (val5.Maximum != null);
if ((val2 ? false : true) ? val2 : (val2 & (val5.Maximum < 1.7976931348623157E+308)))
jsonProperty.Maximum = (decimal)(double)val5.Maximum;
}
MultipleOfAttribute multipleOfAttribute = attributes.OfType<MultipleOfAttribute>().SingleOrDefault();
if (multipleOfAttribute != null)
jsonProperty.MultipleOf = multipleOfAttribute.MultipleOf;
}
dynamic val6 = attributes.TryGetIfAssignableTo("System.ComponentModel.DataAnnotations.MinLengthAttribute", TypeNameStyle.FullName);
val2 = (val6 != null);
if ((val2 ? false : true) ? val2 : (val2 & (val6.Length != null))) {
if (propertyTypeDescription.Type == JsonObjectType.String)
jsonProperty.MinLength = (int?)val6.Length;
else if (propertyTypeDescription.Type == JsonObjectType.Array) {
jsonProperty.MinItems = (int)val6.Length;
}
}
dynamic val7 = attributes.TryGetIfAssignableTo("System.ComponentModel.DataAnnotations.MaxLengthAttribute", TypeNameStyle.FullName);
val2 = (val7 != null);
if ((val2 ? false : true) ? val2 : (val2 & (val7.Length != null))) {
if (propertyTypeDescription.Type == JsonObjectType.String)
jsonProperty.MaxLength = (int?)val7.Length;
else if (propertyTypeDescription.Type == JsonObjectType.Array) {
jsonProperty.MaxItems = (int)val7.Length;
}
}
dynamic val8 = attributes.TryGetIfAssignableTo("System.ComponentModel.DataAnnotations.StringLengthAttribute", TypeNameStyle.FullName);
if ((val8 != null) && propertyTypeDescription.Type == JsonObjectType.String) {
jsonProperty.MinLength = (int?)val8.MinimumLength;
jsonProperty.MaxLength = (int?)val8.MaximumLength;
}
dynamic val9 = attributes.TryGetIfAssignableTo("System.ComponentModel.DataAnnotations.DataTypeAttribute", TypeNameStyle.FullName);
if (val9 != null) {
dynamic val10 = val9.DataType.ToString();
if (DataTypeFormats.ContainsKey(val10))
jsonProperty.Format = (string)DataTypeFormats[val10];
}
}
private object ConvertDefaultValue(Type parentType, IEnumerable<Attribute> propertyAttributes, dynamic defaultValueAttribute)
{
if (ReflectionExtensions.GetTypeInfo((Type)defaultValueAttribute.Value.GetType()).IsEnum) {
if (JsonObjectTypeDescription.IsStringEnum(parentType, propertyAttributes, Settings.DefaultEnumHandling))
return defaultValueAttribute.Value.ToString();
return (int)defaultValueAttribute.Value;
}
return defaultValueAttribute.Value;
}
private string GetDescription(MemberInfo memberInfo, IEnumerable<Attribute> attributes)
{
dynamic val = attributes.TryGetIfAssignableTo("System.ComponentModel.DescriptionAttribute", TypeNameStyle.FullName);
dynamic val2 = val != null;
if ((val2 ? false : true) ? val2 : (val2 & (val.Description != null)))
return (string)val.Description;
dynamic val3 = attributes.TryGetIfAssignableTo("System.ComponentModel.DataAnnotations.DisplayAttribute", TypeNameStyle.FullName);
val2 = (val3 != null);
if ((val2 ? false : true) ? val2 : (val2 & (val3.Description != null)))
return (string)val3.Description;
string xmlSummary = memberInfo.GetXmlSummary();
if (xmlSummary != string.Empty)
return xmlSummary;
return null;
}
}
}