Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ jobs:
6.0.x
8.0.x
9.0.x
10.0.x

# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/dotnetcore-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ jobs:
6.0.x
8.0.x
9.0.x
10.0.x

- name: Install dependencies
run: dotnet restore RulesEngine.sln
Expand Down
2 changes: 1 addition & 1 deletion benchmark/RulesEngineBenchmark/RulesEngineBenchmark.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net6.0;net8.0;net9.0</TargetFrameworks>
<TargetFrameworks>net6.0;net8.0;net9.0;net10.0</TargetFrameworks>
</PropertyGroup>

<ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion demo/DemoApp.EFDataExample/DemoApp.EFDataExample.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
<RootNamespace>DemoApp.EFDataExample</RootNamespace>
<AssemblyName>DemoApp.EFDataExample</AssemblyName>
</PropertyGroup>
Expand Down
2 changes: 1 addition & 1 deletion demo/DemoApp/DemoApp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
<StartupObject>DemoApp.Program</StartupObject>
</PropertyGroup>

Expand Down
4 changes: 2 additions & 2 deletions global.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"sdk": {
"version": "9.0.301",
"version": "10.0.100",
"rollForward": "latestFeature",
"allowPrerelease": false
"allowPrerelease": true
}
}
88 changes: 88 additions & 0 deletions src/RulesEngine/HelperFunctions/Utils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ public static object GetTypedObject(dynamic input)
Type type = CreateAbstractClassType(input);
return CreateObject(type, input);
}
else if (input is IDictionary<string, object> dict)
{
Type type = CreateAbstractClassTypeFromDictionary(dict);
return CreateObjectFromDictionary(type, dict);
}
else
{
return input;
Expand Down Expand Up @@ -129,6 +134,89 @@ private static IList ToList(this IEnumerable self, Type innerType)
var genericMethod = methodInfo.MakeGenericMethod(innerType);
return genericMethod.Invoke(null, new[] { self }) as IList;
}

private static Type CreateAbstractClassTypeFromDictionary(IDictionary<string, object> dictionary)
{
List<DynamicProperty> props = [];

foreach (var kvp in dictionary)
{
Type valueType;
if (kvp.Value is ExpandoObject)
{
valueType = CreateAbstractClassType(kvp.Value);
}
else if (kvp.Value is IDictionary<string, object> nestedDict)
{
valueType = CreateAbstractClassTypeFromDictionary(nestedDict);
}
else if (kvp.Value is IList list)
{
if (list.Count == 0)
{
valueType = typeof(List<object>);
}
else
{
var internalType = list[0] is IDictionary<string, object> innerDict
? CreateAbstractClassTypeFromDictionary(innerDict)
: (list[0] is ExpandoObject ? CreateAbstractClassType(list[0]) : list[0]?.GetType() ?? typeof(object));
valueType = new List<object>().Cast(internalType).ToList(internalType).GetType();
}
}
else
{
valueType = kvp.Value?.GetType() ?? typeof(object);
}
props.Add(new DynamicProperty(kvp.Key, valueType));
}

return DynamicClassFactory.CreateType(props);
}

private static object CreateObjectFromDictionary(Type type, IDictionary<string, object> dictionary)
{
var obj = Activator.CreateInstance(type);
var typeProps = type.GetProperties().ToDictionary(c => c.Name);

foreach (var kvp in dictionary)
{
if (typeProps.ContainsKey(kvp.Key) &&
kvp.Value != null && (kvp.Value.GetType().Name != "DBNull" || kvp.Value != DBNull.Value))
{
object val;
var propInfo = typeProps[kvp.Key];
if (kvp.Value is ExpandoObject)
{
val = CreateObject(propInfo.PropertyType, kvp.Value);
}
else if (kvp.Value is IDictionary<string, object> nestedDict)
{
val = CreateObjectFromDictionary(propInfo.PropertyType, nestedDict);
}
else if (kvp.Value is IList temp)
{
var internalType = propInfo.PropertyType.GenericTypeArguments.FirstOrDefault() ?? typeof(object);
var newList = new List<object>().Cast(internalType).ToList(internalType);
foreach (var t in temp)
{
var child = t is IDictionary<string, object> d
? CreateObjectFromDictionary(internalType, d)
: (t is ExpandoObject ? CreateObject(internalType, t) : t);
newList.Add(child);
}
val = newList;
}
else
{
val = kvp.Value;
}
propInfo.SetValue(obj, val, null);
}
}

return obj;
}
}


Expand Down
6 changes: 3 additions & 3 deletions src/RulesEngine/RulesEngine.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net6.0;net8.0;net9.0;netstandard2.0</TargetFrameworks>
<TargetFrameworks>net6.0;net8.0;net9.0;net10.0;netstandard2.0;netstandard2.1</TargetFrameworks>
<LangVersion>13.0</LangVersion>
<Version>6.0.0</Version>
<Copyright>Copyright (c) Microsoft Corporation.</Copyright>
Expand Down Expand Up @@ -41,7 +41,7 @@
</ItemGroup>

<Choose>
<When Condition="'$(TargetFramework)' == 'net6.0' or '$(TargetFramework)' == 'netstandard2.0'">
<When Condition="'$(TargetFramework)' == 'net6.0' or '$(TargetFramework)' == 'netstandard2.0' or '$(TargetFramework)' == 'netstandard2.1'">
<ItemGroup>
<PackageReference Include="System.Text.Json" Version="6.0.11" />
</ItemGroup>
Expand All @@ -53,7 +53,7 @@
</Otherwise>
</Choose>

