JsonSchemaGenerator
Generates a JsonSchema4 object for a given type.
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NJsonSchema.Annotations;
using NJsonSchema.Infrastructure;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
namespace NJsonSchema.Generation
{
public class JsonSchemaGenerator
{
public JsonSchemaGeneratorSettings Settings { get; }
public JsonSchemaGenerator(JsonSchemaGeneratorSettings settings)
{
Settings = settings;
}
public JsonSchema4 Generate(Type type, ISchemaResolver schemaResolver)
{
return Generate<JsonSchema4>(type, null, null, new JsonSchemaDefinitionAppender(Settings.TypeNameGenerator), schemaResolver);
}
public JsonSchema4 Generate(Type type, JsonSchema4 rootSchema, IEnumerable<Attribute> parentAttributes, ISchemaDefinitionAppender schemaDefinitionAppender, ISchemaResolver schemaResolver)
{
return Generate<JsonSchema4>(type, rootSchema, parentAttributes, schemaDefinitionAppender, schemaResolver);
}
public TSchemaType Generate<TSchemaType>(Type type, JsonSchema4 rootSchema, IEnumerable<Attribute> parentAttributes, ISchemaDefinitionAppender schemaDefinitionAppender, ISchemaResolver schemaResolver) where TSchemaType : JsonSchema4, new
{
TSchemaType val = HandleSpecialTypes<TSchemaType>(type);
if (val != null)
return val;
val = new TSchemaType();
if (rootSchema == null)
rootSchema = val;
JsonObjectTypeDescription jsonObjectTypeDescription = JsonObjectTypeDescription.FromType(type, parentAttributes, Settings.DefaultEnumHandling);
jsonObjectTypeDescription.ApplyType(val);
ApplyExtensionDataAttributes(val, type, parentAttributes);
if (val.Type.HasFlag(JsonObjectType.Object)) {
if (jsonObjectTypeDescription.IsDictionary)
GenerateDictionary(type, rootSchema, val, schemaDefinitionAppender, schemaResolver);
else {
val.TypeNameRaw = GetTypeName(type);
if (schemaResolver.HasSchema(type, false)) {
val.SchemaReference = schemaResolver.GetSchema(type, false);
return val;
}
if ((object)val.GetType() != typeof(JsonSchema4)) {
val.SchemaReference = Generate(type, rootSchema, parentAttributes, schemaDefinitionAppender, schemaResolver);
return val;
}
val.Description = GetDescription(type.GetTypeInfo(), type.GetTypeInfo().GetCustomAttributes());
GenerateObject(type, val, rootSchema, schemaDefinitionAppender, schemaResolver);
}
} else if (type.GetTypeInfo().get_IsEnum()) {
bool isIntegerEnumeration = jsonObjectTypeDescription.Type == JsonObjectType.Integer;
if (schemaResolver.HasSchema(type, isIntegerEnumeration)) {
val.Type = jsonObjectTypeDescription.Type;
val.SchemaReference = schemaResolver.GetSchema(type, isIntegerEnumeration);
return val;
}
if ((object)val.GetType() != typeof(JsonSchema4)) {
val.SchemaReference = Generate(type, rootSchema, parentAttributes, schemaDefinitionAppender, schemaResolver);
return val;
}
LoadEnumerations(type, val, jsonObjectTypeDescription);
val.TypeNameRaw = GetTypeName(type);
val.Description = type.GetXmlDocumentation();
schemaResolver.AddSchema(type, isIntegerEnumeration, val);
} else if (val.Type.HasFlag(JsonObjectType.Array)) {
val.Type = JsonObjectType.Array;
Type[] genericTypeArguments = GetGenericTypeArguments(type);
Type type2 = (genericTypeArguments.Length == 0) ? type.GetElementType() : genericTypeArguments[0];
if ((object)type2 == null)
throw new InvalidOperationException("Could not find item type of array type '" + type.FullName + "'.");
val.Item = Generate(type2, rootSchema, null, schemaDefinitionAppender, schemaResolver);
}
return val;
}
private static void ApplyExtensionDataAttributes<TSchemaType>(TSchemaType schema, Type type, IEnumerable<Attribute> parentAttributes) where TSchemaType : JsonSchema4, new
{
if (parentAttributes == null) {
IEnumerable<JsonSchemaExtensionDataAttribute> customAttributes = type.GetTypeInfo().GetCustomAttributes<JsonSchemaExtensionDataAttribute>();
if (customAttributes.Any())
schema.ExtensionData = customAttributes.ToDictionary((JsonSchemaExtensionDataAttribute a) => a.Property, (JsonSchemaExtensionDataAttribute a) => a.Value);
} else {
IEnumerable<JsonSchemaExtensionDataAttribute> source = parentAttributes.OfType<JsonSchemaExtensionDataAttribute>();
if (source.Any())
schema.ExtensionData = source.ToDictionary((JsonSchemaExtensionDataAttribute a) => a.Property, (JsonSchemaExtensionDataAttribute a) => a.Value);
}
}
private TSchemaType HandleSpecialTypes<TSchemaType>(Type type) where TSchemaType : JsonSchema4, new
{
if ((object)type == typeof(object) || (object)type == typeof(JObject)) {
TSchemaType val = new TSchemaType();
val.Type = JsonObjectType.Object;
val.AllowAdditionalProperties = true;
return val;
}
return null;
}
private string GetTypeName(Type type)
{
if (type.IsConstructedGenericType)
return type.get_Name().Split(new char[1] {
'`'
}).First() + GetTypeName(type.GenericTypeArguments[0]);
return type.get_Name();
}
private void GenerateDictionary<TSchemaType>(Type type, JsonSchema4 rootSchema, TSchemaType schema, ISchemaDefinitionAppender schemaDefinitionAppender, ISchemaResolver schemaResolver) where TSchemaType : JsonSchema4, new
{
Type[] genericTypeArguments = GetGenericTypeArguments(type);
if (genericTypeArguments.Length != 2)
throw new InvalidOperationException("Could not find value type of dictionary type '" + type.FullName + "'.");
Type type2 = genericTypeArguments[1];
if ((object)type2 == typeof(object))
schema.AdditionalPropertiesSchema = new JsonSchema4 {
Type = (JsonObjectType.Array | JsonObjectType.Boolean | JsonObjectType.Integer | JsonObjectType.Null | JsonObjectType.Number | JsonObjectType.Object | JsonObjectType.String)
};
else
schema.AdditionalPropertiesSchema = Generate(type2, rootSchema, null, schemaDefinitionAppender, schemaResolver);
schema.AllowAdditionalProperties = true;
}
public Type[] GetGenericTypeArguments(Type type)
{
Type[] genericTypeArguments = type.GenericTypeArguments;
while ((object)type != null && (object)type != typeof(object) && genericTypeArguments.Length == 0) {
type = type.GetTypeInfo().get_BaseType();
if ((object)type != null)
genericTypeArguments = type.GenericTypeArguments;
}
return genericTypeArguments;
}
protected virtual void GenerateObject<TSchemaType>(Type type, TSchemaType schema, JsonSchema4 rootSchema, ISchemaDefinitionAppender schemaDefinitionAppender, ISchemaResolver schemaResolver) where TSchemaType : JsonSchema4, new
{
schemaResolver.AddSchema(type, false, schema);
schema.AllowAdditionalProperties = false;
GeneratePropertiesAndInheritance(type, schema, rootSchema, schemaDefinitionAppender, schemaResolver);
if (Settings.GenerateKnownTypes)
GenerateKnownTypes(type, rootSchema, schemaDefinitionAppender, schemaResolver);
}
private void GeneratePropertiesAndInheritance(Type type, JsonSchema4 schema, JsonSchema4 rootSchema, ISchemaDefinitionAppender schemaDefinitionAppender, ISchemaResolver schemaResolver)
{
string[] properties = GetTypeProperties(type);
foreach (PropertyInfo item in type.GetTypeInfo().DeclaredProperties.Where(delegate(PropertyInfo p) {
if (properties != null)
return properties.Contains(p.Name);
return true;
})) {
LoadProperty(type, item, schema, rootSchema, schemaDefinitionAppender, schemaResolver);
}
GenerateInheritance(type, schema, rootSchema, schemaDefinitionAppender, schemaResolver);
}
private void GenerateKnownTypes(Type type, JsonSchema4 rootSchema, ISchemaDefinitionAppender schemaDefinitionAppender, ISchemaResolver schemaResolver)
{
foreach (KnownTypeAttribute customAttribute in type.GetTypeInfo().GetCustomAttributes<KnownTypeAttribute>()) {
bool isIntegerEnumeration = JsonObjectTypeDescription.FromType(customAttribute.Type, null, Settings.DefaultEnumHandling).Type == JsonObjectType.Integer;
if (!schemaResolver.HasSchema(customAttribute.Type, isIntegerEnumeration)) {
JsonSchema4 jsonSchema = Generate(customAttribute.Type, rootSchema, null, schemaDefinitionAppender, schemaResolver);
schemaDefinitionAppender.Append(rootSchema, jsonSchema.ActualSchema);
}
}
}
private void GenerateInheritance(Type type, JsonSchema4 schema, JsonSchema4 rootSchema, ISchemaDefinitionAppender schemaDefinitionAppender, ISchemaResolver schemaResolver)
{
Type baseType = type.GetTypeInfo().get_BaseType();
if ((object)baseType != null && (object)baseType != typeof(object)) {
if (Settings.FlattenInheritanceHierarchy)
GeneratePropertiesAndInheritance(baseType, schema, rootSchema, schemaDefinitionAppender, schemaResolver);
else {
JsonSchema4 item = Generate(baseType, rootSchema, null, schemaDefinitionAppender, schemaResolver);
schema.AllOf.Add(item);
}
}
}
protected virtual string[] GetTypeProperties(Type type)
{
if ((object)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[] enumNames = GetEnumNames(type, typeDescription);
foreach (string text in enumNames) {
if (typeDescription.Type == JsonObjectType.Integer) {
object item = Convert.ChangeType(Enum.Parse(type, text), Enum.GetUnderlyingType(type));
schema.Enumeration.Add(item);
} else
schema.Enumeration.Add(text);
schema.EnumerationNames.Add(text);
}
}
private string[] GetEnumNames(Type type, JsonObjectTypeDescription typeDescription)
{
if (typeDescription.Type == JsonObjectType.String)
return Enum.GetNames(type).Select(delegate(string name) {
IEnumerable<Attribute> customAttributes = type.GetTypeInfo().GetDeclaredField(name).GetCustomAttributes();
object obj = TryGetAttribute(customAttributes, "System.Runtime.Serialization.EnumMemberAttribute");
object obj2 = (dynamic)obj != null;
if ((((dynamic)obj2) ? false : true) ? obj2 : ((dynamic)obj2 & !string.IsNullOrEmpty(((dynamic)obj).Value)))
return (string)((dynamic)obj).Value;
return name;
}).ToArray();
return Enum.GetNames(type);
}
private void LoadProperty(Type parentType, PropertyInfo property, JsonSchema4 parentSchema, JsonSchema4 rootSchema, ISchemaDefinitionAppender schemaDefinitionAppender, ISchemaResolver schemaResolver)
{
Type type = property.PropertyType;
JsonObjectTypeDescription jsonObjectTypeDescription = JsonObjectTypeDescription.FromType(type, property.GetCustomAttributes(), Settings.DefaultEnumHandling);
Attribute[] array = property.GetCustomAttributes().ToArray();
if (!IsPropertyIgnored(parentType, array)) {
if (type.get_Name() == "Nullable`1")
type = type.GenericTypeArguments[0];
bool flag = !jsonObjectTypeDescription.IsDictionary && (jsonObjectTypeDescription.Type.HasFlag(JsonObjectType.Object) || jsonObjectTypeDescription.IsEnum);
JsonProperty jsonProperty;
if (flag) {
JsonSchema4 jsonSchema = Generate(type, rootSchema, property.GetCustomAttributes(), schemaDefinitionAppender, schemaResolver);
if (Settings.PropertyNullHandling == PropertyNullHandling.OneOf) {
jsonProperty = new JsonProperty();
jsonProperty.OneOf.Add(new JsonSchema4 {
SchemaReference = jsonSchema.ActualSchema
});
} else
jsonProperty = new JsonProperty {
SchemaReference = jsonSchema.ActualSchema
};
} else {
jsonProperty = Generate<JsonProperty>(type, rootSchema, property.GetCustomAttributes(), schemaDefinitionAppender, schemaResolver);
jsonObjectTypeDescription.ApplyType(jsonProperty);
}
string propertyName = JsonPathUtilities.GetPropertyName(property);
parentSchema.Properties.Add(propertyName, jsonProperty);
Attribute attribute = TryGetAttribute(array, "System.ComponentModel.DataAnnotations.RequiredAttribute");
JsonPropertyAttribute customAttribute = property.GetCustomAttribute<JsonPropertyAttribute>();
bool flag2 = customAttribute != null && (customAttribute.Required == Required.Always || customAttribute.Required == Required.AllowNull);
bool num = attribute != null;
if (num | flag2)
parentSchema.RequiredProperties.Add(propertyName);
bool flag3 = customAttribute != null && customAttribute.Required == Required.AllowNull;
if (!num && (!jsonObjectTypeDescription.IsAlwaysRequired | flag3)) {
if (Settings.PropertyNullHandling == PropertyNullHandling.OneOf) {
if (flag)
jsonProperty.OneOf.Add(new JsonSchema4 {
Type = JsonObjectType.Null
});
else
jsonProperty.Type |= JsonObjectType.Null;
}
} else if (Settings.PropertyNullHandling == PropertyNullHandling.Required && !parentSchema.RequiredProperties.Contains(propertyName)) {
parentSchema.RequiredProperties.Add(propertyName);
}
dynamic val = TryGetAttribute(array, "System.ComponentModel.ReadOnlyAttribute");
if (val != null)
jsonProperty.IsReadOnly = ((byte)val.IsReadOnly != 0);
jsonProperty.Description = GetDescription(property, array);
ApplyPropertyAnnotations(jsonProperty, array, jsonObjectTypeDescription);
}
}
private static bool IsPropertyIgnored(Type parentType, Attribute[] propertyAttributes)
{
if (propertyAttributes.Any((Attribute a) => a is JsonIgnoreAttribute))
return true;
if (parentType.GetTypeInfo().GetCustomAttribute<DataContractAttribute>() != null && !propertyAttributes.Any((Attribute a) => a is DataMemberAttribute) && !propertyAttributes.Any((Attribute a) => a is JsonPropertyAttribute))
return true;
return false;
}
public void ApplyPropertyAnnotations(JsonSchema4 jsonProperty, IEnumerable<Attribute> attributes, JsonObjectTypeDescription propertyTypeDescription)
{
dynamic val = TryGetAttribute(attributes, "System.ComponentModel.DataAnnotations.DisplayAttribute");
dynamic val2 = val != null;
if ((val2 ? false : true) ? val2 : (val2 & (val.Name != null)))
jsonProperty.Title = (string)val.Name;
dynamic val3 = TryGetAttribute(attributes, "System.ComponentModel.DefaultValueAttribute");
val2 = (val3 != null);
if ((val2 ? false : true) ? val2 : (val2 & (val3.Value != null)))
jsonProperty.Default = (object)val3.Value;
dynamic val4 = TryGetAttribute(attributes, "System.ComponentModel.DataAnnotations.RegularExpressionAttribute");
if (val4 != null)
jsonProperty.Pattern = (string)val4.Pattern;
if (propertyTypeDescription.Type == JsonObjectType.Number || propertyTypeDescription.Type == JsonObjectType.Integer) {
dynamic val5 = TryGetAttribute(attributes, "System.ComponentModel.DataAnnotations.RangeAttribute");
if (val5 != null) {
if (val5.Minimum != null)
jsonProperty.Minimum = (double?)val5.Minimum;
if (val5.Maximum != null)
jsonProperty.Maximum = (double?)val5.Maximum;
}
MultipleOfAttribute multipleOfAttribute = attributes.OfType<MultipleOfAttribute>().SingleOrDefault();
if (multipleOfAttribute != null)
jsonProperty.MultipleOf = multipleOfAttribute.MultipleOf;
}
dynamic val6 = TryGetAttribute(attributes, "System.ComponentModel.DataAnnotations.MinLengthAttribute");
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 = TryGetAttribute(attributes, "System.ComponentModel.DataAnnotations.MaxLengthAttribute");
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;
}
}
}
private string GetDescription(MemberInfo memberInfo, IEnumerable<Attribute> attributes)
{
dynamic val = TryGetAttribute(attributes, "System.ComponentModel.DescriptionAttribute");
dynamic val2 = val != null;
if ((val2 ? false : true) ? val2 : (val2 & (val.Description != null)))
return (string)val.Description;
dynamic val3 = TryGetAttribute(attributes, "System.ComponentModel.DataAnnotations.DisplayAttribute");
val2 = (val3 != null);
if ((val2 ? false : true) ? val2 : (val2 & (val3.Description != null)))
return (string)val3.Description;
string xmlDocumentation = memberInfo.GetXmlDocumentation();
if (xmlDocumentation != string.Empty)
return xmlDocumentation;
return null;
}
private Attribute TryGetAttribute(IEnumerable<Attribute> attributes, string attributeType)
{
return attributes.FirstOrDefault((Attribute a) => a.GetType().FullName == attributeType);
}
}
}