SchemaTypesReader.cs 8.8 KB

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