浏览代码

Fixing more serialisation problems; fixed Dashboard serialisation

Kenric Nugteren 1 周之前
父节点
当前提交
7ca5781bc0

+ 0 - 1
InABox.Avalonia.Platform.Desktop/AppVersion.Desktop.cs

@@ -1,6 +1,5 @@
 using System.Globalization;
 using InABox.Core;
-using Newtonsoft.Json;
 
 namespace InABox.Avalonia.Platform.Desktop
 {

+ 0 - 3
InABox.Core/Client/Request.cs

@@ -364,7 +364,6 @@ namespace InABox.Clients
         [JsonConstructor]
         public SaveRequest()
         {
-            // Newtonsoft should initialise Item to non-null
         }
 
         public SaveRequest(TEntity item, string auditNote)
@@ -481,7 +480,6 @@ namespace InABox.Clients
         [JsonConstructor]
         public DeleteRequest()
         {
-            // Newtonsoft should initialise Item to non-null.
         }
 
         public DeleteRequest(TEntity item, string auditNote)
@@ -521,7 +519,6 @@ namespace InABox.Clients
         [JsonConstructor]
         public MultiDeleteRequest()
         {
-            // Newtonsoft should initialise Items to non-null.
         }
 
         public MultiDeleteRequest(TEntity[] items, string auditNote)

+ 75 - 54
InABox.Core/Serialization.cs

@@ -19,7 +19,7 @@ namespace InABox.Core
 
     public class SerialisationException : Exception
     {
-        public SerialisationException(string message): base(message) { }
+        public SerialisationException(string message) : base(message) { }
     }
 
     public interface ISerializeBinary
@@ -37,26 +37,26 @@ namespace InABox.Core
         /// <param name="typeInfo"></param>
         public static void WritablePropertiesOnly(JsonTypeInfo typeInfo)
         {
-            if(typeInfo.Kind == JsonTypeInfoKind.Object)
+            if (typeInfo.Kind == JsonTypeInfoKind.Object)
             {
                 var toRemove = typeInfo.Properties.Where(x => x.Set is null).ToList();
-                foreach(var prop in toRemove)
+                foreach (var prop in toRemove)
                 {
                     typeInfo.Properties.Remove(prop);
                 }
             }
         }
 
-        private static JsonConverter[]? _converters;
-
-        private static JsonConverter[] GetConverters()
+        public static List<JsonConverter> DefaultConverters { get; } = new List<JsonConverter>()
         {
-            _converters ??= CoreUtils.Entities.Where(x => typeof(JsonConverter).IsAssignableFrom(x) && x.HasInterface<IGlobalJsonConverter>())
-                .Select(x => Activator.CreateInstance(x) as JsonConverter)
-                .NotNull()
-                .ToArray();
-            return _converters;
-        }
+            new CoreTableJsonConverter(),
+            new FilterJsonConverter(),
+            new ColumnJsonConverter(),
+            new ColumnsJsonConverter(),
+            new SortOrderJsonConverter(),
+            new UserPropertiesJsonConverter(),
+            new TypeJsonConverter(),
+        };
 
         private static JsonSerializerOptions SerializerSettings(bool indented = true, bool populateObject = false)
         {
@@ -67,7 +67,7 @@ namespace InABox.Core
         {
             var settings = new JsonSerializerOptions { };
 
-            foreach (var converter in GetConverters())
+            foreach (var converter in DefaultConverters)
             {
                 settings.Converters.Add(converter);
             }
@@ -275,10 +275,10 @@ namespace InABox.Core
         public JsonTypeInfo? GetTypeInfo(Type type, JsonSerializerOptions options)
         {
             var typeInfo = _jsonTypeInfoResolver?.GetTypeInfo(type, options);
-            if(typeInfo != null && typeInfo.Kind != JsonTypeInfoKind.None)
+            if (typeInfo != null && typeInfo.Kind != JsonTypeInfoKind.None)
             {
                 var defaultCreateObject = typeInfo.CreateObject;
-                if(defaultCreateObject != null)
+                if (defaultCreateObject != null)
                 {
                     typeInfo.CreateObject = () =>
                     {
@@ -334,7 +334,7 @@ namespace InABox.Core
     /// </remarks>
     public class BinarySerializationSettings
     {
-        
+
         /// <summary>
         /// Should the Info() call return RPC and Rest Ports?  This is
         /// To workaround a bug in RPCsockets that crash on large uploads
@@ -343,7 +343,7 @@ namespace InABox.Core
         /// True in all serialization versions >= 1.2
         /// </remarks>
         public bool RPCClientWorkaround { get; set; }
-        
+
         /// <summary>
         /// Should reference types include a flag for nullability? (Adds an extra boolean field for whether the value is null or not).
         /// </summary>
@@ -351,11 +351,11 @@ namespace InABox.Core
         /// True in all serialisation versions >= 1.1.
         /// </remarks>
         public bool IncludeNullables { get; set; }
-        
+
         public string Version { get; set; }
 
         public static BinarySerializationSettings Latest => V1_2;
-        
+
 
 
         public static BinarySerializationSettings V1_0 = new BinarySerializationSettings("1.0")
@@ -363,13 +363,13 @@ namespace InABox.Core
             IncludeNullables = false,
             RPCClientWorkaround = false
         };
-        
+
         public static BinarySerializationSettings V1_1 = new BinarySerializationSettings("1.1")
         {
             IncludeNullables = true,
             RPCClientWorkaround = false
         };
-        
+
         public static BinarySerializationSettings V1_2 = new BinarySerializationSettings("1.2")
         {
             IncludeNullables = true,
@@ -411,8 +411,8 @@ namespace InABox.Core
         }
 
         private static bool MatchType<T1>(Type t) => typeof(T1) == t;
-        private static bool MatchType<T1,T2>(Type t) => (typeof(T1) == t) || (typeof(T2) == t);
-        
+        private static bool MatchType<T1, T2>(Type t) => (typeof(T1) == t) || (typeof(T2) == t);
+
         /// <summary>
         /// Binary serialize a bunch of different types of values. <see cref="WriteBinaryValue(CoreBinaryWriter, Type, object?)"/> and
         /// <see cref="ReadBinaryValue(CoreBinaryReader, Type)"/> are inverses of each other.
@@ -430,7 +430,7 @@ namespace InABox.Core
         public static void WriteBinaryValue(this CoreBinaryWriter writer, Type type, object? value)
         {
             value ??= CoreUtils.GetDefault(type);
-            
+
             if (value == null)
             {
                 if (MatchType<string>(type))
@@ -446,13 +446,13 @@ namespace InABox.Core
                 else
                     writer.Write(0);
             }
-            
+
             else if (MatchType<byte[], object>(type) && value is byte[] bArray)
             {
                 writer.Write(bArray.Length);
                 writer.Write(bArray);
             }
-            
+
             else if (type.IsArray && value is Array array)
             {
                 var elementType = type.GetElementType();
@@ -462,77 +462,77 @@ namespace InABox.Core
                     WriteBinaryValue(writer, elementType, val1);
                 }
             }
-            
+
             else if (type.IsEnum && value is Enum e)
             {
                 var underlyingType = type.GetEnumUnderlyingType();
                 WriteBinaryValue(writer, underlyingType, Convert.ChangeType(e, underlyingType));
             }
-            
+
             else if (MatchType<bool, object>(type) && value is bool b)
             {
                 writer.Write(b);
             }
-            
+
             else if (MatchType<string, object>(type) && value is string str)
                 writer.Write(str);
 
             else if (MatchType<Guid, object>(type) && value is Guid guid)
                 writer.Write(guid);
-            
+
             else if (MatchType<byte, object>(type) && value is byte i8)
                 writer.Write(i8);
-            
+
             else if (MatchType<Int16, object>(type) && value is Int16 i16)
                 writer.Write(i16);
-            
+
             else if (MatchType<Int32, object>(type) && value is Int32 i32)
                 writer.Write(i32);
-            
+
             else if (MatchType<Int64, object>(type) && value is Int64 i64)
                 writer.Write(i64);
-            
+
             else if (MatchType<float, object>(type) && value is float f32)
                 writer.Write(f32);
-            
+
             else if (MatchType<double, object>(type) && value is double f64)
                 writer.Write(f64);
-            
+
             else if (MatchType<DateTime, object>(type) && value is DateTime date)
                 writer.Write(date.Ticks);
-            
+
             else if (MatchType<TimeSpan, object>(type) && value is TimeSpan time)
                 writer.Write(time.Ticks);
-            
+
             else if (MatchType<LoggablePropertyAttribute, object>(type) && value is LoggablePropertyAttribute lpa)
                 writer.Write(lpa.Format ?? string.Empty);
-            
+
             else if (typeof(IPackable).IsAssignableFrom(type) && value is IPackable pack)
             {
                 if (writer.Settings.IncludeNullables)
                     writer.Write(true);
                 pack.Pack(writer);
             }
-            
+
             else if (typeof(ISerializeBinary).IsAssignableFrom(type) && value is ISerializeBinary binary)
             {
                 if (writer.Settings.IncludeNullables)
                     writer.Write(true);
                 binary.SerializeBinary(writer);
             }
-            
+
             else if (Nullable.GetUnderlyingType(type) is Type t)
             {
                 writer.Write(true);
                 writer.WriteBinaryValue(t, value);
             }
-            
+
             else if (value is UserProperty userprop)
                 WriteBinaryValue(writer, userprop.Type, userprop.Value);
-            
+
             else
                 throw new SerialisationException($"Invalid type; Target DataType is {type} and value DataType is {value?.GetType().ToString() ?? "null"}");
-            
+
         }
 
         public static void WriteBinaryValue<T>(this CoreBinaryWriter writer, T value)
@@ -817,7 +817,7 @@ namespace InABox.Core
                 writer.Write(property.Name);
             }
 
-            if(objects != null)
+            if (objects != null)
             {
                 foreach (var obj in objects)
                 {
@@ -841,17 +841,17 @@ namespace InABox.Core
         {
             return ReadObjects<TObject>(reader, typeof(TObject));
         }
-        
+
         public static List<TObject> ReadObjects<TObject>(this CoreBinaryReader reader, Type type) where TObject : BaseObject
         {
             if (!typeof(TObject).IsAssignableFrom(type))
                 throw new SerialisationException($"{type.EntityName()} is not a subclass of {typeof(TObject).EntityName()}");
-            
+
             var objs = new List<TObject>();
             var properties = new List<IProperty>();
 
             var nObjs = reader.ReadInt32();
-            if(nObjs == 0)
+            if (nObjs == 0)
             {
                 return objs;
             }
@@ -861,7 +861,7 @@ namespace InABox.Core
             {
                 var propertyName = reader.ReadString();
                 var property = DatabaseSchema.Property(type, propertyName)
-                    ?? throw new SerialisationException($"Property {propertyName} does not exist on {type.EntityName()}"); 
+                    ?? throw new SerialisationException($"Property {propertyName} does not exist on {type.EntityName()}");
                 properties.Add(property);
             }
 
@@ -884,15 +884,16 @@ namespace InABox.Core
         }
     }
 
-    public interface IGlobalJsonConverter
-    {
-    }
+    public interface IPolymorphicallySerialisable { }
 
+    /// <summary>
+    /// Adds a '$type' property to all classes that implement <see cref="IPolymorphicallySerialisable"/>.
+    /// </summary>
     public class PolymorphicConverter : JsonConverter<object>
     {
         public override bool CanConvert(Type typeToConvert)
         {
-            return typeToConvert.IsInterface || typeToConvert.IsAbstract;
+            return typeof(IPolymorphicallySerialisable).IsAssignableFrom(typeToConvert) && (typeToConvert.IsInterface || typeToConvert.IsAbstract);
         }
 
         public override object? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
@@ -925,7 +926,7 @@ namespace InABox.Core
         }
     }
 
-    public abstract class CustomJsonConverter<T> : JsonConverter<T>, IGlobalJsonConverter
+    public abstract class CustomJsonConverter<T> : JsonConverter<T>
     {
         protected object? ReadJson(ref Utf8JsonReader reader)
         {
@@ -1036,4 +1037,24 @@ namespace InABox.Core
             WriteJson(writer, value);
         }
     }
+
+    public class TypeJsonConverter : CustomJsonConverter<Type>
+    {
+        public override Type? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+        {
+            if(reader.TokenType == JsonTokenType.String)
+            {
+                return Type.GetType(reader.GetString());
+            }
+            else
+            {
+                return null;
+            }
+        }
+
+        public override void Write(Utf8JsonWriter writer, Type value, JsonSerializerOptions options)
+        {
+            writer.WriteStringValue(value.AssemblyQualifiedName);
+        }
+    }
 }

+ 10 - 0
inabox.wpf/Dashboard/DynamicDashboard.cs

@@ -6,6 +6,7 @@ using System.Linq;
 using System.Text;
 using System.Text.Json;
 using System.Text.Json.Serialization;
+using System.Text.Json.Serialization.Metadata;
 using System.Threading.Tasks;
 
 namespace InABox.Wpf.Dashboard;
@@ -23,6 +24,7 @@ public class DynamicDashboard
 
     [JsonPropertyOrder(1)]
     [JsonPropertyName("PresenterProperties")]
+    [JsonInclude]
     private string PresenterProperties
     {
         get => DataPresenter is not null ? Serialization.Serialize(DataPresenter.Properties) : "";
@@ -34,6 +36,7 @@ public class DynamicDashboard
 
     [JsonPropertyOrder(2)]
     [JsonPropertyName("PresenterType")]
+    [JsonInclude]
     private Type? PresenterType
     {
         get => DataPresenter?.GetType();
@@ -90,6 +93,13 @@ public static class DynamicDashboardUtils
     {
         var settings = Serialization.CreateSerializerSettings();
         settings.Converters.Add(new PolymorphicConverter());
+        settings.TypeInfoResolver = new DefaultJsonTypeInfoResolver
+        {
+            Modifiers =
+            {
+                Serialization.WritablePropertiesOnly
+            }
+        };
         return settings;
     }
 

+ 1 - 1
inabox.wpf/Dashboard/DynamicDashboardDataComponent.cs

@@ -17,7 +17,7 @@ public interface IDynamicDashboardTable
     IEnumerable<CoreColumn> GetColumns(DynamicDashboardDataComponent data);
 }
 
-public interface IDynamicDashboardDataQuery : IDynamicDashboardTable
+public interface IDynamicDashboardDataQuery : IDynamicDashboardTable, IPolymorphicallySerialisable
 {
     Type Type { get; }
 

+ 3 - 2
inabox.wpf/Dashboard/Presenters/IDynamicDashboardDataPresenter.cs

@@ -1,4 +1,5 @@
-using Newtonsoft.Json;
+using InABox.Core;
+using Newtonsoft.Json;
 using System;
 using System.Collections.Generic;
 using System.ComponentModel;
@@ -9,7 +10,7 @@ using System.Windows;
 
 namespace InABox.Wpf.Dashboard;
 
-public interface IDynamicDashboardDataPresenter
+public interface IDynamicDashboardDataPresenter : IPolymorphicallySerialisable
 {
     /// <summary>
     /// The desired height for this presenter while previewing in design mode.