Implement JSON difference serialization

This commit is contained in:
2022-11-17 20:37:24 +02:00
parent c4be764f61
commit 6bfb0205fd
3 changed files with 154 additions and 8 deletions

View File

@@ -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
/// <inheritdoc />
public override void WriteJsonDiff(JsonWriter writer, object value, object other, Newtonsoft.Json.JsonSerializer serializer)
{
@@ -170,6 +175,7 @@ namespace FlaxEngine.Json
}
writer.WriteEndObject();
}
#endif
/// <inheritdoc />
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
@@ -232,8 +238,10 @@ namespace FlaxEngine.Json
/// <inheritdoc />
public override bool CanWrite => true;
#if !USE_NETCORE
/// <inheritdoc />
public override bool CanWriteDiff => true;
#endif
}
/// <summary>

View File

@@ -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);
}
/// <summary>
@@ -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
/// <returns>The output json string.</returns>
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<string, JToken> 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();
}
/// <summary>