public JsonSchemaRefAttribute(Type tagType, InfluenceRange influence = InfluenceRange.Entiry) { Type schemaBaseType; if (!RefChecker.IsRefTagDerived(tagType, out schemaBaseType)) { throw new ArgumentException("IRefTag<T> must be derived by tagType"); } TagType = tagType; Influence = influence; }
public static JsonSchemaAttribute CreateFromType(Type ty, JsonSchemaRegistory reg = null, bool asRef = false) { var kind = Node.KindOfType(ty); switch (kind) { case NodeKind.Boolean: return(new JsonSchemaAttribute { Type = "boolean", }); case NodeKind.Integer: object[] enumsForInteger = null; if (TypeHelper.TypeWrap(ty).IsEnum) { enumsForInteger = System.Enum.GetValues(ty).Cast <object>().ToArray(); } return(new JsonSchemaAttribute { Type = "integer", Enum = enumsForInteger, }); case NodeKind.Float: return(new JsonSchemaAttribute { Type = "number", }); case NodeKind.String: object[] enumsForString = null; if (TypeHelper.TypeWrap(ty).IsEnum) { enumsForString = TypeHelper.GetStringEnumNames(ty); } return(new JsonSchemaAttribute { Type = "string", Enum = enumsForString, }); case NodeKind.Array: var elemTy = TypeHelper.ElemTypeOfIEnumerable(ty); return(new JsonSchemaAttribute { Type = "array", Items = elemTy != null?CreateFromType(elemTy, reg, true) : null, }); case NodeKind.Object: if (ty == typeof(object)) { return(new JsonSchemaAttribute()); } if (TypeHelper.TypeWrap(ty).IsGenericType&& ty.GetGenericTypeDefinition() == typeof(Dictionary <,>)) { return(new JsonSchemaAttribute { Type = "object", }); } break; default: throw new NotImplementedException(); } if (reg == null) { reg = JsonSchemaRegistory.GetDefault(); } var schema = TypeHelper.GetCustomAttribute <JsonSchemaAttribute>(ty); if (schema == null) { schema = new JsonSchemaAttribute(); } schema.Type = "object"; var schemaId = schema.Id; if (schemaId == null) { schemaId = ty.ToString(); } var refSchema = reg.Resolve(schemaId); if (refSchema != null) { schema = refSchema; goto skip; } else { reg.Register(schemaId, schema); } var baseType = TypeHelper.TypeWrap(ty).BaseType; HashSet <string> baseFieldNames = null; if (baseType != null) { Type schemaBaseType; if (RefChecker.IsRefTag(baseType, out schemaBaseType)) { var baseSchemaValue = CreateFromType(schemaBaseType, reg, false); schema.Type = baseSchemaValue.Type; goto skip; } // Nest fields included in the base class var baseSchema = CreateFromType(baseType, reg, true); if (baseSchema != null && baseSchema.Ref != null) { schema.AddToAllOf(baseSchema); var baseFields = TypeHelper.TypeWrap(baseType).GetFields(BindingFlags.Public | BindingFlags.Instance); baseFieldNames = new HashSet <string>(baseFields.Select(f => f.Name)); } } var properties = new Dictionary <string, JsonSchemaAttribute>(); var required = new List <string>(); var dependencies = new Dictionary <string, string[]>(); var fields = TypeHelper.TypeWrap(ty).GetFields(BindingFlags.Public | BindingFlags.Instance); foreach (var field in fields) { var fieldType = field.FieldType; JsonSchemaAttribute fieldSchema = null; var attr = TypeHelper.GetCustomAttribute <JsonFieldAttribute>(field); var elemName = JsonFieldAttribute.FieldName(attr, field); // TODO: duplication check // If elements are also included in Base classes, skip collecting a schema for the elements. if (baseFieldNames != null && baseFieldNames.Contains(field.Name)) { fieldSchema = new JsonSchemaAttribute(); goto skipField; } fieldSchema = TypeHelper.GetCustomAttribute <JsonSchemaAttribute>(field); if (fieldSchema == null) { fieldSchema = new JsonSchemaAttribute(); } var fieldItemsSchema = TypeHelper.GetCustomAttribute <ItemsJsonSchemaAttribute>(field); if (fieldItemsSchema != null) { fieldSchema.Items = fieldItemsSchema; } if (attr != null && attr.DynamicResolverTag != null) { if (!TypeHelper.TypeWrap(fieldType).IsGenericType || fieldType.GetGenericTypeDefinition() != typeof(Dictionary <,>)) { var baseMsg = "A type of the field which has DynamicResolver must be a Dictionary<,>"; var msg = string.Format("{0}: Type = {1} at \"{2}\" of {3}", baseMsg, fieldType, elemName, ty); throw new ArgumentException(msg); } var keyType = TypeHelper.TypeWrap(fieldType).GetGenericArguments()[0]; if (keyType != typeof(string)) { var baseMsg = "A key of the dictionary which has DynamicResolver must be a string type"; var msg = string.Format("{0}: KeyType = {1} at \"{2}\" of {3}", baseMsg, keyType, elemName, ty); throw new ArgumentException(msg); } fieldSchema._dynamicResolverTag = attr.DynamicResolverTag; } var fieldItemRequired = TypeHelper.GetCustomAttribute <JsonSchemaRequiredAttribute>(field); if (fieldItemRequired != null) { required.Add(elemName); } var fieldItemDependencies = TypeHelper.GetCustomAttribute <JsonSchemaDependenciesAttribute>(field); if (fieldItemDependencies != null) { dependencies.Add(elemName, fieldItemDependencies.Dependencies); } var fieldTypeSchema = CreateFromType(fieldType, reg, true); if (fieldTypeSchema.Ref != null) { fieldSchema = fieldTypeSchema; } else { // Update if (fieldSchema.Type == null) { fieldSchema.Type = fieldTypeSchema.Type; } if (fieldSchema.Enum == null) { fieldSchema.Enum = fieldTypeSchema.Enum; } if (fieldTypeSchema.Items != null) { var fieldTypeSchemaItems = fieldTypeSchema.Items as JsonSchemaAttribute; if (fieldTypeSchemaItems.Ref != null) { fieldSchema.Items = fieldTypeSchemaItems; } else { if (fieldTypeSchemaItems.Type != null) { var fieldSchemaItems = fieldSchema.Items as JsonSchemaAttribute; if (fieldSchemaItems != null) { fieldSchemaItems.Type = fieldTypeSchemaItems.Type; } else { fieldSchema.Items = new JsonSchemaAttribute { Type = fieldTypeSchemaItems.Type, }; } } if (fieldTypeSchemaItems.Enum != null) { var fieldSchemaItems = fieldSchema.Items as JsonSchemaAttribute; fieldSchemaItems.Enum = fieldTypeSchemaItems.Enum; } } } } // Add custom refs to AllOf not to override constrains which already existing. var customRef = TypeHelper.GetCustomAttribute <JsonSchemaRefAttribute>(field); if (customRef != null) { Type schemaBaseType; if (!RefChecker.IsRefTagDerived(customRef.TagType, out schemaBaseType)) { throw new ArgumentException("IRefTag<T> must be derived by tagType"); } var customSchema = CreateFromType(customRef.TagType, reg, true); switch (customRef.Influence) { case InfluenceRange.Entiry: fieldSchema.AddToAllOf(customSchema); break; case InfluenceRange.AdditionalProperties: if (fieldSchema.AdditionalProperties == null) { fieldSchema.AdditionalProperties = new JsonSchemaAttribute(); } fieldSchema.AdditionalProperties.AddToAllOf(customSchema); break; } } // Add custom refs to AllOf not to override constrains which already existing. var customItemsRef = TypeHelper.GetCustomAttribute <ItemsJsonSchemaRefAttribute>(field); if (customItemsRef != null) { Type schemaBaseType; if (!RefChecker.IsRefTagDerived(customItemsRef.TagType, out schemaBaseType)) { throw new ArgumentException("IRefTag<T> must be derived by tagType"); } var customSchema = CreateFromType(customItemsRef.TagType, reg, true); switch (customItemsRef.Influence) { case InfluenceRange.Entiry: if (fieldSchema.Items == null) { fieldSchema.Items = new JsonSchemaAttribute(); } ((JsonSchemaAttribute)fieldSchema.Items).AddToAllOf(customSchema); break; case InfluenceRange.AdditionalProperties: if (fieldSchema.Items == null) { fieldSchema.Items = new JsonSchemaAttribute(); } if (((JsonSchemaAttribute)fieldSchema.Items).AdditionalProperties == null) { ((JsonSchemaAttribute)fieldSchema.Items).AdditionalProperties = new JsonSchemaAttribute(); } ((JsonSchemaAttribute)fieldSchema.Items).AdditionalProperties.AddToAllOf(customSchema); break; } } skipField: properties.Add(elemName, fieldSchema); } schema.Properties = properties; if (required.Count != 0) { schema.Required = required.ToArray(); } if (dependencies.Count != 0) { schema.Dependencies = dependencies; } skip: if (asRef) { return(new JsonSchemaAttribute { Ref = schemaId, }); } return(schema); }