Browse Source

fixed Filter and Column JSOS Serialisers

Kenric Nugteren 1 week ago
parent
commit
d21aa8aff8

+ 1 - 8
InABox.Core/CoreTable/CoreTableJsonConverter.cs

@@ -4,17 +4,10 @@ using System.Text.Json;
 
 namespace InABox.Core
 {
-
     public class CoreTableJsonConverter : CustomJsonConverter<CoreTable>
     {
         public override void Write(Utf8JsonWriter writer, CoreTable value, JsonSerializerOptions options)
         {
-            if (value == null)
-            {
-                writer.WriteNullValue();
-                return;
-            }
-            
             writer.WriteStartObject();
 
             // Columns
@@ -92,7 +85,7 @@ namespace InABox.Core
                                 while (reader.TokenType != JsonTokenType.EndArray)
                                 {
                                     var col = result.Columns[iCol];
-                                    newrow[col.ColumnName] = CoreUtils.ChangeType(ReadJson(reader), col.DataType);
+                                    newrow[col.ColumnName] = CoreUtils.ChangeType(ReadJson(ref reader), col.DataType);
                                     reader.Read();
                                     iCol++;
                                 }

+ 1 - 1
InABox.Core/Objects/UserProperties.cs

@@ -166,7 +166,7 @@ namespace InABox.Core
             {
                 var key = reader.GetString();
                 reader.Read();
-                var val = ReadJson(reader);
+                var val = ReadJson(ref reader);
                 result[key] = val;
             }
             return result;

+ 4 - 11
InABox.Core/Query/Column.cs

@@ -764,7 +764,6 @@ namespace InABox.Core
 
     public class ColumnJsonConverter : CustomJsonConverter<IColumn>
     {
-        
         public override bool CanConvert(Type objectType)
         {
             if (objectType.IsConstructedGenericType)
@@ -774,7 +773,6 @@ namespace InABox.Core
                 if (ot == tt)
                     return true;
             }
-
             return false;
         }
 
@@ -785,7 +783,7 @@ namespace InABox.Core
 
             string sType = "";
             string sProp = "";
-            while (reader.TokenType != JsonTokenType.EndObject && reader.Read())
+            while (reader.Read() && reader.TokenType != JsonTokenType.EndObject)
             {
                 var tag = reader.GetString();
                 reader.Read();
@@ -800,15 +798,10 @@ namespace InABox.Core
                 var coltype = Type.GetType(sType);
                 if (coltype != null)
                 {
-                    var genericcoltype = typeof(Column<>).MakeGenericType(coltype);
-                    var result = Activator.CreateInstance(genericcoltype) as IColumn;
-                    result.Property = sProp;
-                    return result;
+                    return Activator.CreateInstance(coltype, sProp) as IColumn;
                 }
             }
-
             return null;
-
         }
 
         public override void Write(Utf8JsonWriter writer, IColumn value, JsonSerializerOptions options)
@@ -819,8 +812,8 @@ namespace InABox.Core
                 return;
             }
             writer.WriteStartObject();
-            writer.WriteString("$type",value.GetType().FullName);
-            writer.WriteString("Property",col.Name);
+            writer.WriteString("$type", value.GetType().FullName);
+            writer.WriteString("Property", col.Name);
             writer.WriteEndObject();
         }
     }

+ 89 - 84
InABox.Core/Query/Filter.cs

@@ -4,6 +4,7 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Linq.Expressions;
 using System.Text.Json;
+using System.Text.Json.Serialization;
 using System.Text.RegularExpressions;
 using static InABox.Core.IFilter;
 
@@ -55,6 +56,11 @@ namespace InABox.Core
             SerializedSubquery = subquery;
         }
 
+        [JsonConstructor]
+        private SubQuerySerializationWrapper()
+        {
+        }
+
         public string Type { get; set; }
         public string SerializedSubquery { get; set; }
     }
@@ -279,7 +285,7 @@ namespace InABox.Core
         /// <summary>
         /// List of filters to apply to the main filter with boolean operator AND. The <see cref="Ands"/> take precedence over the <see cref="Ors"/>.
         /// </summary>
