diff --git a/Source/Engine/Serialization/JsonConverters.cs b/Source/Engine/Serialization/JsonConverters.cs index 4de09cb5d..c194b766e 100644 --- a/Source/Engine/Serialization/JsonConverters.cs +++ b/Source/Engine/Serialization/JsonConverters.cs @@ -37,7 +37,11 @@ namespace FlaxEngine.Json { // Skip serialization as reference id for the root object serialization (eg. Script) var cache = JsonSerializer.Current.Value; +#if !USE_NETCORE if (cache != null && cache.IsDuringSerialization && cache.SerializerWriter.SerializeStackSize == 0) +#else + if (cache != null && cache.IsDuringSerialization) +#endif { return false; } @@ -142,6 +146,7 @@ namespace FlaxEngine.Json writer.WriteEndObject(); } +#if !USE_NETCORE /// public override void WriteJsonDiff(JsonWriter writer, object value, object other, Newtonsoft.Json.JsonSerializer serializer) { @@ -170,6 +175,7 @@ namespace FlaxEngine.Json } writer.WriteEndObject(); } +#endif /// public override object ReadJson(JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer) @@ -232,8 +238,10 @@ namespace FlaxEngine.Json /// public override bool CanWrite => true; +#if !USE_NETCORE /// public override bool CanWriteDiff => true; +#endif } /// diff --git a/Source/Engine/Serialization/JsonSerializer.cs b/Source/Engine/Serialization/JsonSerializer.cs index 29a5951f9..b22f92927 100644 --- a/Source/Engine/Serialization/JsonSerializer.cs +++ b/Source/Engine/Serialization/JsonSerializer.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. using System; +using System.Collections.Generic; using System.Globalization; using System.IO; using System.Runtime.CompilerServices; @@ -11,7 +12,7 @@ using FlaxEngine.Json.JsonCustomSerializers; using FlaxEngine.Utilities; using Newtonsoft.Json; using Newtonsoft.Json.Converters; -using Newtonsoft.Json.Serialization; +using Newtonsoft.Json.Linq; namespace FlaxEngine.Json { @@ -23,7 +24,11 @@ namespace FlaxEngine.Json public StringBuilder StringBuilder; public StringWriter StringWriter; public JsonTextWriter JsonWriter; +#if !USE_NETCORE public JsonSerializerInternalWriter SerializerWriter; +#else + public /*JsonSerializerInternalWriter*/ object SerializerWriter; +#endif public UnmanagedMemoryStream MemoryStream; public StreamReader Reader; public bool IsDuringSerialization; @@ -32,9 +37,18 @@ namespace FlaxEngine.Json { JsonSerializer = Newtonsoft.Json.JsonSerializer.CreateDefault(settings); JsonSerializer.Formatting = Formatting.Indented; +#if USE_NETCORE + Type jsonSerializerInternalWriterType = + typeof(Newtonsoft.Json.Serialization.IValueProvider).Assembly.GetType( + "Newtonsoft.Json.Serialization.JsonSerializerInternalWriter"); + System.Reflection.ConstructorInfo ctor = jsonSerializerInternalWriterType.GetConstructors + (System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public)[0]; + SerializerWriter = ctor.Invoke(new object[] { JsonSerializer }); +#else + SerializerWriter = new JsonSerializerInternalWriter(JsonSerializer); +#endif StringBuilder = new StringBuilder(256); StringWriter = new StringWriter(StringBuilder, CultureInfo.InvariantCulture); - SerializerWriter = new JsonSerializerInternalWriter(JsonSerializer); MemoryStream = new UnmanagedMemoryStream((byte*)0, 0); Reader = new StreamReader(MemoryStream, Encoding.UTF8, false); JsonWriter = new JsonTextWriter(StringWriter) @@ -107,7 +121,72 @@ namespace FlaxEngine.Json return sceneObjA.PrefabObjectID == sceneObjB.PrefabObjectID; }*/ - return Newtonsoft.Json.Utilities.MiscellaneousUtils.DefaultValueEquals(objA, objB); + // Based on Newtonsoft.Json MiscellaneousUtils-class ValueEquals-method + bool DefaultValueEquals(object objA_, object objB_) + { + bool IsInteger(object value) + { + var type = value.GetType(); + return type == typeof(SByte) || + type == typeof(Byte) || + type == typeof(Int16) || + type == typeof(UInt16) || + type == typeof(Int32) || + type == typeof(UInt32) || + type == typeof(Int64) || + type == typeof(SByte) || + type == typeof(UInt64); + } + + if (objA_ == objB_) + { + return true; + } + if (objA_ == null || objB_ == null) + { + return false; + } + + // comparing an Int32 and Int64 both of the same value returns false + // make types the same then compare + if (objA_.GetType() != objB_.GetType()) + { + if (IsInteger(objA_) && IsInteger(objB_)) + { + return Convert.ToDecimal(objA_, CultureInfo.CurrentCulture).Equals(Convert.ToDecimal(objB_, CultureInfo.CurrentCulture)); + } + else if ((objA_ is double || objA_ is float || objA_ is decimal) && (objB_ is double || objB_ is float || objB_ is decimal)) + { + return Mathd.NearEqual(Convert.ToDouble(objA_, CultureInfo.CurrentCulture), Convert.ToDouble(objB_, CultureInfo.CurrentCulture)); + } + else + { + return false; + } + } + + // Diff on collections + if (objA_ is System.Collections.IList aList && objB_ is System.Collections.IList bList) + { + if (aList.Count != bList.Count) + return false; + } + if (objA_ is System.Collections.IEnumerable aEnumerable && objB_ is System.Collections.IEnumerable bEnumerable) + { + var aEnumerator = aEnumerable.GetEnumerator(); + var bEnumerator = bEnumerable.GetEnumerator(); + while (aEnumerator.MoveNext()) + { + if (!bEnumerator.MoveNext() || !ValueEquals(aEnumerator.Current, bEnumerator.Current)) + return false; + } + return !bEnumerator.MoveNext(); + } + + return objA_.Equals(objB_); + } + + return /*Newtonsoft.Json.Utilities.MiscellaneousUtils.*/DefaultValueEquals(objA, objB); } /// @@ -124,7 +203,17 @@ namespace FlaxEngine.Json cache.StringBuilder.Clear(); cache.IsDuringSerialization = true; +#if !USE_NETCORE cache.SerializerWriter.Serialize(cache.JsonWriter, obj, type); +#else + Type jsonSerializerInternalWriterType = + typeof(Newtonsoft.Json.Serialization.IValueProvider).Assembly.GetType( + "Newtonsoft.Json.Serialization.JsonSerializerInternalWriter"); + + System.Reflection.MethodInfo Serialize = jsonSerializerInternalWriterType.GetMethod("Serialize", + System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public); + Serialize.Invoke(cache.SerializerWriter, new object[] { cache.JsonWriter, obj, type }); +#endif return cache.StringBuilder.ToString(); } @@ -143,7 +232,17 @@ namespace FlaxEngine.Json cache.StringBuilder.Clear(); cache.IsDuringSerialization = true; +#if !USE_NETCORE cache.SerializerWriter.Serialize(cache.JsonWriter, obj, type); +#else + Type jsonSerializerInternalWriterType = + typeof(Newtonsoft.Json.Serialization.IValueProvider).Assembly.GetType( + "Newtonsoft.Json.Serialization.JsonSerializerInternalWriter"); + + System.Reflection.MethodInfo Serialize = jsonSerializerInternalWriterType.GetMethod("Serialize", + System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public); + Serialize.Invoke(cache.SerializerWriter, new object[] { cache.JsonWriter, obj, type }); +#endif return cache.StringBuilder.ToString(); } @@ -157,15 +256,25 @@ namespace FlaxEngine.Json /// The output json string. public static string SerializeDiff(object obj, object other, bool isManagedOnly = false) { - Type type = obj.GetType(); var cache = isManagedOnly ? CacheManagedOnly.Value : Cache.Value; Current.Value = cache; cache.StringBuilder.Clear(); cache.IsDuringSerialization = true; - cache.SerializerWriter.SerializeDiff(cache.JsonWriter, obj, type, other); - return cache.StringBuilder.ToString(); + JObject jObj = JObject.FromObject(obj, cache.JsonSerializer); + JObject jOther = JObject.FromObject(other, cache.JsonSerializer); + JObject diff = new JObject(); + foreach (KeyValuePair prop in jObj) + { + JProperty otherProp = jOther.Property(prop.Key); + if (JToken.DeepEquals(prop.Value, otherProp.Value)) + continue; + + diff.Add(prop.Key, prop.Value); + } + + return diff.ToString(); } /// diff --git a/Source/Engine/UI/UIControl.cs b/Source/Engine/UI/UIControl.cs index bd1d2e795..9b32774ff 100644 --- a/Source/Engine/UI/UIControl.cs +++ b/Source/Engine/UI/UIControl.cs @@ -6,7 +6,9 @@ using System.IO; using System.Text; using FlaxEngine.GUI; using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using Newtonsoft.Json.Serialization; +using System.Collections.Generic; namespace FlaxEngine { @@ -340,10 +342,22 @@ namespace FlaxEngine jsonWriter.StringEscapeHandling = jsonSerializer.StringEscapeHandling; jsonWriter.Culture = jsonSerializer.Culture; jsonWriter.DateFormatString = jsonSerializer.DateFormatString; - +#if !USE_NETCORE JsonSerializerInternalWriter serializerWriter = new JsonSerializerInternalWriter(jsonSerializer); serializerWriter.Serialize(jsonWriter, _control, type); +#else + Type jsonSerializerInternalWriterType = + typeof(Newtonsoft.Json.Serialization.IValueProvider).Assembly.GetType( + "Newtonsoft.Json.Serialization.JsonSerializerInternalWriter"); + System.Reflection.ConstructorInfo ctor = jsonSerializerInternalWriterType.GetConstructors + (System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public)[0]; + object serializerWriter = ctor.Invoke(new object[] { jsonSerializer }); + + System.Reflection.MethodInfo Serialize = jsonSerializerInternalWriterType.GetMethod("Serialize", + System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public); + Serialize.Invoke(serializerWriter, new object[] { jsonWriter, _control, type }); +#endif } controlType = type.FullName; @@ -380,10 +394,25 @@ namespace FlaxEngine jsonWriter.StringEscapeHandling = jsonSerializer.StringEscapeHandling; jsonWriter.Culture = jsonSerializer.Culture; jsonWriter.DateFormatString = jsonSerializer.DateFormatString; - +#if !USE_NETCORE JsonSerializerInternalWriter serializerWriter = new JsonSerializerInternalWriter(jsonSerializer); serializerWriter.SerializeDiff(jsonWriter, _control, type, other._control); +#else + JObject jObj = JObject.FromObject(_control, jsonSerializer); + JObject jOther = JObject.FromObject(other._control, jsonSerializer); + JObject diff = new JObject(); + foreach (KeyValuePair prop in jObj) + { + JProperty otherProp = jOther.Property(prop.Key); + if (JToken.DeepEquals(prop.Value, otherProp.Value)) + continue; + + diff.Add(prop.Key, prop.Value); + } + + diff.WriteTo(jsonWriter); +#endif } controlType = string.Empty;