SchemaTypesReader.cs 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Reflection;
  5. using System.Text;
  6. using JSONSCHEMA = NJsonSchema.JsonSchema;
  7. namespace SharpGLTF.SchemaReflection
  8. {
  9. /// <summary>
  10. /// Utility class that reads a json schema and creates a <see cref="SchemaType.Context"/>
  11. /// Which is a collection of Schema Types resembling <see cref="System.Type"/>
  12. /// </summary>
  13. static class SchemaTypesReader
  14. {
  15. // issues related to gltf2 schema parsing:
  16. // https://github.com/RSuter/NJsonSchema
  17. // https://github.com/RSuter/NJsonSchema/issues/378
  18. // https://github.com/RSuter/NJsonSchema/issues/377
  19. #region API
  20. public static SchemaType.Context Generate(NJsonSchema.CodeGeneration.CSharp.CSharpTypeResolver types)
  21. {
  22. var context = new SchemaType.Context();
  23. foreach(var t in types.Types.Keys)
  24. {
  25. context._UseType(t);
  26. }
  27. return context;
  28. }
  29. #endregion
  30. #region core
  31. private static SchemaType _UseType(this SchemaType.Context ctx, JSONSCHEMA schema, bool isRequired = true)
  32. {
  33. if (ctx == null) throw new ArgumentNullException(nameof(ctx));
  34. if (schema == null) throw new ArgumentNullException(nameof(schema));
  35. if (schema is NJsonSchema.JsonSchemaProperty prop)
  36. {
  37. // System.Diagnostics.Debug.Assert(prop.Name != "scene");
  38. isRequired &= prop.IsRequired;
  39. }
  40. if (_IsStringType(schema))
  41. {
  42. return ctx.UseString();
  43. }
  44. if (_IsBlittableType(schema))
  45. {
  46. bool isNullable = !isRequired;
  47. if (schema.Type == NJsonSchema.JsonObjectType.Integer) return ctx.UseBlittable(typeof(Int32).GetTypeInfo(), isNullable);
  48. if (schema.Type == NJsonSchema.JsonObjectType.Number) return ctx.UseBlittable(typeof(Double).GetTypeInfo(), isNullable);
  49. if (schema.Type == NJsonSchema.JsonObjectType.Boolean) return ctx.UseBlittable(typeof(Boolean).GetTypeInfo(), isNullable);
  50. throw new NotImplementedException();
  51. }
  52. if (schema.HasReference) return ctx._UseType(schema.ActualTypeSchema, isRequired); // we could have our own ref
  53. if (schema.IsArray)
  54. {
  55. return ctx.UseArray(ctx._UseType(schema.Item.ActualSchema));
  56. }
  57. if (_IsEnumeration(schema))
  58. {
  59. if (schema is NJsonSchema.JsonSchemaProperty property)
  60. {
  61. bool isNullable = !isRequired;
  62. var dict = new Dictionary<string, Int64>();
  63. foreach (var v in property.AnyOf)
  64. {
  65. var val = v.Enumeration.FirstOrDefault();
  66. var key = v.Description;
  67. if (val is String) { key = (string)val; val = (Int64)0; }
  68. if (string.IsNullOrWhiteSpace(key)) continue;
  69. dict[key] = (Int64)val;
  70. }
  71. // JSon Schema AnyOf enumerations are basically anonymous, so we create
  72. // a "shared name" with a concatenation of all the values:
  73. var name = string.Join("-", dict.Keys.OrderBy(item => item));
  74. var etype = ctx.UseEnum(name, isNullable);
  75. etype.Description = schema.Description;
  76. foreach (var kvp in dict) etype.SetValue(kvp.Key, (int)kvp.Value);
  77. if (dict.Values.Distinct().Count() > 1) etype.UseIntegers = true;
  78. return etype;
  79. }
  80. throw new NotImplementedException();
  81. }
  82. if (_IsDictionary(schema))
  83. {
  84. var key = ctx.UseString();
  85. var val = ctx._UseType(_GetDictionaryValue(schema));
  86. return ctx.UseDictionary(key, val);
  87. }
  88. if (_IsClass(schema))
  89. {
  90. var classDecl = ctx.UseClass(schema.Title);
  91. classDecl.Description = schema.Description;
  92. // process base class
  93. if (schema.InheritedSchema != null) classDecl.BaseClass = ctx._UseType(schema.InheritedSchema) as ClassType;
  94. // filter declared properties
  95. var keys = _GetProperyNames(schema);
  96. if (schema.InheritedSchema != null) // filter our parent properties
  97. {
  98. var baseKeys = _GetInheritedPropertyNames(schema).ToArray();
  99. keys = keys.Except(baseKeys).ToArray();
  100. }
  101. // process declared properties
  102. var props = keys.Select(key => schema.Properties.Values.FirstOrDefault(item => item.Name == key));
  103. var required = schema.RequiredProperties;
  104. // System.Diagnostics.Debug.Assert(schema.Title != "Buffer View");
  105. foreach(var p in props)
  106. {
  107. var field = classDecl.UseField(p.Name);
  108. field.Description = p.Description;
  109. field.FieldType = ctx._UseType(p, required.Contains(p.Name));
  110. field.MinimumValue = p.Minimum;
  111. field.MaximumValue = p.Maximum;
  112. field.DefaultValue = p.Default;
  113. field.MinItems = p.MinItems;
  114. field.MaxItems = p.MaxItems;
  115. }
  116. return classDecl;
  117. }
  118. if (schema.Type == NJsonSchema.JsonObjectType.Object) return ctx.UseAnyType();
  119. if (schema.Type == NJsonSchema.JsonObjectType.None) return ctx.UseAnyType();
  120. throw new NotImplementedException();
  121. }
  122. private static bool _IsBlittableType(JSONSCHEMA schema)
  123. {
  124. if (schema == null) return false;
  125. if (schema.Type == NJsonSchema.JsonObjectType.Boolean) return true;
  126. if (schema.Type == NJsonSchema.JsonObjectType.Number) return true;
  127. if (schema.Type == NJsonSchema.JsonObjectType.Integer) return true;
  128. return false;
  129. }
  130. private static bool _IsStringType(JSONSCHEMA schema)
  131. {
  132. return schema.Type == NJsonSchema.JsonObjectType.String;
  133. }
  134. private static bool _IsEnumeration(JSONSCHEMA schema)
  135. {
  136. if (schema.Type != NJsonSchema.JsonObjectType.None) return false;
  137. if (schema.IsArray || schema.IsDictionary) return false;
  138. if (schema.AnyOf.Count == 0) return false;
  139. // if (!schema.IsEnumeration) return false; // useless
  140. return true;
  141. }
  142. private static bool _IsDictionary(JSONSCHEMA schema)
  143. {
  144. // if (schema.Type != NJsonSchema.JsonObjectType.Object) return false;
  145. if (schema.AdditionalPropertiesSchema != null) return true;
  146. if (schema.AllowAdditionalProperties == false && schema.PatternProperties.Any()) return true;
  147. return false;
  148. }
  149. private static JSONSCHEMA _GetDictionaryValue(JSONSCHEMA schema)
  150. {
  151. if (schema.AdditionalPropertiesSchema != null)
  152. {
  153. return schema.AdditionalPropertiesSchema;
  154. }
  155. if (schema.AllowAdditionalProperties == false && schema.PatternProperties.Any())
  156. {
  157. var valueTypes = schema.PatternProperties.Values.ToArray();
  158. if (valueTypes.Length == 1) return valueTypes.First();
  159. }
  160. throw new NotImplementedException();
  161. }
  162. private static bool _IsClass(JSONSCHEMA schema)
  163. {
  164. if (schema.Type != NJsonSchema.JsonObjectType.Object) return false;
  165. return !string.IsNullOrWhiteSpace(schema.Title);
  166. }
  167. private static string[] _GetProperyNames(JSONSCHEMA schema)
  168. {
  169. return schema
  170. .Properties
  171. .Values
  172. .Select(item => item.Name)
  173. .ToArray();
  174. }
  175. private static string[] _GetInheritedPropertyNames(JSONSCHEMA schema)
  176. {
  177. if (schema?.InheritedSchema == null) return Enumerable.Empty<string>().ToArray();
  178. return _GetInheritedPropertyNames(schema.InheritedSchema)
  179. .Concat(_GetProperyNames(schema.InheritedSchema))
  180. .ToArray();
  181. }
  182. #endregion
  183. }
  184. }