-        IList<IFilter> Ands { get; }
+        IEnumerable<IFilter> Ands { get; }
         /// <summary>
         /// List of filters to apply to the main filter with boolean operator OR. The <see cref="Ands"/> take precedence over the <see cref="Ors"/>.
         /// </summary>
@@ -494,7 +500,7 @@ namespace InABox.Core
 
         public bool IsNot { get; set; }
 
-        IList<IFilter> IFilter.Ands => Ands.OfType<IFilter>().ToList();
+        IEnumerable<IFilter> IFilter.Ands => Ands;
 
         IEnumerable<IFilter> IFilter.Ors => Ors;
 
@@ -2120,14 +2126,18 @@ namespace InABox.Core
 
         public override IFilter? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
         {
-            
             if (reader.TokenType == JsonTokenType.Null)
                 return null;
-            
-            IFilter? _result = null;
-            Type? _type;
-            
-            while (reader.TokenType != JsonTokenType.EndObject && reader.Read())
+
+            Type? _type = null;
+            string? propertyName = null;
+            Operator op = default;
+            bool isNot = false;
+            object? value = null;
+            List<IFilter> ands = new List<IFilter>();
+            List<IFilter> ors = new List<IFilter>();
+
+            while (reader.Read() && reader.TokenType != JsonTokenType.EndObject)
             {
                 var tag = reader.GetString();
                 reader.Read();
@@ -2137,111 +2147,105 @@ namespace InABox.Core
                     if (!string.IsNullOrWhiteSpace(sType))
                     {
                         _type = Type.GetType(sType);
-                        if (_type != null)
-                        {
-                            var gType = typeof(Filter<>).MakeGenericType(_type);
-                            _result = Activator.CreateInstance(gType) as IFilter;
-                        }
                     }
                 }
-                else if (string.Equals(tag, "Expression") && _result != null)
-                    _result.Property = reader.GetString() ?? "";
-                else if (string.Equals(tag, "Operator") && _result != null)
-                    _result.Operator = Enum.Parse<Operator>(reader.GetString());
-                else if (string.Equals(tag, "IsNot") && _result != null)
-                    _result.IsNot = reader.GetBoolean();
-                else if (string.Equals(tag, "FilterConstant") && _result != null)
-                    _result.Value = Enum.Parse<FilterConstant>(reader.GetString());
-                else if (string.Equals(tag, "CustomValue") && _result != null)
-                    _result.Value = new CustomFilterValue(Convert.FromBase64String(reader.GetString()));
-                else if (string.Equals(tag, "Value") && _result != null)
+                else if (string.Equals(tag, "Expression"))
+                    propertyName = reader.GetString() ?? "";
+                else if (string.Equals(tag, "Operator"))
+                    op = Enum.Parse<Operator>(reader.GetString());
+                else if (string.Equals(tag, "IsNot"))
+                    isNot = reader.GetBoolean();
+                else if (string.Equals(tag, "FilterConstant"))
+                    value = Enum.Parse<FilterConstant>(reader.GetString());
+                else if (string.Equals(tag, "CustomValue"))
+                    value = new CustomFilterValue(Convert.FromBase64String(reader.GetString()));
+                else if (string.Equals(tag, "Value"))
                 {
-                    if (reader.TokenType == JsonTokenType.StartArray)
-                    {
-                        List<object> values = new List<object>();
-                        while (reader.TokenType != JsonTokenType.EndArray)
-                        {
-                            reader.Read();
-                            var value = ReadJson(reader);
-                            values.Add(value);
-                        }
-
-                        _result.Value = values.ToArray();
-                    }
-                    else
-                        _result.Value = ReadJson(reader);
-
+                    value = ReadJson(ref reader);
                 }
-                    
-                
-                else if (string.Equals(tag, "Ands") && _result != null)
+                else if (string.Equals(tag, "Ands"))
                 {
-                    reader.Read();
-                    while (reader.TokenType != JsonTokenType.EndObject)
+                    while (reader.Read() && reader.TokenType != JsonTokenType.EndArray)
                     {
                         var and = Read(ref reader, typeof(IFilter), options);
                         if (and != null)
-                            _result.Ands.Add(and);
+                            ands.Add(and);
                     }
                 }
-                
-                else if (string.Equals(tag, "Ors") && _result != null)
+                else if (string.Equals(tag, "Ors"))
                 {
-                    reader.Read();
-                    while (reader.TokenType != JsonTokenType.EndObject)
+                    while (reader.Read() && reader.TokenType != JsonTokenType.EndArray)
                     {
-                        var or = Read(ref reader, typeof(ISortOrder), options);
+                        var or = Read(ref reader, typeof(IFilter), options);
                         if (or != null)
-                            _result.Ands.Add(or);
+                            ors.Add(or);
                     }
                 }
-
-                
             }
