JsonSerializable.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics.CodeAnalysis;
  4. using System.Linq;
  5. using System.Numerics;
  6. using System.Text;
  7. using System.Text.Json;
  8. using JSONEXCEPTION = System.Text.Json.JsonException;
  9. using JSONTOKEN = System.Text.Json.JsonTokenType;
  10. namespace SharpGLTF.IO
  11. {
  12. /// <summary>
  13. /// Represents the base class of a serializable glTF schema2 object.
  14. /// Inherited by <see cref="Schema2.ExtraProperties"/>.
  15. /// </summary>
  16. public abstract class JsonSerializable
  17. {
  18. #region validation
  19. internal void ValidateReferences(Validation.ValidationContext validate)
  20. {
  21. validate = validate.GetContext(this);
  22. OnValidateReferences(validate);
  23. }
  24. internal void ValidateContent(Validation.ValidationContext validate)
  25. {
  26. validate = validate.GetContext(this);
  27. OnValidateContent(validate);
  28. }
  29. protected virtual void OnValidateReferences(Validation.ValidationContext validate) { }
  30. protected virtual void OnValidateContent(Validation.ValidationContext validate) { }
  31. #endregion
  32. #region serialization
  33. internal void Serialize(Utf8JsonWriter writer)
  34. {
  35. Guard.NotNull(writer, nameof(writer));
  36. writer.WriteStartObject();
  37. SerializeProperties(writer);
  38. writer.WriteEndObject();
  39. }
  40. protected abstract void SerializeProperties(Utf8JsonWriter writer);
  41. protected static void SerializeProperty(Utf8JsonWriter writer, string name, Object value)
  42. {
  43. if (value == null) return;
  44. Guard.NotNull(writer, nameof(writer));
  45. _SerializeProperty(writer, name, value);
  46. }
  47. protected static void SerializeProperty(Utf8JsonWriter writer, string name, Boolean? value, Boolean? defval = null)
  48. {
  49. if (!value.HasValue) return;
  50. if (defval.HasValue && defval.Value.Equals(value.Value)) return;
  51. Guard.NotNull(writer, nameof(writer));
  52. writer.WriteBoolean(name, value.Value);
  53. }
  54. protected static void SerializeProperty(Utf8JsonWriter writer, string name, Int32? value, Int32? defval = null)
  55. {
  56. if (!value.HasValue) return;
  57. if (defval.HasValue && defval.Value.Equals(value.Value)) return;
  58. Guard.NotNull(writer, nameof(writer));
  59. writer.WriteNumber(name, value.Value);
  60. }
  61. protected static void SerializeProperty(Utf8JsonWriter writer, string name, Single? value, Single? defval = null)
  62. {
  63. if (!value.HasValue) return;
  64. if (defval.HasValue && defval.Value.Equals(value.Value)) return;
  65. Guard.NotNull(writer, nameof(writer));
  66. writer.WriteNumber(name, value.Value);
  67. }
  68. protected static void SerializeProperty(Utf8JsonWriter writer, string name, Double? value, Double? defval = null)
  69. {
  70. if (!value.HasValue) return;
  71. if (defval.HasValue && defval.Value.Equals(value.Value)) return;
  72. Guard.NotNull(writer, nameof(writer));
  73. writer.WriteNumber(name, value.Value);
  74. }
  75. protected static void SerializeProperty(Utf8JsonWriter writer, string name, Vector2? value, Vector2? defval = null)
  76. {
  77. if (!value.HasValue) return;
  78. if (defval.HasValue && defval.Value.Equals(value.Value)) return;
  79. Guard.NotNull(writer, nameof(writer));
  80. writer.WritePropertyName(name);
  81. writer.WriteVector2(value.Value);
  82. }
  83. protected static void SerializeProperty(Utf8JsonWriter writer, string name, Vector3? value, Vector3? defval = null)
  84. {
  85. if (!value.HasValue) return;
  86. if (defval.HasValue && defval.Value.Equals(value.Value)) return;
  87. Guard.NotNull(writer, nameof(writer));
  88. writer.WritePropertyName(name);
  89. writer.WriteVector3(value.Value);
  90. }
  91. protected static void SerializeProperty(Utf8JsonWriter writer, string name, Vector4? value, Vector4? defval = null)
  92. {
  93. if (!value.HasValue) return;
  94. if (defval.HasValue && defval.Value.Equals(value.Value)) return;
  95. Guard.NotNull(writer, nameof(writer));
  96. writer.WritePropertyName(name);
  97. writer.WriteVector4(value.Value);
  98. }
  99. protected static void SerializeProperty(Utf8JsonWriter writer, string name, Quaternion? value, Quaternion? defval = null)
  100. {
  101. if (!value.HasValue) return;
  102. if (defval.HasValue && defval.Value.Equals(value.Value)) return;
  103. Guard.NotNull(writer, nameof(writer));
  104. writer.WritePropertyName(name);
  105. writer.WriteQuaternion(value.Value);
  106. }
  107. protected static void SerializeProperty(Utf8JsonWriter writer, string name, Matrix4x4? value, Matrix4x4? defval = null)
  108. {
  109. if (!value.HasValue) return;
  110. if (defval.HasValue && defval.Value.Equals(value.Value)) return;
  111. Guard.NotNull(writer, nameof(writer));
  112. writer.WritePropertyName(name);
  113. writer.WriteMatrix4x4(value.Value);
  114. }
  115. protected static void SerializePropertyEnumValue<T>(Utf8JsonWriter writer, string name, T? value, T? defval = null)
  116. where T : struct
  117. {
  118. Guard.IsTrue(typeof(T).IsEnum, nameof(T));
  119. if (!value.HasValue) return;
  120. if (defval.HasValue && defval.Value.Equals(value)) return;
  121. Guard.NotNull(writer, nameof(writer));
  122. writer.WriteNumber(name, (int)(Object)value);
  123. }
  124. protected static void SerializePropertyEnumSymbol<T>(Utf8JsonWriter writer, string name, T? value, T? defval = null)
  125. where T : struct
  126. {
  127. Guard.IsTrue(typeof(T).IsEnum, nameof(T));
  128. if (!value.HasValue) return;
  129. if (defval.HasValue && defval.Value.Equals(value)) return;
  130. Guard.NotNull(writer, nameof(writer));
  131. writer.WriteString(name, Enum.GetName(typeof(T), value));
  132. }
  133. protected static void SerializePropertyObject<T>(Utf8JsonWriter writer, string name, T value)
  134. where T : JsonSerializable
  135. {
  136. if (value == null) return;
  137. Guard.NotNull(writer, nameof(writer));
  138. _SerializeProperty(writer, name, value);
  139. }
  140. protected static void SerializeProperty<T>(Utf8JsonWriter writer, string name, IReadOnlyList<T> collection, int? minItems = 1)
  141. {
  142. if (collection == null) return;
  143. if (minItems.HasValue && collection.Count < minItems.Value) return;
  144. Guard.NotNull(writer, nameof(writer));
  145. writer.WritePropertyName(name);
  146. writer.WriteStartArray();
  147. foreach (var item in collection) _SerializeValue(writer, item);
  148. writer.WriteEndArray();
  149. }
  150. protected static void SerializeProperty<T>(Utf8JsonWriter writer, string name, IReadOnlyDictionary<String, T> collection)
  151. {
  152. if (collection == null) return;
  153. if (collection.Count < 1) return;
  154. Guard.NotNull(writer, nameof(writer));
  155. writer.WritePropertyName(name);
  156. writer.WriteStartObject();
  157. foreach (var item in collection) _SerializeProperty(writer, item.Key, item.Value);
  158. writer.WriteEndObject();
  159. }
  160. private static void _SerializeProperty(Utf8JsonWriter writer, String name, Object value)
  161. {
  162. Guard.NotNull(writer, nameof(writer));
  163. Guard.NotNull(value, nameof(value));
  164. if (_IsNullOrEmpty(value)) return;
  165. if (writer.TryWriteProperty(name, value)) return;
  166. writer.WritePropertyName(name);
  167. _SerializeValue(writer, value);
  168. }
  169. private static bool _IsNullOrEmpty(Object value)
  170. {
  171. if (value == null) return true;
  172. if (value is System.Collections.ICollection c && c.Count == 0) return true;
  173. return false;
  174. }
  175. private static void _SerializeValue(Utf8JsonWriter writer, Object value)
  176. {
  177. Guard.NotNull(writer, nameof(writer));
  178. Guard.NotNull(value, nameof(value));
  179. System.Diagnostics.Debug.Assert(!value.GetType().IsEnum, "gltf schema does not define a typed way of serializing enums");
  180. if (writer.TryWriteValue(value)) return;
  181. if (value is System.Text.Json.Nodes.JsonNode jnode)
  182. {
  183. jnode.WriteTo(writer);
  184. return;
  185. }
  186. if (value is JsonSerializable vgltf) { vgltf.Serialize(writer); return; }
  187. if (value is System.Collections.IDictionary dict)
  188. {
  189. if (dict.Count == 0) return;
  190. writer.WriteStartObject();
  191. foreach (var key in dict.Keys)
  192. {
  193. var val = dict[key];
  194. if (val == null) continue;
  195. // if the value is a collection, we need to check if the collection is empty
  196. // to prevent writing the key, without writing the value.
  197. if (!(val is String || val is JsonSerializable))
  198. {
  199. if (val is System.Collections.IList xlist && xlist.Count == 0) continue;
  200. if (val is System.Collections.IDictionary xdict && xdict.Count == 0) continue;
  201. }
  202. _SerializeProperty(writer, key.ToString(), val);
  203. }
  204. writer.WriteEndObject();
  205. return;
  206. }
  207. if (value is System.Collections.IList list)
  208. {
  209. if (list.Count == 0) return;
  210. writer.WriteStartArray();
  211. foreach (var item in list)
  212. {
  213. _SerializeValue(writer, item);
  214. }
  215. writer.WriteEndArray();
  216. return;
  217. }
  218. throw new NotImplementedException($"Serialization of {value.GetType().Name} types is not supported.");
  219. }
  220. #endregion
  221. #region deserialization
  222. internal void Deserialize(ref Utf8JsonReader reader)
  223. {
  224. if (reader.TokenType == JSONTOKEN.PropertyName) reader.Read();
  225. if (reader.TokenType == JSONTOKEN.StartObject)
  226. {
  227. while (reader.Read() && reader.TokenType != JSONTOKEN.EndObject)
  228. {
  229. if (reader.TokenType == JSONTOKEN.PropertyName)
  230. {
  231. var key = reader.GetString();
  232. DeserializeProperty(key, ref reader);
  233. }
  234. else
  235. {
  236. throw new NotImplementedException();
  237. }
  238. }
  239. return;
  240. }
  241. throw new JSONEXCEPTION($"Unexpected token {reader.TokenType}");
  242. }
  243. protected static Object DeserializeUnknownObject(ref Utf8JsonReader reader)
  244. {
  245. if (reader.TokenType == JSONTOKEN.PropertyName) reader.Read();
  246. if (reader.TokenType == JSONTOKEN.StartArray)
  247. {
  248. var list = new List<Object>();
  249. while (reader.Read() && reader.TokenType != JSONTOKEN.EndArray)
  250. {
  251. list.Add(DeserializeUnknownObject(ref reader));
  252. }
  253. return list;
  254. }
  255. if (reader.TokenType == JSONTOKEN.StartObject)
  256. {
  257. var dict = new Dictionary<String, Object>();
  258. while (reader.Read() && reader.TokenType != JSONTOKEN.EndObject)
  259. {
  260. if (reader.TokenType == JSONTOKEN.PropertyName)
  261. {
  262. var key = reader.GetString();
  263. dict[key] = DeserializeUnknownObject(ref reader);
  264. }
  265. else
  266. {
  267. throw new JSONEXCEPTION();
  268. }
  269. }
  270. return dict;
  271. }
  272. System.Diagnostics.Debug.Assert(reader.TokenType != JSONTOKEN.None);
  273. System.Diagnostics.Debug.Assert(reader.TokenType != JSONTOKEN.EndArray);
  274. System.Diagnostics.Debug.Assert(reader.TokenType != JSONTOKEN.EndObject);
  275. // System.Diagnostics.Debug.Assert(reader.TokenType != JsonToken.EndConstructor);
  276. return reader.GetAnyValue();
  277. }
  278. protected abstract void DeserializeProperty(string jsonPropertyName, ref Utf8JsonReader reader);
  279. protected static T DeserializePropertyValue<
  280. #if !NETSTANDARD
  281. [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)]
  282. #endif
  283. T>(ref Utf8JsonReader reader)
  284. {
  285. _TryCastValue<T>(ref reader, out Object v);
  286. System.Diagnostics.Debug.Assert(reader.TokenType != JSONTOKEN.StartArray);
  287. System.Diagnostics.Debug.Assert(reader.TokenType != JSONTOKEN.StartObject);
  288. System.Diagnostics.Debug.Assert(reader.TokenType != JSONTOKEN.PropertyName);
  289. // System.Diagnostics.Debug.Assert(reader.TokenType != JsonToken.StartConstructor);
  290. return (T)v;
  291. }
  292. protected static void DeserializePropertyList<
  293. #if !NETSTANDARD
  294. [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)]
  295. #endif
  296. T>(ref Utf8JsonReader reader, IList<T> list)
  297. {
  298. // Guard.NotNull(reader, nameof(reader));
  299. Guard.NotNull(list, nameof(list));
  300. if (reader.TokenType == JSONTOKEN.PropertyName) reader.Read();
  301. if (reader.TokenType != JSONTOKEN.StartArray) throw new JSONEXCEPTION();
  302. if (reader.TokenType == JSONTOKEN.StartObject) throw new JSONEXCEPTION();
  303. while (reader.Read() && reader.TokenType != JSONTOKEN.EndArray)
  304. {
  305. if (_TryCastValue<T>(ref reader, out Object item))
  306. {
  307. list.Add((T)item);
  308. }
  309. System.Diagnostics.Debug.Assert(reader.TokenType != JSONTOKEN.StartArray);
  310. System.Diagnostics.Debug.Assert(reader.TokenType != JSONTOKEN.StartObject);
  311. System.Diagnostics.Debug.Assert(reader.TokenType != JSONTOKEN.PropertyName);
  312. // System.Diagnostics.Debug.Assert(reader.TokenType != JsonToken.StartConstructor);
  313. }
  314. if (list.Count == 0) throw new JSONEXCEPTION("Empty array found.");
  315. System.Diagnostics.Debug.Assert(reader.TokenType == JSONTOKEN.EndArray);
  316. }
  317. protected static void DeserializePropertyDictionary<
  318. #if !NETSTANDARD
  319. [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)]
  320. #endif
  321. T>(ref Utf8JsonReader reader, IDictionary<string, T> dict)
  322. {
  323. Guard.NotNull(dict, nameof(dict));
  324. if (reader.TokenType == JSONTOKEN.PropertyName) reader.Read();
  325. if (reader.TokenType == JSONTOKEN.StartArray) throw new JSONEXCEPTION();
  326. if (reader.TokenType != JSONTOKEN.StartObject) throw new JSONEXCEPTION();
  327. while (reader.Read() && reader.TokenType != JSONTOKEN.EndObject)
  328. {
  329. if (reader.TokenType == JSONTOKEN.PropertyName)
  330. {
  331. var key = reader.GetString();
  332. if (_TryCastValue<T>(ref reader, out Object val))
  333. {
  334. dict[key] = (T)val;
  335. }
  336. System.Diagnostics.Debug.Assert(reader.TokenType != JSONTOKEN.StartArray);
  337. System.Diagnostics.Debug.Assert(reader.TokenType != JSONTOKEN.StartObject);
  338. System.Diagnostics.Debug.Assert(reader.TokenType != JSONTOKEN.PropertyName);
  339. // System.Diagnostics.Debug.Assert(reader.TokenType != JsonToken.StartConstructor);
  340. }
  341. }
  342. if (dict.Count == 0) throw new JSONEXCEPTION("Empty dictionary found.");
  343. }
  344. private static bool _TryCastValue
  345. <
  346. #if !NETSTANDARD
  347. [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.NonPublicConstructors)]
  348. #endif
  349. T>(ref Utf8JsonReader reader, out Object value)
  350. {
  351. value = null;
  352. if (reader.TokenType == JSONTOKEN.EndArray) return false;
  353. if (reader.TokenType == JSONTOKEN.EndObject) return false;
  354. // if (reader.TokenType == JsonToken.EndConstructor) return false;
  355. if (reader.TokenType == JSONTOKEN.PropertyName) reader.Read();
  356. // untangle nullable
  357. var vtype = typeof(T);
  358. var ntype = Nullable.GetUnderlyingType(vtype);
  359. if (ntype != null) vtype = ntype;
  360. // known types
  361. if (vtype == typeof(String)) { value = reader.AsString(); return true; }
  362. if (vtype == typeof(Boolean)) { value = reader.AsBoolean(); return true; }
  363. if (vtype == typeof(Int16)) { value = reader.GetInt16(); return true; }
  364. if (vtype == typeof(Int32)) { value = reader.GetInt32(); return true; }
  365. if (vtype == typeof(Int64)) { value = reader.GetInt64(); return true; }
  366. if (vtype == typeof(UInt16)) { value = reader.GetUInt16(); return true; }
  367. if (vtype == typeof(UInt32)) { value = reader.GetUInt32(); return true; }
  368. if (vtype == typeof(UInt64)) { value = reader.GetUInt64(); return true; }
  369. if (vtype == typeof(Single)) { value = reader.GetSingle(); return true; }
  370. if (vtype == typeof(Double)) { value = reader.GetDouble(); return true; }
  371. if (vtype == typeof(Decimal)) { value = reader.GetDecimal(); return true; }
  372. if (vtype.IsEnum) { value = reader.AsEnum(vtype); return true; }
  373. if (vtype == typeof(Vector2))
  374. {
  375. var l = new List<float>(2);
  376. DeserializePropertyList<float>(ref reader, l);
  377. value = new Vector2(l[0], l[1]);
  378. return true;
  379. }
  380. if (vtype == typeof(Vector3))
  381. {
  382. var l = new List<float>(3);
  383. DeserializePropertyList<float>(ref reader, l);
  384. value = new Vector3(l[0], l[1], l[2]);
  385. return true;
  386. }
  387. if (vtype == typeof(Vector4))
  388. {
  389. var l = new List<float>(4);
  390. DeserializePropertyList<float>(ref reader, l);
  391. value = new Vector4(l[0], l[1], l[2], l[3]);
  392. return true;
  393. }
  394. if (vtype == typeof(Quaternion))
  395. {
  396. var l = new List<float>(4);
  397. DeserializePropertyList<float>(ref reader, l);
  398. value = new Quaternion(l[0], l[1], l[2], l[3]);
  399. return true;
  400. }
  401. if (vtype == typeof(Matrix4x4))
  402. {
  403. var l = new List<float>(16);
  404. DeserializePropertyList<float>(ref reader, l);
  405. value = new Matrix4x4
  406. (
  407. l[0], l[1], l[2], l[3],
  408. l[4], l[5], l[6], l[7],
  409. l[8], l[9], l[10], l[11],
  410. l[12], l[13], l[14], l[15]
  411. );
  412. return true;
  413. }
  414. if (typeof(System.Text.Json.Nodes.JsonNode).IsAssignableFrom(vtype))
  415. {
  416. value = System.Text.Json.Nodes.JsonNode.Parse(ref reader);
  417. return true;
  418. }
  419. if (typeof(JsonSerializable).IsAssignableFrom(vtype))
  420. {
  421. // Instance creation on AOT compiled binaries depends on classes defining:
  422. // [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicConstructors | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
  423. var item = Activator.CreateInstance(vtype, true) as JsonSerializable;
  424. // System.Diagnostics.Debug.Assert(reader.TokenType == JSONTOKEN.StartObject);
  425. item.Deserialize(ref reader);
  426. // System.Diagnostics.Debug.Assert(reader.TokenType == JSONTOKEN.EndObject);
  427. value = item;
  428. return true;
  429. }
  430. if (vtype.IsGenericType)
  431. {
  432. if (vtype.GetGenericTypeDefinition() == typeof(Dictionary<,>))
  433. {
  434. var valType = vtype.GetGenericArguments()[1];
  435. if (valType == typeof(Int32))
  436. {
  437. var dict = new Dictionary<string, Int32>();
  438. DeserializePropertyDictionary(ref reader, dict);
  439. value = dict;
  440. return true;
  441. }
  442. }
  443. throw new NotImplementedException($"Can't deserialize {vtype}");
  444. }
  445. throw new NotImplementedException($"Can't deserialize {vtype}");
  446. }
  447. #endregion
  448. }
  449. }