using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Numerics; using System.Text; using System.Text.Json; using JSONEXCEPTION = System.Text.Json.JsonException; using JSONTOKEN = System.Text.Json.JsonTokenType; namespace SharpGLTF.IO { /// /// Represents the base class of a serializable glTF schema2 object. /// Inherited by . /// public abstract class JsonSerializable { #region validation internal void ValidateReferences(Validation.ValidationContext validate) { validate = validate.GetContext(this); OnValidateReferences(validate); } internal void ValidateContent(Validation.ValidationContext validate) { validate = validate.GetContext(this); OnValidateContent(validate); } protected virtual void OnValidateReferences(Validation.ValidationContext validate) { } protected virtual void OnValidateContent(Validation.ValidationContext validate) { } #endregion #region serialization internal void Serialize(Utf8JsonWriter writer) { Guard.NotNull(writer, nameof(writer)); writer.WriteStartObject(); SerializeProperties(writer); writer.WriteEndObject(); } protected abstract void SerializeProperties(Utf8JsonWriter writer); protected static void SerializeProperty(Utf8JsonWriter writer, string name, Object value) { if (value == null) return; Guard.NotNull(writer, nameof(writer)); _SerializeProperty(writer, name, value); } protected static void SerializeProperty(Utf8JsonWriter writer, string name, Boolean? value, Boolean? defval = null) { if (!value.HasValue) return; if (defval.HasValue && defval.Value.Equals(value.Value)) return; Guard.NotNull(writer, nameof(writer)); writer.WriteBoolean(name, value.Value); } protected static void SerializeProperty(Utf8JsonWriter writer, string name, Int32? value, Int32? defval = null) { if (!value.HasValue) return; if (defval.HasValue && defval.Value.Equals(value.Value)) return; Guard.NotNull(writer, nameof(writer)); writer.WriteNumber(name, value.Value); } protected static void SerializeProperty(Utf8JsonWriter writer, string name, Single? value, Single? defval = null) { if (!value.HasValue) return; if (defval.HasValue && defval.Value.Equals(value.Value)) return; Guard.NotNull(writer, nameof(writer)); writer.WriteNumber(name, value.Value); } protected static void SerializeProperty(Utf8JsonWriter writer, string name, Double? value, Double? defval = null) { if (!value.HasValue) return; if (defval.HasValue && defval.Value.Equals(value.Value)) return; Guard.NotNull(writer, nameof(writer)); writer.WriteNumber(name, value.Value); } protected static void SerializeProperty(Utf8JsonWriter writer, string name, Vector2? value, Vector2? defval = null) { if (!value.HasValue) return; if (defval.HasValue && defval.Value.Equals(value.Value)) return; Guard.NotNull(writer, nameof(writer)); writer.WritePropertyName(name); writer.WriteVector2(value.Value); } protected static void SerializeProperty(Utf8JsonWriter writer, string name, Vector3? value, Vector3? defval = null) { if (!value.HasValue) return; if (defval.HasValue && defval.Value.Equals(value.Value)) return; Guard.NotNull(writer, nameof(writer)); writer.WritePropertyName(name); writer.WriteVector3(value.Value); } protected static void SerializeProperty(Utf8JsonWriter writer, string name, Vector4? value, Vector4? defval = null) { if (!value.HasValue) return; if (defval.HasValue && defval.Value.Equals(value.Value)) return; Guard.NotNull(writer, nameof(writer)); writer.WritePropertyName(name); writer.WriteVector4(value.Value); } protected static void SerializeProperty(Utf8JsonWriter writer, string name, Quaternion? value, Quaternion? defval = null) { if (!value.HasValue) return; if (defval.HasValue && defval.Value.Equals(value.Value)) return; Guard.NotNull(writer, nameof(writer)); writer.WritePropertyName(name); writer.WriteQuaternion(value.Value); } protected static void SerializeProperty(Utf8JsonWriter writer, string name, Matrix4x4? value, Matrix4x4? defval = null) { if (!value.HasValue) return; if (defval.HasValue && defval.Value.Equals(value.Value)) return; Guard.NotNull(writer, nameof(writer)); writer.WritePropertyName(name); writer.WriteMatrix4x4(value.Value); } protected static void SerializePropertyEnumValue(Utf8JsonWriter writer, string name, T? value, T? defval = null) where T : struct { Guard.IsTrue(typeof(T).IsEnum, nameof(T)); if (!value.HasValue) return; if (defval.HasValue && defval.Value.Equals(value)) return; Guard.NotNull(writer, nameof(writer)); writer.WriteNumber(name, (int)(Object)value); } protected static void SerializePropertyEnumSymbol(Utf8JsonWriter writer, string name, T? value, T? defval = null) where T : struct { Guard.IsTrue(typeof(T).IsEnum, nameof(T)); if (!value.HasValue) return; if (defval.HasValue && defval.Value.Equals(value)) return; Guard.NotNull(writer, nameof(writer)); writer.WriteString(name, Enum.GetName(typeof(T), value)); } protected static void SerializePropertyObject(Utf8JsonWriter writer, string name, T value) where T : JsonSerializable { if (value == null) return; Guard.NotNull(writer, nameof(writer)); _SerializeProperty(writer, name, value); } protected static void SerializeProperty(Utf8JsonWriter writer, string name, IReadOnlyList collection, int? minItems = 1) { if (collection == null) return; if (minItems.HasValue && collection.Count < minItems.Value) return; Guard.NotNull(writer, nameof(writer)); writer.WritePropertyName(name); writer.WriteStartArray(); foreach (var item in collection) _SerializeValue(writer, item); writer.WriteEndArray(); } protected static void SerializeProperty(Utf8JsonWriter writer, string name, IReadOnlyDictionary collection) { if (collection == null) return; if (collection.Count < 1) return; Guard.NotNull(writer, nameof(writer)); writer.WritePropertyName(name); writer.WriteStartObject(); foreach (var item in collection) _SerializeProperty(writer, item.Key, item.Value); writer.WriteEndObject(); } private static void _SerializeProperty(Utf8JsonWriter writer, String name, Object value) { Guard.NotNull(writer, nameof(writer)); Guard.NotNull(value, nameof(value)); if (_IsNullOrEmpty(value)) return; if (writer.TryWriteProperty(name, value)) return; writer.WritePropertyName(name); _SerializeValue(writer, value); } private static bool _IsNullOrEmpty(Object value) { if (value == null) return true; if (value is System.Collections.ICollection c && c.Count == 0) return true; return false; } private static void _SerializeValue(Utf8JsonWriter writer, Object value) { Guard.NotNull(writer, nameof(writer)); Guard.NotNull(value, nameof(value)); System.Diagnostics.Debug.Assert(!value.GetType().IsEnum, "gltf schema does not define a typed way of serializing enums"); if (writer.TryWriteValue(value)) return; if (value is System.Text.Json.Nodes.JsonNode jnode) { jnode.WriteTo(writer); return; } if (value is JsonSerializable vgltf) { vgltf.Serialize(writer); return; } if (value is System.Collections.IDictionary dict) { if (dict.Count == 0) return; writer.WriteStartObject(); foreach (var key in dict.Keys) { var val = dict[key]; if (val == null) continue; // if the value is a collection, we need to check if the collection is empty // to prevent writing the key, without writing the value. if (!(val is String || val is JsonSerializable)) { if (val is System.Collections.IList xlist && xlist.Count == 0) continue; if (val is System.Collections.IDictionary xdict && xdict.Count == 0) continue; } _SerializeProperty(writer, key.ToString(), val); } writer.WriteEndObject(); return; } if (value is System.Collections.IList list) { if (list.Count == 0) return; writer.WriteStartArray(); foreach (var item in list) { _SerializeValue(writer, item); } writer.WriteEndArray(); return; } throw new NotImplementedException($"Serialization of {value.GetType().Name} types is not supported."); } #endregion #region deserialization internal void Deserialize(ref Utf8JsonReader reader) { if (reader.TokenType == JSONTOKEN.PropertyName) reader.Read(); if (reader.TokenType == JSONTOKEN.StartObject) { while (reader.Read() && reader.TokenType != JSONTOKEN.EndObject) { if (reader.TokenType == JSONTOKEN.PropertyName) { var key = reader.GetString(); DeserializeProperty(key, ref reader); } else { throw new NotImplementedException(); } } return; } throw new JSONEXCEPTION($"Unexpected token {reader.TokenType}"); } protected static Object DeserializeUnknownObject(ref Utf8JsonReader reader) { if (reader.TokenType == JSONTOKEN.PropertyName) reader.Read(); if (reader.TokenType == JSONTOKEN.StartArray) { var list = new List(); while (reader.Read() && reader.TokenType != JSONTOKEN.EndArray) { list.Add(DeserializeUnknownObject(ref reader)); } return list; } if (reader.TokenType == JSONTOKEN.StartObject) { var dict = new Dictionary(); while (reader.Read() && reader.TokenType != JSONTOKEN.EndObject) { if (reader.TokenType == JSONTOKEN.PropertyName) { var key = reader.GetString(); dict[key] = DeserializeUnknownObject(ref reader); } else { throw new JSONEXCEPTION(); } } return dict; } System.Diagnostics.Debug.Assert(reader.TokenType != JSONTOKEN.None); System.Diagnostics.Debug.Assert(reader.TokenType != JSONTOKEN.EndArray); System.Diagnostics.Debug.Assert(reader.TokenType != JSONTOKEN.EndObject); // System.Diagnostics.Debug.Assert(reader.TokenType != JsonToken.EndConstructor); return reader.GetAnyValue(); } protected abstract void DeserializeProperty(string jsonPropertyName, ref Utf8JsonReader reader); protected static T DeserializePropertyValue< #if !NETSTANDARD [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] #endif T>(ref Utf8JsonReader reader) { _TryCastValue(ref reader, out Object v); System.Diagnostics.Debug.Assert(reader.TokenType != JSONTOKEN.StartArray); System.Diagnostics.Debug.Assert(reader.TokenType != JSONTOKEN.StartObject); System.Diagnostics.Debug.Assert(reader.TokenType != JSONTOKEN.PropertyName); // System.Diagnostics.Debug.Assert(reader.TokenType != JsonToken.StartConstructor); return (T)v; } protected static void DeserializePropertyList< #if !NETSTANDARD [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] #endif T>(ref Utf8JsonReader reader, IList list) { // Guard.NotNull(reader, nameof(reader)); Guard.NotNull(list, nameof(list)); if (reader.TokenType == JSONTOKEN.PropertyName) reader.Read(); if (reader.TokenType != JSONTOKEN.StartArray) throw new JSONEXCEPTION(); if (reader.TokenType == JSONTOKEN.StartObject) throw new JSONEXCEPTION(); while (reader.Read() && reader.TokenType != JSONTOKEN.EndArray) { if (_TryCastValue(ref reader, out Object item)) { list.Add((T)item); } System.Diagnostics.Debug.Assert(reader.TokenType != JSONTOKEN.StartArray); System.Diagnostics.Debug.Assert(reader.TokenType != JSONTOKEN.StartObject); System.Diagnostics.Debug.Assert(reader.TokenType != JSONTOKEN.PropertyName); // System.Diagnostics.Debug.Assert(reader.TokenType != JsonToken.StartConstructor); } if (list.Count == 0) throw new JSONEXCEPTION("Empty array found."); System.Diagnostics.Debug.Assert(reader.TokenType == JSONTOKEN.EndArray); } protected static void DeserializePropertyDictionary< #if !NETSTANDARD [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] #endif T>(ref Utf8JsonReader reader, IDictionary dict) { Guard.NotNull(dict, nameof(dict)); if (reader.TokenType == JSONTOKEN.PropertyName) reader.Read(); if (reader.TokenType == JSONTOKEN.StartArray) throw new JSONEXCEPTION(); if (reader.TokenType != JSONTOKEN.StartObject) throw new JSONEXCEPTION(); while (reader.Read() && reader.TokenType != JSONTOKEN.EndObject) { if (reader.TokenType == JSONTOKEN.PropertyName) { var key = reader.GetString(); if (_TryCastValue(ref reader, out Object val)) { dict[key] = (T)val; } System.Diagnostics.Debug.Assert(reader.TokenType != JSONTOKEN.StartArray); System.Diagnostics.Debug.Assert(reader.TokenType != JSONTOKEN.StartObject); System.Diagnostics.Debug.Assert(reader.TokenType != JSONTOKEN.PropertyName); // System.Diagnostics.Debug.Assert(reader.TokenType != JsonToken.StartConstructor); } } if (dict.Count == 0) throw new JSONEXCEPTION("Empty dictionary found."); } private static bool _TryCastValue < #if !NETSTANDARD [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.NonPublicConstructors)] #endif T>(ref Utf8JsonReader reader, out Object value) { value = null; if (reader.TokenType == JSONTOKEN.EndArray) return false; if (reader.TokenType == JSONTOKEN.EndObject) return false; // if (reader.TokenType == JsonToken.EndConstructor) return false; if (reader.TokenType == JSONTOKEN.PropertyName) reader.Read(); // untangle nullable var vtype = typeof(T); var ntype = Nullable.GetUnderlyingType(vtype); if (ntype != null) vtype = ntype; // known types if (vtype == typeof(String)) { value = reader.AsString(); return true; } if (vtype == typeof(Boolean)) { value = reader.AsBoolean(); return true; } if (vtype == typeof(Int16)) { value = reader.GetInt16(); return true; } if (vtype == typeof(Int32)) { value = reader.GetInt32(); return true; } if (vtype == typeof(Int64)) { value = reader.GetInt64(); return true; } if (vtype == typeof(UInt16)) { value = reader.GetUInt16(); return true; } if (vtype == typeof(UInt32)) { value = reader.GetUInt32(); return true; } if (vtype == typeof(UInt64)) { value = reader.GetUInt64(); return true; } if (vtype == typeof(Single)) { value = reader.GetSingle(); return true; } if (vtype == typeof(Double)) { value = reader.GetDouble(); return true; } if (vtype == typeof(Decimal)) { value = reader.GetDecimal(); return true; } if (vtype.IsEnum) { value = reader.AsEnum(vtype); return true; } if (vtype == typeof(Vector2)) { var l = new List(2); DeserializePropertyList(ref reader, l); value = new Vector2(l[0], l[1]); return true; } if (vtype == typeof(Vector3)) { var l = new List(3); DeserializePropertyList(ref reader, l); value = new Vector3(l[0], l[1], l[2]); return true; } if (vtype == typeof(Vector4)) { var l = new List(4); DeserializePropertyList(ref reader, l); value = new Vector4(l[0], l[1], l[2], l[3]); return true; } if (vtype == typeof(Quaternion)) { var l = new List(4); DeserializePropertyList(ref reader, l); value = new Quaternion(l[0], l[1], l[2], l[3]); return true; } if (vtype == typeof(Matrix4x4)) { var l = new List(16); DeserializePropertyList(ref reader, l); value = new Matrix4x4 ( l[0], l[1], l[2], l[3], l[4], l[5], l[6], l[7], l[8], l[9], l[10], l[11], l[12], l[13], l[14], l[15] ); return true; } if (typeof(System.Text.Json.Nodes.JsonNode).IsAssignableFrom(vtype)) { value = System.Text.Json.Nodes.JsonNode.Parse(ref reader); return true; } if (typeof(JsonSerializable).IsAssignableFrom(vtype)) { // Instance creation on AOT compiled binaries depends on classes defining: // [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicConstructors | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] var item = Activator.CreateInstance(vtype, true) as JsonSerializable; // System.Diagnostics.Debug.Assert(reader.TokenType == JSONTOKEN.StartObject); item.Deserialize(ref reader); // System.Diagnostics.Debug.Assert(reader.TokenType == JSONTOKEN.EndObject); value = item; return true; } if (vtype.IsGenericType) { if (vtype.GetGenericTypeDefinition() == typeof(Dictionary<,>)) { var valType = vtype.GetGenericArguments()[1]; if (valType == typeof(Int32)) { var dict = new Dictionary(); DeserializePropertyDictionary(ref reader, dict); value = dict; return true; } } throw new NotImplementedException($"Can't deserialize {vtype}"); } throw new NotImplementedException($"Can't deserialize {vtype}"); } #endregion } }