-            return _result;
             
+            if (_type is null)
+            {
+                return null;
+            }
+
+            var result = (Activator.CreateInstance(_type) as IFilter)!;
+
+            result.Property = propertyName ?? "";
+            result.Operator = op;
+            result.IsNot = isNot;
+            if(op == Operator.InQuery || op == Operator.NotInQuery)
+            {
+                var wrapper = Serialization.Deserialize<SubQuerySerializationWrapper>(value as string);
+                if(wrapper != null)
+                {
+                    result.Value = Serialization.Deserialize(typeof(SubQuery<>).MakeGenericType(CoreUtils.GetEntity(wrapper.Type)), wrapper.SerializedSubquery);
+                }
+            }
+            else if(op == Operator.InList || op == Operator.NotInList)
+            {
+                result.Value = CoreUtils.ChangeType(value, result.Type.MakeArrayType());
+            }
+            else
+            {
+                result.Value = CoreUtils.ChangeType(value, result.Type);
+            }
+
+            foreach(var or in ors)
+            {
+                result.Or(or);
+            }
+            foreach(var and in ands)
+            {
+                result.And(and);
+            }
+
+            return result;
         }
 
         public override void Write(Utf8JsonWriter writer, IFilter filter, JsonSerializerOptions options)
         {
-            
             var prop = filter.Property;
 
             var op = filter.Operator;
             var val = filter.Value;
 
-            var ands = filter.Ands.ToList();
-            var ors = filter.Ors.ToList();
-
-            //var table = value as DataTable;
-            //// Save Columns
             writer.WriteStartObject();
             
-            writer.WriteString("$type",filter.GetType().FullName);
-
-            writer.WriteString("Expression",prop);
-
-            writer.WriteString("Operator",op.ToString());
-
-            writer.WriteBoolean("IsNot",filter.IsNot);
+            // For the above reader to work, the $type property *must* be serialised first.
+            writer.WriteString("$type", filter.GetType().FullName);
+            writer.WriteString("Expression", prop);
+            writer.WriteString("Operator", op.ToString());
+            writer.WriteBoolean("IsNot", filter.IsNot);
 
             if (val is FilterConstant fcVal)
-                writer.WriteString("FilterConstant",fcVal.ToString());
+                writer.WriteString("FilterConstant", fcVal.ToString());
             else if(val is CustomFilterValue cVal)
-                writer.WriteBase64String("CustomValue",cVal.Data);
+                writer.WriteBase64String("CustomValue", cVal.Data);
             else
             {
                 writer.WritePropertyName("Value");
                 var valType = val == null ? filter.Type : val.GetType();
-                if (valType.IsArray)
-                {
-                    writer.WriteStartArray();
-                    var arr = (val as IEnumerable)!;
-                    foreach (var v in arr)
-                        WriteJson(writer, v);
-                    writer.WriteEndArray();
-                }
-                else if (valType.IsConstructedGenericType && valType.GetGenericTypeDefinition() == typeof(SubQuery<>))
+                if (valType.IsConstructedGenericType && valType.GetGenericTypeDefinition() == typeof(SubQuery<>))
                 {
                     var serialized = Serialization.Serialize(val);
                     var wrapper = new SubQuerySerializationWrapper(valType.GenericTypeArguments[0].EntityName(), serialized);
@@ -2251,24 +2255,25 @@ namespace InABox.Core
                     writer.WriteStringValue(str);
                 }
                 else
-                    WriteJson(writer,val);
-                
+                {
+                    WriteJson(writer, val);
+                }
             }
 
-            if (ands?.Any() == true)
+            if (filter.Ands.Any())
             {
                 writer.WritePropertyName("Ands");
                 writer.WriteStartArray();
-                foreach (var and in ands)
+                foreach (var and in filter.Ands)
                     Write(writer, and, options);
                 writer.WriteEndArray();
             }
 
-            if (ors?.Any() == true)
+            if (filter.Ors.Any())
             {
                 writer.WritePropertyName("Ors");
                 writer.WriteStartArray();
-                foreach (var or in ors)
+                foreach (var or in filter.Ors)
                     Write(writer, or, options);
                 writer.WriteEndArray();
             }

+ 45 - 8
InABox.Core/Serialization.cs

@@ -868,7 +868,7 @@ namespace InABox.Core
 
     public abstract class CustomJsonConverter<T> : JsonConverter<T>
     {
-        protected object? ReadJson(Utf8JsonReader reader)
+        protected object? ReadJson(ref Utf8JsonReader reader)
         {
             switch (reader.TokenType)
             {
@@ -886,6 +886,15 @@ namespace InABox.Core
                     return false;
                 case JsonTokenType.Null:
                     return null;
+                case JsonTokenType.StartArray:
+                    var values = new List<object?>();
+                    reader.Read();
+                    while(reader.TokenType != JsonTokenType.EndArray)
+                    {
+                        values.Add(ReadJson(ref reader));
+                        reader.Read();
+                    }
+                    return values;
                 default:
                     return null;
             }
@@ -895,7 +904,10 @@ namespace InABox.Core
         /// Write a value as a JSON object; note that some data types, like
         /// <see cref="Guid"/> and <see cref="DateTime"/> will be encoded as
         /// strings, and therefore will be returned as strings when read by
-        /// <see cref="ReadJson(Utf8JsonReader)"/>.
+        /// <see cref="ReadJson(Utf8JsonReader)"/>. However, all types that
+        /// this can write should be able to be retrieved by calling <see
+        /// cref="CoreUtils.ChangeType(object?, Type)"/> on the resultant
+        /// value.
         /// </summary>
         protected void WriteJson(Utf8JsonWriter writer, object? value)
         {
@@ -905,16 +917,41 @@ namespace InABox.Core
                 writer.WriteStringValue(sVal);
             else if (value is bool bVal)
                 writer.WriteBooleanValue(bVal);
-            else if (value is int iVal)
-                writer.WriteNumberValue(iVal);
-            else if (value is long lVal)
-                writer.WriteNumberValue(lVal);
+            else if (value is byte b)
+                writer.WriteNumberValue(b);
+            else if (value is short i16)
+                writer.WriteNumberValue(i16);
+            else if (value is int i32)
+                writer.WriteNumberValue(i32);
+            else if (value is long i64)
+                writer.WriteNumberValue(i64);
+            else if (value is float f)
+                writer.WriteNumberValue(f);
             else if (value is double dVal)
                 writer.WriteNumberValue(dVal);
             else if (value is DateTime dtVal)
                 writer.WriteStringValue(dtVal.ToString());
+            else if (value is TimeSpan tsVal)
+                writer.WriteStringValue(tsVal.ToString());
             else if (value is Guid guid)
                 writer.WriteStringValue(guid.ToString());
+            else if(value is byte[] arr)
+            {
+                writer.WriteBase64StringValue(arr);
+            }
+            else if(value is Array array)
+            {
+                writer.WriteStartArray();
+                foreach(var val1 in array)
+                {
+                    WriteJson(writer, val1);
+                }
+                writer.WriteEndArray();
+            }
+            else if(value is Enum e)
+            {
+                WriteJson(writer, Convert.ChangeType(e, e.GetType().GetEnumUnderlyingType()));
+            }
             else
             {
                 Logger.Send(LogType.Error, "", $"Could not write object of type {value.GetType()} as JSON");
@@ -961,13 +998,13 @@ namespace InABox.Core
                             string? name = reader.GetString();
                             reader.Read();
                             if (!string.IsNullOrWhiteSpace(name))
-                                obj.OriginalValues[name] = ReadJson(reader);
+                                obj.OriginalValues[name] = ReadJson(ref reader);
 
                         }
                     }
                     else if (DatabaseSchema.Property(typeToConvert, propertyName) is IProperty prop)
                     {
-                        var value = ReadJson(reader);
+                        var value = ReadJson(ref reader);
                         prop.Setter()(obj, value);
                     }
                 }