<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0' or '$(TargetFramework)' == 'netstandard2.1'">
<PackageReference Include="Microsoft.CSharp" Version="4.7.0"/>
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,42 @@ public void TestExpressionWithDifferentCompilerSettings(bool fastExpressionEnabl
var result = ruleParser.Evaluate<bool>("d1 < 20", new[] { Models.RuleParameter.Create("d1", d1) });
Assert.False(result);
}

[Fact]
public void TestExpressionWithDictionaryParameter()
{
var parser = new RuleExpressionParser(new ReSettings());

var payload = new System.Collections.Generic.Dictionary<string, object>
{
{ "Formule", "Essentielle" }
};

var ruleParameters = new[] { RuleParameter.Create("_", payload) };

var resultNotEqual = parser.Evaluate<bool>("Formule != \"Essentielle\"", ruleParameters);
Assert.False(resultNotEqual);

var resultEqual = parser.Evaluate<bool>("Formule == \"Essentielle\"", ruleParameters);
Assert.True(resultEqual);
}

[Fact]
public void TestExpressionWithDictionaryParameter_MultipleKeys()
{
var parser = new RuleExpressionParser(new ReSettings());

var payload = new System.Collections.Generic.Dictionary<string, object>
{
{ "Name", "John" },
{ "Age", 30 }
};

var ruleParameters = new[] { RuleParameter.Create("input", payload) };

var result = parser.Evaluate<bool>("Name == \"John\" && Age == 30", ruleParameters);
Assert.True(result);
}
}


Expand Down
2 changes: 1 addition & 1 deletion test/RulesEngine.UnitTest/RulesEngine.UnitTest.csproj
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net6.0;net8.0;net9.0</TargetFrameworks>
<TargetFrameworks>net6.0;net8.0;net9.0;net10.0</TargetFrameworks>
<SignAssembly>True</SignAssembly>
<AssemblyOriginatorKeyFile>..\..\signing\RulesEngine-publicKey.snk</AssemblyOriginatorKeyFile>
<DelaySign>True</DelaySign>
Expand Down
114 changes: 114 additions & 0 deletions test/RulesEngine.UnitTest/UtilsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,120 @@ public void CreateAbstractType_dynamicObject()

}

[Fact]
public void GetTypedObject_Dictionary_ReturnsTypedObject()
{
var dict = new Dictionary<string, object>
{
{ "Name", "Alice" },
{ "Age", 25 }
};

var result = Utils.GetTypedObject(dict);
Assert.IsNotType<Dictionary<string, object>>(result);
Assert.NotNull(result.GetType().GetProperty("Name"));
Assert.NotNull(result.GetType().GetProperty("Age"));
}

[Fact]
public void GetTypedObject_Dictionary_NestedDictionary()
{
var dict = new Dictionary<string, object>
{
{ "Name", "Alice" },
{ "Address", new Dictionary<string, object>
{
{ "City", "Seattle" },
{ "Zip", "98101" }
}
}
};

var result = Utils.GetTypedObject(dict);
Assert.IsNotType<Dictionary<string, object>>(result);
var addressProp = result.GetType().GetProperty("Address");
Assert.NotNull(addressProp);
var address = addressProp.GetValue(result);
Assert.NotNull(address.GetType().GetProperty("City"));
Assert.NotNull(address.GetType().GetProperty("Zip"));
}

[Fact]
public void GetTypedObject_Dictionary_WithList()
{
var dict = new Dictionary<string, object>
{
{ "Name", "Alice" },
{ "Scores", new List<object> { 90, 85, 92 } }
};

var result = Utils.GetTypedObject(dict);
Assert.IsNotType<Dictionary<string, object>>(result);
Assert.NotNull(result.GetType().GetProperty("Name"));
Assert.NotNull(result.GetType().GetProperty("Scores"));
}

[Fact]
public void GetTypedObject_Dictionary_WithEmptyList()
{
var dict = new Dictionary<string, object>
{
{ "Items", new List<object>() }
};

var result = Utils.GetTypedObject(dict);
Assert.IsNotType<Dictionary<string, object>>(result);
Assert.NotNull(result.GetType().GetProperty("Items"));
}

[Fact]
public void GetTypedObject_Dictionary_WithNestedExpandoObject()
{
dynamic nested = new ExpandoObject();
nested.Value = "test";

var dict = new Dictionary<string, object>
{
{ "Nested", (object)nested }
};

var result = Utils.GetTypedObject(dict);
Assert.IsNotType<Dictionary<string, object>>(result);
var nestedProp = result.GetType().GetProperty("Nested");
Assert.NotNull(nestedProp);
}

[Fact]
public void GetTypedObject_Dictionary_WithListOfDictionaries()
{
var dict = new Dictionary<string, object>
{
{ "People", new List<object>
{
new Dictionary<string, object> { { "Name", "Alice" } },
new Dictionary<string, object> { { "Name", "Bob" } }
}
}
};

var result = Utils.GetTypedObject(dict);
Assert.IsNotType<Dictionary<string, object>>(result);
Assert.NotNull(result.GetType().GetProperty("People"));
}

[Fact]
public void GetTypedObject_Dictionary_WithNullValue()
{
var dict = new Dictionary<string, object>
{
{ "Name", "Alice" },
{ "Middle", null }
};

var result = Utils.GetTypedObject(dict);
Assert.IsNotType<Dictionary<string, object>>(result);
Assert.NotNull(result.GetType().GetProperty("Name"));
}

}
}
Loading