Skip to content

Commit

Permalink
Merge pull request #82 from asulwer/empty-arrays-2
Browse files Browse the repository at this point in the history
  • Loading branch information
asulwer authored Nov 13, 2024
2 parents d5f9504 + d4d7e81 commit 10a6799
Show file tree
Hide file tree
Showing 3 changed files with 216 additions and 1 deletion.
84 changes: 84 additions & 0 deletions demo/DemoApp/JsonSerializerConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// 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 RulesEngine.HelperFunctions;
using RulesEngine.Models;
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using static RulesEngine.Extensions.ListofRuleResultTreeExtension;

namespace DemoApp
{
public class JsonSerializerConverter : IDemo
{
public async Task Run(CancellationToken cancellationToken = default)
{
Console.WriteLine($"Running {nameof(JsonSerializerConverter)}....");

string payload = "{\"prop\":\"someString\",\"someInt\":3,\"nest\":{\"code\":\"bar\",\"foo\":true},\"emptyArray\":[],\"populatedArray\":[{\"a\":2,\"subArray\":[{\"c\":4}]}]}";

var options = new JsonSerializerOptions {
Converters = { new ObjectAsPrimitiveConverter(floatFormat: FloatFormat.Double, unknownNumberFormat: UnknownNumberFormat.Error, objectFormat: ObjectFormat.Expando) },
WriteIndented = true,
};
var target = JsonSerializer.Deserialize<ExpandoObject>(payload, options)!;

Workflow[] workflow = [
new() {
WorkflowName = "Workflow",
Rules = [
new() {
RuleName = "someInt check",
Expression = "someInt > 1",
RuleExpressionType = RuleExpressionType.LambdaExpression
},
new() {
RuleName = "empty array",
Expression = "not emptyArray.Any(a == 'a')",
RuleExpressionType = RuleExpressionType.LambdaExpression
},
new() {
RuleName = "populatedArray with subArray not match",
Expression = "populatedArray.Any(subArray.Any(c == 4))",
RuleExpressionType = RuleExpressionType.LambdaExpression
},
new() {
RuleName = "check prop",
Expression = "prop = \"someString\"",
RuleExpressionType = RuleExpressionType.LambdaExpression
},
new() {
RuleName = "check nested code",
Expression = "nest.code eq \"bar\" and nest.foo == true",
RuleExpressionType = RuleExpressionType.LambdaExpression
}
]
}
];

var rulesEngine = new RulesEngine.RulesEngine(workflow);

List<RuleResultTree> results = await rulesEngine.ExecuteAllRulesAsync("Workflow", cancellationToken, target);

//Different ways to show test results:
var outcome = results.TrueForAll(r => r.IsSuccess);

results.OnSuccess(eventName => {
Console.WriteLine($"Result '{eventName}' is as expected.");
outcome = true;
});

results.OnFail(() => {
outcome = false;
});

Console.WriteLine($"Test outcome: {outcome}.");
}
}
}
128 changes: 128 additions & 0 deletions demo/DemoApp/ObjectAsPrimitiveConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// 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.HelperFunctions
{
public class ObjectAsPrimitiveConverter : JsonConverter<object>
{
public FloatFormat FloatFormat { get; init; }
UnknownNumberFormat UnknownNumberFormat { get; init; }
ObjectFormat ObjectFormat { get; init; }

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;
}

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() => ObjectFormat == ObjectFormat.Expando ? new ExpandoObject() : new Dictionary<string, object>();
}

public enum FloatFormat
{
Double,
Decimal,
}

public enum UnknownNumberFormat
{
Error,
JsonElement,
}

public enum ObjectFormat
{
Expando,
Dictionary,
}
}
5 changes: 4 additions & 1 deletion src/RulesEngine/Models/ImplicitObject.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
// https://github.com/asulwer/RulesEngine/issues/75

using System.Diagnostics.CodeAnalysis;

using System.Diagnostics.CodeAnalysis;

Check warning on line 7 in src/RulesEngine/Models/ImplicitObject.cs

View workflow job for this annotation

GitHub Actions / build

The using directive for 'System.Diagnostics.CodeAnalysis' appeared previously in this namespace

Check warning on line 7 in src/RulesEngine/Models/ImplicitObject.cs

View workflow job for this annotation

GitHub Actions / build

The using directive for 'System.Diagnostics.CodeAnalysis' appeared previously in this namespace

Expand Down Expand Up @@ -36,4 +39,4 @@ public class ImplicitObject
#endregion

}
}
}

0 comments on commit 10a6799

Please sign in to comment.