Skip to content

Commit

Permalink
Merge pull request #83 from asulwer/fix/empty-array-tests
Browse files Browse the repository at this point in the history
empty array tests
  • Loading branch information
JasonBoggsAtHMSdotCom authored Nov 13, 2024
2 parents 71b0adc + 75d9bfb commit d5f9504
Show file tree
Hide file tree
Showing 3 changed files with 232 additions and 1 deletion.
145 changes: 145 additions & 0 deletions src/RulesEngine/Serialization/ObjectAsPrimitiveConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
// https://github.com/asulwer/RulesEngine/issues/75
// https://stackoverflow.com/questions/65972825/c-sharp-deserializing-nested-json-to-nested-dictionarystring-object

using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Text.Json.Serialization;
using System.Text.Json;

namespace RulesEngine.Serialization
{
public class ObjectAsPrimitiveConverter : JsonConverter<object>
{

#region Fields

private readonly FloatFormat floatFormat;
private readonly UnknownNumberFormat unknownNumberFormat;
private readonly ObjectFormat objectFormat;

#endregion

#region Constructors

public ObjectAsPrimitiveConverter() : this(FloatFormat.Double, UnknownNumberFormat.Error, ObjectFormat.Expando) { }
public ObjectAsPrimitiveConverter(FloatFormat floatFormat, UnknownNumberFormat unknownNumberFormat, ObjectFormat objectFormat)
{
this.floatFormat = floatFormat;
this.unknownNumberFormat = unknownNumberFormat;
this.objectFormat = objectFormat;
}

#endregion

#region Methods

public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options)
{
if (value.GetType() == typeof(object))
{
writer.WriteStartObject();
writer.WriteEndObject();
}
else
{
JsonSerializer.Serialize(writer, value, value.GetType(), options);
}
}

public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
switch (reader.TokenType)
{
case JsonTokenType.Null:
return null;
case JsonTokenType.False:
return false;
case JsonTokenType.True:
return true;
case JsonTokenType.String:
return reader.GetString();
case JsonTokenType.Number:
{
if (reader.TryGetInt32(out var i))
return i;
if (reader.TryGetInt64(out var l))
return l;
// BigInteger could be added here.
if (floatFormat == FloatFormat.Decimal && reader.TryGetDecimal(out var m))
return m;
else if (floatFormat == FloatFormat.Double && reader.TryGetDouble(out var d))
return d;
using var doc = JsonDocument.ParseValue(ref reader);
if (unknownNumberFormat == UnknownNumberFormat.JsonElement)
return doc.RootElement.Clone();
throw new JsonException(string.Format("Cannot parse number {0}", doc.RootElement.ToString()));
}
case JsonTokenType.StartArray:
{
var list = new List<object>();
while (reader.Read())
{
switch (reader.TokenType)
{
default:
list.Add(Read(ref reader, typeof(object), options));
break;
case JsonTokenType.EndArray:
return list;
}
}
throw new JsonException();
}
case JsonTokenType.StartObject:
var dict = CreateDictionary();
while (reader.Read())
{
switch (reader.TokenType)
{
case JsonTokenType.EndObject:
return dict;
case JsonTokenType.PropertyName:
var key = reader.GetString();
reader.Read();
dict.Add(key, Read(ref reader, typeof(object), options));
break;
default:
throw new JsonException();
}
}
throw new JsonException();
default:
throw new JsonException(string.Format("Unknown token {0}", reader.TokenType));
}
}

protected virtual IDictionary<string, object> CreateDictionary()
{
return objectFormat == ObjectFormat.Expando ? new ExpandoObject() : (IDictionary<string, object>)new Dictionary<string, object>();
}

#endregion

}

public enum FloatFormat
{
Double,
Decimal,
}

public enum UnknownNumberFormat
{
Error,
JsonElement,
}

public enum ObjectFormat
{
Expando,
Dictionary,
}
}
63 changes: 63 additions & 0 deletions test/RulesEngine.UnitTest/EmptyArrayTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using RulesEngine.Models;
using RulesEngine.Serialization;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Dynamic;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Xunit;

namespace RulesEngine.UnitTest
{
[ExcludeFromCodeCoverage]
public class EmptyArrayTest
{
[Fact]
private async Task EmptyArray_ReturnsExepectedResults()
{
Workflow[] workflow = [
new() {
WorkflowName = "Workflow",
Rules = [
new() {
RuleName = "empty array",
Expression = "not things.Any(a == 3)",
RuleExpressionType = RuleExpressionType.LambdaExpression
}
]
}
];
var rulesEngine = new RulesEngine(workflow, new() {
IsExpressionCaseSensitive = false,
CustomTypes = [
typeof(IEnumerable)
]
});

var options = new JsonSerializerOptions {
Converters = { new ObjectAsPrimitiveConverter() },
WriteIndented = true,
};
string payload = @"{""things"":[]}";

var target = System.Text.Json.JsonSerializer.Deserialize<ExpandoObject>(payload, options);

CancellationTokenSource cancellationTokenSource = new();
List<RuleResultTree> results = await rulesEngine.ExecuteAllRulesAsync("Workflow", cancellationTokenSource.Token, target);

Assert.Single(results);

var result = results[0];

Assert.Equal(result.ExceptionMessage, string.Empty);
Assert.True(result.IsSuccess);

}

}
}
25 changes: 24 additions & 1 deletion test/RulesEngine.UnitTest/UtilsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// Licensed under the MIT License.

using Newtonsoft.Json.Linq;
using RulesEngine.HelperFunctions;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
Expand All @@ -11,6 +10,10 @@
using System.Text.Json;
using Xunit;

using RulesEngine.HelperFunctions;
using RulesEngine.Serialization;
using RulesEngine.Models;

namespace RulesEngine.UnitTest
{
[ExcludeFromCodeCoverage]
Expand Down Expand Up @@ -161,5 +164,25 @@ public void CreateObject_WithJsonElementArray_ShouldConvertToExpandoObject()
Assert.Equal(95.7, scores[1]);
Assert.Equal(85L, scores[2]);
}

[Fact]
public void CreateObject_WithEmptyArray_ShouldConvertToListDictionaryImplicitObject()
{
var jsonString = @"{""things"":[]}";

var options = new JsonSerializerOptions {
Converters = { new ObjectAsPrimitiveConverter() },
WriteIndented = true,
};

var document = JsonSerializer.Deserialize<ExpandoObject>(jsonString, options);

var type = Utils.CreateAbstractClassType(document);
dynamic result = Utils.CreateObject(type, document);

Assert.IsType<List<Dictionary<string, ImplicitObject>>>(result.things);
Assert.Empty((List<Dictionary<string, ImplicitObject>>)result.things);

}
}
}

0 comments on commit d5f9504

Please sign in to comment.