| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599 |
- 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
- {
- /// <summary>
- /// Represents the base class of a serializable glTF schema2 object.
- /// Inherited by <see cref="Schema2.ExtraProperties"/>.
- /// </summary>
- 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<T>(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<T>(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<T>(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<T>(Utf8JsonWriter writer, string name, IReadOnlyList<T> 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<T>(Utf8JsonWriter writer, string name, IReadOnlyDictionary<String, T> 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<Object>();
- while (reader.Read() && reader.TokenType != JSONTOKEN.EndArray)
- {
- list.Add(DeserializeUnknownObject(ref reader));
- }
- return list;
- }
- if (reader.TokenType == JSONTOKEN.StartObject)
- {
- var dict = new Dictionary<String, Object>();
- 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<T>(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<T> 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<T>(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<string, T> 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<T>(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<float>(2);
- DeserializePropertyList<float>(ref reader, l);
- value = new Vector2(l[0], l[1]);
- return true;
- }
- if (vtype == typeof(Vector3))
- {
- var l = new List<float>(3);
- DeserializePropertyList<float>(ref reader, l);
- value = new Vector3(l[0], l[1], l[2]);
- return true;
- }
- if (vtype == typeof(Vector4))
- {
- var l = new List<float>(4);
- DeserializePropertyList<float>(ref reader, l);
- value = new Vector4(l[0], l[1], l[2], l[3]);
- return true;
- }
- if (vtype == typeof(Quaternion))
- {
- var l = new List<float>(4);
- DeserializePropertyList<float>(ref reader, l);
- value = new Quaternion(l[0], l[1], l[2], l[3]);
- return true;
- }
- if (vtype == typeof(Matrix4x4))
- {
- var l = new List<float>(16);
- DeserializePropertyList<float>(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<string, Int32>();
- DeserializePropertyDictionary(ref reader, dict);
- value = dict;
- return true;
- }
- }
- throw new NotImplementedException($"Can't deserialize {vtype}");
- }
- throw new NotImplementedException($"Can't deserialize {vtype}");
- }
- #endregion
- }
- }
|