SchemaTypesReader.cs 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  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 key = v.Description;
  66. var val = v.Enumeration?.FirstOrDefault();
  67. var ext = v.ExtensionData?.FirstOrDefault() ?? default;
  68. if (val is String txt)
  69. {
  70. System.Diagnostics.Debug.Assert(v.Type == NJsonSchema.JsonObjectType.None);
  71. key = txt; val = (Int64)0;
  72. }
  73. if (v.Type == NJsonSchema.JsonObjectType.None && ext.Key == "const")
  74. {
  75. key = (string)ext.Value; val = (Int64)0;
  76. }
  77. if (v.Type == NJsonSchema.JsonObjectType.Integer && ext.Key == "const")
  78. {
  79. val = (Int64)ext.Value;
  80. }
  81. System.Diagnostics.Debug.Assert(key != null || dict.Count > 0);
  82. if (string.IsNullOrWhiteSpace(key)) continue;
  83. dict[key] = (Int64)val;
  84. }
  85. // JSon Schema AnyOf enumerations are basically anonymous, so we create
  86. // a "shared name" with a concatenation of all the values:
  87. var name = string.Join("-", dict.Keys.OrderBy(item => item));
  88. var etype = ctx.UseEnum(name, isNullable);
  89. etype.Description = schema.Description;
  90. foreach (var kvp in dict) etype.SetValue(kvp.Key, (int)kvp.Value);
  91. if (dict.Values.Distinct().Count() > 1) etype.UseIntegers = true;
  92. return etype;
  93. }
  94. throw new NotImplementedException();
  95. }
  96. if (_IsDictionary(schema))
  97. {
  98. var key = ctx.UseString();
  99. var val = ctx._UseType(_GetDictionaryValue(schema));
  100. return ctx.UseDictionary(key, val);
  101. }
  102. if (_IsClass(schema))
  103. {
  104. var classDecl = ctx.UseClass(schema.Title);
  105. classDecl.Description = schema.Description;
  106. // process base class
  107. if (schema.InheritedSchema != null) classDecl.BaseClass = ctx._UseType(schema.InheritedSchema) as ClassType;
  108. // filter declared properties
  109. var keys = _GetProperyNames(schema);
  110. if (schema.InheritedSchema != null) // filter our parent properties
  111. {
  112. var baseKeys = _GetInheritedPropertyNames(schema).ToArray();
  113. keys = keys.Except(baseKeys).ToArray();
  114. }
  115. // process declared properties
  116. var props = keys.Select(key => schema.Properties.Values.FirstOrDefault(item => item.Name == key));
  117. var required = schema.RequiredProperties;
  118. // System.Diagnostics.Debug.Assert(schema.Title != "Buffer View");
  119. foreach(var p in props)
  120. {
  121. var field = classDecl.UseField(p.Name);
  122. field.Description = p.Description;
  123. field.FieldType = ctx._UseType(p, required.Contains(p.Name));
  124. field.ExclusiveMinimumValue = p.ExclusiveMinimum ?? (p.IsExclusiveMinimum ? p.Minimum : null);
  125. field.InclusiveMinimumValue = p.IsExclusiveMinimum ? null : p.Minimum;
  126. field.DefaultValue = p.Default;
  127. field.InclusiveMaximumValue = p.IsExclusiveMaximum ? null : p.Maximum;
  128. field.ExclusiveMaximumValue = p.ExclusiveMaximum ?? (p.IsExclusiveMaximum ? p.Maximum : null);
  129. field.MinItems = p.MinItems;
  130. field.MaxItems = p.MaxItems;
  131. }
  132. return classDecl;
  133. }
  134. if (schema.Type == NJsonSchema.JsonObjectType.Object) return ctx.UseAnyType();
  135. if (schema.Type == NJsonSchema.JsonObjectType.None) return ctx.UseAnyType();
  136. throw new NotImplementedException();
  137. }
  138. private static bool _IsBlittableType(JSONSCHEMA schema)
  139. {
  140. if (schema == null) return false;
  141. if (schema.Type == NJsonSchema.JsonObjectType.Boolean) return true;
  142. if (schema.Type == NJsonSchema.JsonObjectType.Number) return true;
  143. if (schema.Type == NJsonSchema.JsonObjectType.Integer) return true;
  144. return false;
  145. }
  146. private static bool _IsStringType(JSONSCHEMA schema)
  147. {
  148. return schema.Type == NJsonSchema.JsonObjectType.String;
  149. }
  150. private static bool _IsEnumeration(JSONSCHEMA schema)
  151. {
  152. if (schema.Type != NJsonSchema.JsonObjectType.None) return false;
  153. if (schema.IsArray || schema.IsDictionary) return false;
  154. if (schema.AnyOf.Count == 0) return false;
  155. // if (!schema.IsEnumeration) return false; // useless
  156. return true;
  157. }
  158. private static bool _IsDictionary(JSONSCHEMA schema)
  159. {
  160. // if (schema.Type != NJsonSchema.JsonObjectType.Object) return false;
  161. if (schema.AdditionalPropertiesSchema != null) return true;
  162. if (schema.AllowAdditionalProperties == false && schema.PatternProperties.Any()) return true;
  163. return false;
  164. }
  165. private static JSONSCHEMA _GetDictionaryValue(JSONSCHEMA schema)
  166. {
  167. if (schema.AdditionalPropertiesSchema != null)
  168. {
  169. return schema.AdditionalPropertiesSchema;
  170. }
  171. if (schema.AllowAdditionalProperties == false && schema.PatternProperties.Any())
  172. {
  173. var valueTypes = schema.PatternProperties.Values.ToArray();
  174. if (valueTypes.Length == 1) return valueTypes.First();
  175. }
  176. throw new NotImplementedException();
  177. }
  178. private static bool _IsClass(JSONSCHEMA schema)
  179. {
  180. if (schema.Type != NJsonSchema.JsonObjectType.Object) return false;
  181. return !string.IsNullOrWhiteSpace(schema.Title);
  182. }
  183. private static string[] _GetProperyNames(JSONSCHEMA schema)
  184. {
  185. return schema
  186. .Properties
  187. .Values
  188. .Select(item => item.Name)
  189. .ToArray();
  190. }
  191. private static string[] _GetInheritedPropertyNames(JSONSCHEMA schema)
  192. {
  193. if (schema?.InheritedSchema == null) return Enumerable.Empty<string>().ToArray();
  194. return _GetInheritedPropertyNames(schema.InheritedSchema)
  195. .Concat(_GetProperyNames(schema.InheritedSchema))
  196. .ToArray();
  197. }
  198. #endregion
  199. }
  200. }