diff --git a/src/System.Linq.Dynamic.Core.NewtonsoftJson/Config/NewtonsoftJsonParsingConfig.cs b/src/System.Linq.Dynamic.Core.NewtonsoftJson/Config/NewtonsoftJsonParsingConfig.cs
index fad7f9f1..fbccbf64 100644
--- a/src/System.Linq.Dynamic.Core.NewtonsoftJson/Config/NewtonsoftJsonParsingConfig.cs
+++ b/src/System.Linq.Dynamic.Core.NewtonsoftJson/Config/NewtonsoftJsonParsingConfig.cs
@@ -15,5 +15,20 @@ public class NewtonsoftJsonParsingConfig : ParsingConfig
///
/// The default to use.
///
- public DynamicJsonClassOptions? DynamicJsonClassOptions { get; set; }
+ public DynamicJsonClassOptions? DynamicJsonClassOptions { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether the objects in an array should be normalized before processing.
+ ///
+ public bool Normalize { get; set; } = true;
+
+ ///
+ /// Gets or sets the behavior to apply when a property value does not exist during normalization.
+ ///
+ ///
+ /// Use this property to control how the normalization process handles properties that are missing or undefined.
+ /// The selected behavior may affect the output or error handling of normalization operations.
+ /// The default value is .
+ ///
+ public NormalizationNonExistingPropertyBehavior NormalizationNonExistingPropertyValueBehavior { get; set; }
}
\ No newline at end of file
diff --git a/src/System.Linq.Dynamic.Core.NewtonsoftJson/Config/NormalizationNonExistingPropertyBehavior.cs b/src/System.Linq.Dynamic.Core.NewtonsoftJson/Config/NormalizationNonExistingPropertyBehavior.cs
new file mode 100644
index 00000000..bb68277b
--- /dev/null
+++ b/src/System.Linq.Dynamic.Core.NewtonsoftJson/Config/NormalizationNonExistingPropertyBehavior.cs
@@ -0,0 +1,17 @@
+namespace System.Linq.Dynamic.Core.NewtonsoftJson.Config;
+
+///
+/// Specifies the behavior to use when setting a property value that does not exist or is missing during normalization.
+///
+public enum NormalizationNonExistingPropertyBehavior
+{
+ ///
+ /// Specifies that the default value should be used.
+ ///
+ UseDefaultValue = 0,
+
+ ///
+ /// Specifies that null values should be used.
+ ///
+ UseNull = 1
+}
\ No newline at end of file
diff --git a/src/System.Linq.Dynamic.Core.NewtonsoftJson/JsonValueInfo.cs b/src/System.Linq.Dynamic.Core.NewtonsoftJson/JsonValueInfo.cs
new file mode 100644
index 00000000..963ff37d
--- /dev/null
+++ b/src/System.Linq.Dynamic.Core.NewtonsoftJson/JsonValueInfo.cs
@@ -0,0 +1,10 @@
+using Newtonsoft.Json.Linq;
+
+namespace System.Linq.Dynamic.Core.NewtonsoftJson;
+
+internal readonly struct JsonValueInfo(JTokenType type, object? value)
+{
+ public JTokenType Type { get; } = type;
+
+ public object? Value { get; } = value;
+}
\ No newline at end of file
diff --git a/src/System.Linq.Dynamic.Core.NewtonsoftJson/NewtonsoftJsonExtensions.cs b/src/System.Linq.Dynamic.Core.NewtonsoftJson/NewtonsoftJsonExtensions.cs
index 8aefa397..943df3dd 100644
--- a/src/System.Linq.Dynamic.Core.NewtonsoftJson/NewtonsoftJsonExtensions.cs
+++ b/src/System.Linq.Dynamic.Core.NewtonsoftJson/NewtonsoftJsonExtensions.cs
@@ -1,6 +1,7 @@
using System.Collections;
using System.Linq.Dynamic.Core.NewtonsoftJson.Config;
using System.Linq.Dynamic.Core.NewtonsoftJson.Extensions;
+using System.Linq.Dynamic.Core.NewtonsoftJson.Utils;
using System.Linq.Dynamic.Core.Validation;
using JetBrains.Annotations;
using Newtonsoft.Json.Linq;
@@ -870,7 +871,13 @@ private static JArray ToJArray(Func func)
private static IQueryable ToQueryable(JArray source, NewtonsoftJsonParsingConfig? config = null)
{
- return source.ToDynamicJsonClassArray(config?.DynamicJsonClassOptions).AsQueryable();
+ var normalized = config?.Normalize == true ?
+ NormalizeUtils.NormalizeArray(source, config.NormalizationNonExistingPropertyValueBehavior):
+ source;
+
+ return normalized
+ .ToDynamicJsonClassArray(config?.DynamicJsonClassOptions)
+ .AsQueryable();
}
#endregion
}
\ No newline at end of file
diff --git a/src/System.Linq.Dynamic.Core.NewtonsoftJson/Utils/NormalizeUtils.cs b/src/System.Linq.Dynamic.Core.NewtonsoftJson/Utils/NormalizeUtils.cs
new file mode 100644
index 00000000..5669915c
--- /dev/null
+++ b/src/System.Linq.Dynamic.Core.NewtonsoftJson/Utils/NormalizeUtils.cs
@@ -0,0 +1,131 @@
+using System.Collections.Generic;
+using System.Linq.Dynamic.Core.NewtonsoftJson.Config;
+using Newtonsoft.Json.Linq;
+
+namespace System.Linq.Dynamic.Core.NewtonsoftJson.Utils;
+
+internal static class NormalizeUtils
+{
+ ///
+ /// Normalizes an array of JSON objects so that each object contains all properties found in the array,
+ /// including nested objects. Missing properties will have null values.
+ ///
+ internal static JArray NormalizeArray(JArray jsonArray, NormalizationNonExistingPropertyBehavior normalizationBehavior)
+ {
+ if (jsonArray.Any(item => item is not JObject))
+ {
+ return jsonArray;
+ }
+
+ var schema = BuildSchema(jsonArray);
+ var normalizedArray = new JArray();
+
+ foreach (var jo in jsonArray.OfType())
+ {
+ var normalizedObj = NormalizeObject(jo, schema, normalizationBehavior);
+ normalizedArray.Add(normalizedObj);
+ }
+
+ return normalizedArray;
+ }
+
+ private static Dictionary BuildSchema(JArray array)
+ {
+ var schema = new Dictionary();
+
+ foreach (var item in array)
+ {
+ if (item is JObject obj)
+ {
+ MergeSchema(schema, obj);
+ }
+ }
+
+ return schema;
+ }
+
+ private static void MergeSchema(Dictionary schema, JObject obj)
+ {
+ foreach (var prop in obj.Properties())
+ {
+ if (prop.Value is JObject nested)
+ {
+ if (!schema.TryGetValue(prop.Name, out var jsonValueInfo))
+ {
+ jsonValueInfo = new JsonValueInfo(JTokenType.Object, new Dictionary());
+ schema[prop.Name] = jsonValueInfo;
+ }
+
+ MergeSchema((Dictionary)jsonValueInfo.Value!, nested);
+ }
+ else
+ {
+ if (!schema.ContainsKey(prop.Name))
+ {
+ schema[prop.Name] = new JsonValueInfo(prop.Value.Type, null);
+ }
+ }
+ }
+ }
+
+ private static JObject NormalizeObject(JObject source, Dictionary schema, NormalizationNonExistingPropertyBehavior normalizationBehavior)
+ {
+ var result = new JObject();
+
+ foreach (var key in schema.Keys)
+ {
+ if (schema[key].Value is Dictionary nestedSchema)
+ {
+ result[key] = source.ContainsKey(key) && source[key] is JObject jo ? NormalizeObject(jo, nestedSchema, normalizationBehavior) : CreateEmptyObject(nestedSchema, normalizationBehavior);
+ }
+ else
+ {
+ if (source.ContainsKey(key))
+ {
+ result[key] = source[key];
+ }
+ else
+ {
+ result[key] = normalizationBehavior == NormalizationNonExistingPropertyBehavior.UseDefaultValue ? GetDefaultValue(schema[key]) : JValue.CreateNull();
+ }
+ }
+ }
+
+ return result;
+ }
+
+ private static JObject CreateEmptyObject(Dictionary schema, NormalizationNonExistingPropertyBehavior normalizationBehavior)
+ {
+ var obj = new JObject();
+ foreach (var key in schema.Keys)
+ {
+ if (schema[key].Value is Dictionary nestedSchema)
+ {
+ obj[key] = CreateEmptyObject(nestedSchema, normalizationBehavior);
+ }
+ else
+ {
+ obj[key] = normalizationBehavior == NormalizationNonExistingPropertyBehavior.UseDefaultValue ? GetDefaultValue(schema[key]) : JValue.CreateNull();
+ }
+ }
+
+ return obj;
+ }
+
+ private static JToken GetDefaultValue(JsonValueInfo jType)
+ {
+ return jType.Type switch
+ {
+ JTokenType.Array => new JArray(),
+ JTokenType.Boolean => default(bool),
+ JTokenType.Bytes => new byte[0],
+ JTokenType.Date => DateTime.MinValue,
+ JTokenType.Float => default(float),
+ JTokenType.Guid => Guid.Empty,
+ JTokenType.Integer => default(int),
+ JTokenType.String => string.Empty,
+ JTokenType.TimeSpan => TimeSpan.MinValue,
+ _ => JValue.CreateNull(),
+ };
+ }
+}
\ No newline at end of file
diff --git a/src/System.Linq.Dynamic.Core.SystemTextJson/Config/NormalizationNonExistingPropertyBehavior.cs b/src/System.Linq.Dynamic.Core.SystemTextJson/Config/NormalizationNonExistingPropertyBehavior.cs
new file mode 100644
index 00000000..daafaa4b
--- /dev/null
+++ b/src/System.Linq.Dynamic.Core.SystemTextJson/Config/NormalizationNonExistingPropertyBehavior.cs
@@ -0,0 +1,17 @@
+namespace System.Linq.Dynamic.Core.SystemTextJson.Config;
+
+///
+/// Specifies the behavior to use when setting a property vlue that does not exist or is missing during normalization.
+///
+public enum NormalizationNonExistingPropertyBehavior
+{
+ ///
+ /// Specifies that the default value should be used.
+ ///
+ UseDefaultValue = 0,
+
+ ///
+ /// Specifies that null values should be used.
+ ///
+ UseNull = 1
+}
\ No newline at end of file
diff --git a/src/System.Linq.Dynamic.Core.SystemTextJson/Config/SystemTextJsonParsingConfig.cs b/src/System.Linq.Dynamic.Core.SystemTextJson/Config/SystemTextJsonParsingConfig.cs
index eb42ff6d..a4c1e76a 100644
--- a/src/System.Linq.Dynamic.Core.SystemTextJson/Config/SystemTextJsonParsingConfig.cs
+++ b/src/System.Linq.Dynamic.Core.SystemTextJson/Config/SystemTextJsonParsingConfig.cs
@@ -8,5 +8,23 @@ public class SystemTextJsonParsingConfig : ParsingConfig
///
/// The default ParsingConfig for .
///
- public new static SystemTextJsonParsingConfig Default { get; } = new();
+ public new static SystemTextJsonParsingConfig Default { get; } = new SystemTextJsonParsingConfig
+ {
+ ConvertObjectToSupportComparison = true
+ };
+
+ ///
+ /// Gets or sets a value indicating whether the objects in an array should be normalized before processing.
+ ///
+ public bool Normalize { get; set; } = true;
+
+ ///
+ /// Gets or sets the behavior to apply when a property value does not exist during normalization.
+ ///
+ ///
+ /// Use this property to control how the normalization process handles properties that are missing or undefined.
+ /// The selected behavior may affect the output or error handling of normalization operations.
+ /// The default value is .
+ ///
+ public NormalizationNonExistingPropertyBehavior NormalizationNonExistingPropertyValueBehavior { get; set; }
}
\ No newline at end of file
diff --git a/src/System.Linq.Dynamic.Core.SystemTextJson/Extensions/JsonValueExtensions.cs b/src/System.Linq.Dynamic.Core.SystemTextJson/Extensions/JsonValueExtensions.cs
new file mode 100644
index 00000000..907f7bf3
--- /dev/null
+++ b/src/System.Linq.Dynamic.Core.SystemTextJson/Extensions/JsonValueExtensions.cs
@@ -0,0 +1,21 @@
+#if !NET8_0_OR_GREATER
+namespace System.Text.Json.Nodes;
+
+internal static class JsonValueExtensions
+{
+ internal static JsonValueKind? GetValueKind(this JsonNode node)
+ {
+ if (node is JsonObject)
+ {
+ return JsonValueKind.Object;
+ }
+
+ if (node is JsonArray)
+ {
+ return JsonValueKind.Array;
+ }
+
+ return node.GetValue