diff --git a/src/System.Linq.Dynamic.Core.NewtonsoftJson/Config/NewtonsoftJsonParsingConfig.cs b/src/System.Linq.Dynamic.Core.NewtonsoftJson/Config/NewtonsoftJsonParsingConfig.cs index fbccbf64..3cb24306 100644 --- a/src/System.Linq.Dynamic.Core.NewtonsoftJson/Config/NewtonsoftJsonParsingConfig.cs +++ b/src/System.Linq.Dynamic.Core.NewtonsoftJson/Config/NewtonsoftJsonParsingConfig.cs @@ -10,7 +10,10 @@ public class NewtonsoftJsonParsingConfig : ParsingConfig /// /// The default ParsingConfig for . /// - public new static NewtonsoftJsonParsingConfig Default { get; } = new(); + public new static NewtonsoftJsonParsingConfig Default { get; } = new NewtonsoftJsonParsingConfig + { + ConvertObjectToSupportComparison = true + }; /// /// The default to use. @@ -28,7 +31,7 @@ public class NewtonsoftJsonParsingConfig : ParsingConfig /// /// 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 . + /// 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 index bb68277b..44608e83 100644 --- a/src/System.Linq.Dynamic.Core.NewtonsoftJson/Config/NormalizationNonExistingPropertyBehavior.cs +++ b/src/System.Linq.Dynamic.Core.NewtonsoftJson/Config/NormalizationNonExistingPropertyBehavior.cs @@ -6,12 +6,12 @@ public enum NormalizationNonExistingPropertyBehavior { /// - /// Specifies that the default value should be used. + /// Specifies that a null value should be used. /// - UseDefaultValue = 0, + UseNull = 0, /// - /// Specifies that null values should be used. + /// Specifies that the default value should be used. /// - UseNull = 1 + UseDefaultValue = 1 } \ No newline at end of file diff --git a/src/System.Linq.Dynamic.Core.NewtonsoftJson/Extensions/JObjectExtensions.cs b/src/System.Linq.Dynamic.Core.NewtonsoftJson/Extensions/JObjectExtensions.cs index 1a7be2f4..2f79840b 100644 --- a/src/System.Linq.Dynamic.Core.NewtonsoftJson/Extensions/JObjectExtensions.cs +++ b/src/System.Linq.Dynamic.Core.NewtonsoftJson/Extensions/JObjectExtensions.cs @@ -44,10 +44,7 @@ private class JTokenResolvers : Dictionary func) private static IQueryable ToQueryable(JArray source, NewtonsoftJsonParsingConfig? config = null) { - var normalized = config?.Normalize == true ? - NormalizeUtils.NormalizeArray(source, config.NormalizationNonExistingPropertyValueBehavior): + config = config ?? NewtonsoftJsonParsingConfig.Default; + config.ConvertObjectToSupportComparison = true; + + var normalized = config.Normalize == true ? + NormalizeUtils.NormalizeArray(source, config.NormalizationNonExistingPropertyValueBehavior) : source; return normalized diff --git a/src/System.Linq.Dynamic.Core.NewtonsoftJson/Utils/NormalizeUtils.cs b/src/System.Linq.Dynamic.Core.NewtonsoftJson/Utils/NormalizeUtils.cs index 5669915c..fe980319 100644 --- a/src/System.Linq.Dynamic.Core.NewtonsoftJson/Utils/NormalizeUtils.cs +++ b/src/System.Linq.Dynamic.Core.NewtonsoftJson/Utils/NormalizeUtils.cs @@ -86,7 +86,7 @@ private static JObject NormalizeObject(JObject source, Dictionary schem } else { - obj[key] = normalizationBehavior == NormalizationNonExistingPropertyBehavior.UseDefaultValue ? GetDefaultValue(schema[key]) : JValue.CreateNull(); + obj[key] = GetDefaultOrNullValue(normalizationBehavior, schema[key]); } } @@ -125,7 +125,28 @@ private static JToken GetDefaultValue(JsonValueInfo jType) JTokenType.Integer => default(int), JTokenType.String => string.Empty, JTokenType.TimeSpan => TimeSpan.MinValue, + _ => GetNullValue(jType), + }; + } + + private static JValue GetNullValue(JsonValueInfo jType) + { + return jType.Type switch + { + JTokenType.Boolean => new JValue((bool?)null), + JTokenType.Bytes => new JValue((byte[]?)null), + JTokenType.Date => new JValue((DateTime?)null), + JTokenType.Float => new JValue((float?)null), + JTokenType.Guid => new JValue((Guid?)null), + JTokenType.Integer => new JValue((int?)null), + JTokenType.String => new JValue((string?)null), + JTokenType.TimeSpan => new JValue((TimeSpan?)null), _ => JValue.CreateNull(), }; } + + private static JToken GetDefaultOrNullValue(NormalizationNonExistingPropertyBehavior behavior, JsonValueInfo jType) + { + return behavior == NormalizationNonExistingPropertyBehavior.UseDefaultValue ? GetDefaultValue(jType) : GetNullValue(jType); + } } \ 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 index daafaa4b..381f0408 100644 --- a/src/System.Linq.Dynamic.Core.SystemTextJson/Config/NormalizationNonExistingPropertyBehavior.cs +++ b/src/System.Linq.Dynamic.Core.SystemTextJson/Config/NormalizationNonExistingPropertyBehavior.cs @@ -1,17 +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. +/// 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. + /// Specifies that a null value should be used. /// - UseDefaultValue = 0, + UseNull = 0, /// - /// Specifies that null values should be used. + /// Specifies that the default value should be used. /// - UseNull = 1 + UseDefaultValue = 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 a4c1e76a..4892bcf2 100644 --- a/src/System.Linq.Dynamic.Core.SystemTextJson/Config/SystemTextJsonParsingConfig.cs +++ b/src/System.Linq.Dynamic.Core.SystemTextJson/Config/SystemTextJsonParsingConfig.cs @@ -24,7 +24,7 @@ public class SystemTextJsonParsingConfig : ParsingConfig /// /// 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 . + /// 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/JsonDocumentExtensions.cs b/src/System.Linq.Dynamic.Core.SystemTextJson/Extensions/JsonDocumentExtensions.cs index 7daf15a5..3437d3c9 100644 --- a/src/System.Linq.Dynamic.Core.SystemTextJson/Extensions/JsonDocumentExtensions.cs +++ b/src/System.Linq.Dynamic.Core.SystemTextJson/Extensions/JsonDocumentExtensions.cs @@ -34,10 +34,7 @@ private class JTokenResolvers : Dictionary src, Type newType) { var method = ConvertToTypedArrayGenericMethod.MakeGenericMethod(newType); - return (IEnumerable)method.Invoke(null, new object[] { src })!; + return (IEnumerable)method.Invoke(null, [src])!; } private static readonly MethodInfo ConvertToTypedArrayGenericMethod = typeof(JsonDocumentExtensions).GetMethod(nameof(ConvertToTypedArrayGeneric), BindingFlags.NonPublic | BindingFlags.Static)!; diff --git a/src/System.Linq.Dynamic.Core.SystemTextJson/Utils/NormalizeUtils.cs b/src/System.Linq.Dynamic.Core.SystemTextJson/Utils/NormalizeUtils.cs index fa5836d2..7f6e4abf 100644 --- a/src/System.Linq.Dynamic.Core.SystemTextJson/Utils/NormalizeUtils.cs +++ b/src/System.Linq.Dynamic.Core.SystemTextJson/Utils/NormalizeUtils.cs @@ -104,15 +104,16 @@ private static JsonObject NormalizeObject(JsonObject source, Dictionary sc } else { - obj[key] = normalizationBehavior == NormalizationNonExistingPropertyBehavior.UseDefaultValue ? GetDefaultValue(jType) : null; + obj[key] = GetDefaultOrNullValue(normalizationBehavior, jType); } } @@ -150,7 +151,25 @@ private static JsonObject CreateEmptyObject(Dictionary sc JsonValueKind.Number => default(int), JsonValueKind.String => string.Empty, JsonValueKind.True => false, + _ => GetNullValue(jType), + }; + } + + private static JsonNode? GetNullValue(JsonValueInfo jType) + { + return jType.Type switch + { + JsonValueKind.Array => null, + JsonValueKind.False => JsonValue.Create(false), + JsonValueKind.Number => JsonValue.Create(null), + JsonValueKind.String => JsonValue.Create(null), + JsonValueKind.True => JsonValue.Create(true), _ => null, }; } + + private static JsonNode? GetDefaultOrNullValue(NormalizationNonExistingPropertyBehavior behavior, JsonValueInfo jType) + { + return behavior == NormalizationNonExistingPropertyBehavior.UseDefaultValue ? GetDefaultValue(jType) : GetNullValue(jType); + } } \ No newline at end of file diff --git a/test/System.Linq.Dynamic.Core.NewtonsoftJson.Tests/NewtonsoftJsonTests.cs b/test/System.Linq.Dynamic.Core.NewtonsoftJson.Tests/NewtonsoftJsonTests.cs index e391ed29..d07d8052 100644 --- a/test/System.Linq.Dynamic.Core.NewtonsoftJson.Tests/NewtonsoftJsonTests.cs +++ b/test/System.Linq.Dynamic.Core.NewtonsoftJson.Tests/NewtonsoftJsonTests.cs @@ -1,5 +1,4 @@ -using System.Linq.Dynamic.Core.Exceptions; -using System.Linq.Dynamic.Core.NewtonsoftJson.Config; +using System.Linq.Dynamic.Core.NewtonsoftJson.Config; using FluentAssertions; using Newtonsoft.Json.Linq; using Xunit; @@ -13,11 +12,13 @@ public class NewtonsoftJsonTests [ { "Name": "John", - "Age": 30 + "Age": 30, + "IsNull": null }, { "Name": "Doe", - "Age": 40 + "Age": 40, + "AlsoNull": null } ] """; @@ -567,6 +568,6 @@ public void NormalizeArray_When_NormalizeIsFalse_ShouldThrow() Action act = () => JArray.Parse(array).Where(config, "Age >= 30"); // Assert - act.Should().Throw().WithMessage("The binary operator GreaterThanOrEqual is not defined for the types 'System.Object' and 'System.Int32'."); + act.Should().Throw().WithMessage("Unable to find property 'Age' on type '<>*"); } } \ No newline at end of file diff --git a/test/System.Linq.Dynamic.Core.SystemTextJson.Tests/SystemTextJsonTests.cs b/test/System.Linq.Dynamic.Core.SystemTextJson.Tests/SystemTextJsonTests.cs index e699e086..dd5e62ee 100644 --- a/test/System.Linq.Dynamic.Core.SystemTextJson.Tests/SystemTextJsonTests.cs +++ b/test/System.Linq.Dynamic.Core.SystemTextJson.Tests/SystemTextJsonTests.cs @@ -17,11 +17,13 @@ public class SystemTextJsonTests [ { "Name": "John", - "Age": 30 + "Age": 30, + "IsNull": null }, { "Name": "Doe", - "Age": 40 + "Age": 40, + "AlsoNull": null } ] """; @@ -161,20 +163,20 @@ public void Distinct() public void First() { // Act + Assert 1 - _source.First().GetRawText().Should().BeEquivalentTo(JsonDocument.Parse(@"{""Name"":""John"",""Age"":30}").RootElement.GetRawText()); + _source.First().GetRawText().Should().BeEquivalentTo(JsonDocument.Parse(@"{""Name"":""John"",""Age"":30,""IsNull"":null,""AlsoNull"":null}").RootElement.GetRawText()); // Act + Assert 2 - _source.First("Age > 30").GetRawText().Should().BeEquivalentTo(JsonDocument.Parse(@"{""Name"":""Doe"",""Age"":40}").RootElement.GetRawText()); + _source.First("Age > 30").GetRawText().Should().BeEquivalentTo(JsonDocument.Parse(@"{""Name"":""Doe"",""Age"":40,""IsNull"":null,""AlsoNull"":null}").RootElement.GetRawText()); } [Fact] public void FirstOrDefault() { // Act + Assert 1 - _source.FirstOrDefault()!.Value.GetRawText().Should().BeEquivalentTo(JsonDocument.Parse(@"{""Name"":""John"",""Age"":30}").RootElement.GetRawText()); + _source.FirstOrDefault()!.Value.GetRawText().Should().BeEquivalentTo(JsonDocument.Parse(@"{""Name"":""John"",""Age"":30,""IsNull"":null,""AlsoNull"":null}").RootElement.GetRawText()); // Act + Assert 2 - _source.FirstOrDefault("Age > 30")!.Value.GetRawText().Should().BeEquivalentTo(JsonDocument.Parse(@"{""Name"":""Doe"",""Age"":40}").RootElement.GetRawText()); + _source.FirstOrDefault("Age > 30")!.Value.GetRawText().Should().BeEquivalentTo(JsonDocument.Parse(@"{""Name"":""Doe"",""Age"":40,""IsNull"":null,""AlsoNull"":null}").RootElement.GetRawText()); // Act + Assert 3 _source.FirstOrDefault("Age > 999").Should().BeNull(); @@ -267,20 +269,20 @@ public void GroupBySimpleKeySelector() public void Last() { // Act + Assert 1 - _source.Last().GetRawText().Should().BeEquivalentTo(JsonDocument.Parse(@"{""Name"":""Doe"",""Age"":40}").RootElement.GetRawText()); + _source.Last().GetRawText().Should().BeEquivalentTo(JsonDocument.Parse(@"{""Name"":""Doe"",""Age"":40,""IsNull"":null,""AlsoNull"":null}").RootElement.GetRawText()); // Act + Assert 2 - _source.Last("Age > 0").GetRawText().Should().BeEquivalentTo(JsonDocument.Parse(@"{""Name"":""Doe"",""Age"":40}").RootElement.GetRawText()); + _source.Last("Age > 0").GetRawText().Should().BeEquivalentTo(JsonDocument.Parse(@"{""Name"":""Doe"",""Age"":40,""IsNull"":null,""AlsoNull"":null}").RootElement.GetRawText()); } [Fact] public void LastOrDefault() { // Act + Assert 1 - _source.LastOrDefault()!.Value.GetRawText().Should().BeEquivalentTo(JsonDocument.Parse(@"{""Name"":""Doe"",""Age"":40}").RootElement.GetRawText()); + _source.LastOrDefault()!.Value.GetRawText().Should().BeEquivalentTo(JsonDocument.Parse(@"{""Name"":""Doe"",""Age"":40,""IsNull"":null,""AlsoNull"":null}").RootElement.GetRawText()); // Act + Assert 2 - _source.LastOrDefault("Age > 0")!.Value.GetRawText().Should().BeEquivalentTo(JsonDocument.Parse(@"{""Name"":""Doe"",""Age"":40}").RootElement.GetRawText()); + _source.LastOrDefault("Age > 0")!.Value.GetRawText().Should().BeEquivalentTo(JsonDocument.Parse(@"{""Name"":""Doe"",""Age"":40,""IsNull"":null,""AlsoNull"":null}").RootElement.GetRawText()); // Act + Assert 3 _source.LastOrDefault("Age > 999").Should().BeNull(); @@ -444,7 +446,7 @@ public void SelectMany() public void Single() { // Act + Assert - _source.Single("Age > 30").GetRawText().Should().BeEquivalentTo(JsonDocument.Parse(@"{""Name"":""Doe"",""Age"":40}").RootElement.GetRawText()); + _source.Single("Age > 30").GetRawText().Should().BeEquivalentTo(JsonDocument.Parse(@"{""Name"":""Doe"",""Age"":40,""IsNull"":null,""AlsoNull"":null}").RootElement.GetRawText()); } [Fact]