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
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ public class NewtonsoftJsonParsingConfig : ParsingConfig
/// <summary>
/// The default ParsingConfig for <see cref="NewtonsoftJsonParsingConfig"/>.
/// </summary>
public new static NewtonsoftJsonParsingConfig Default { get; } = new();
public new static NewtonsoftJsonParsingConfig Default { get; } = new NewtonsoftJsonParsingConfig
{
ConvertObjectToSupportComparison = true
};

/// <summary>
/// The default <see cref="DynamicJsonClassOptions"/> to use.
Expand All @@ -28,7 +31,7 @@ public class NewtonsoftJsonParsingConfig : ParsingConfig
/// <remarks>
/// 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 <see cref="NormalizationNonExistingPropertyBehavior.UseDefaultValue"/>.
/// The default value is <see cref="NormalizationNonExistingPropertyBehavior.UseNull"/>.
/// </remarks>
public NormalizationNonExistingPropertyBehavior NormalizationNonExistingPropertyValueBehavior { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
public enum NormalizationNonExistingPropertyBehavior
{
/// <summary>
/// Specifies that the default value should be used.
/// Specifies that a null value should be used.
/// </summary>
UseDefaultValue = 0,
UseNull = 0,

/// <summary>
/// Specifies that null values should be used.
/// Specifies that the default value should be used.
/// </summary>
UseNull = 1
UseDefaultValue = 1
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,7 @@ private class JTokenResolvers : Dictionary<JTokenType, Func<JToken, DynamicJsonC
foreach (var prop in src.Properties())
{
var value = Resolvers[prop.Type](prop.Value, options);
if (value != null)
{
dynamicPropertiesWithValue.Add(new DynamicPropertyWithValue(prop.Name, value));
}
dynamicPropertiesWithValue.Add(new DynamicPropertyWithValue(prop.Name, value));
}

return DynamicClassFactory.CreateInstance(dynamicPropertiesWithValue);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -871,8 +871,11 @@ private static JArray ToJArray(Func<IQueryable> 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ private static JObject NormalizeObject(JObject source, Dictionary<string, JsonVa
}
else
{
result[key] = normalizationBehavior == NormalizationNonExistingPropertyBehavior.UseDefaultValue ? GetDefaultValue(schema[key]) : JValue.CreateNull();
result[key] = GetDefaultOrNullValue(normalizationBehavior, schema[key]);
}
}
}
Expand All @@ -105,7 +105,7 @@ private static JObject CreateEmptyObject(Dictionary<string, JsonValueInfo> schem
}
else
{
obj[key] = normalizationBehavior == NormalizationNonExistingPropertyBehavior.UseDefaultValue ? GetDefaultValue(schema[key]) : JValue.CreateNull();
obj[key] = GetDefaultOrNullValue(normalizationBehavior, schema[key]);
}
}

Expand All @@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
namespace System.Linq.Dynamic.Core.SystemTextJson.Config;

/// <summary>
/// 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.
/// </summary>
public enum NormalizationNonExistingPropertyBehavior
{
/// <summary>
/// Specifies that the default value should be used.
/// Specifies that a null value should be used.
/// </summary>
UseDefaultValue = 0,
UseNull = 0,

/// <summary>
/// Specifies that null values should be used.
/// Specifies that the default value should be used.
/// </summary>
UseNull = 1
UseDefaultValue = 1
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public class SystemTextJsonParsingConfig : ParsingConfig
/// <remarks>
/// 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 <see cref="NormalizationNonExistingPropertyBehavior.UseDefaultValue"/>.
/// The default value is <see cref="NormalizationNonExistingPropertyBehavior.UseNull"/>.
/// </remarks>
public NormalizationNonExistingPropertyBehavior NormalizationNonExistingPropertyValueBehavior { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,7 @@ private class JTokenResolvers : Dictionary<JsonValueKind, Func<JsonElement, Dyna
foreach (var prop in src.Value.EnumerateObject())
{
var value = Resolvers[prop.Value.ValueKind](prop.Value, options);
if (value != null)
{
dynamicPropertiesWithValue.Add(new DynamicPropertyWithValue(prop.Name, value));
}
dynamicPropertiesWithValue.Add(new DynamicPropertyWithValue(prop.Name, value));
}

return DynamicClassFactory.CreateInstance(dynamicPropertiesWithValue);
Expand Down Expand Up @@ -129,7 +126,7 @@ private static IEnumerable ConvertJsonElementToEnumerable(JsonElement arg, Dynam
private static IEnumerable ConvertToTypedArray(IEnumerable<object?> 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)!;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,15 +104,16 @@ private static JsonObject NormalizeObject(JsonObject source, Dictionary<string,
{
if (source.ContainsKey(key))
{
var value = source[key];
#if NET8_0_OR_GREATER
result[key] = source[key]!.DeepClone();
result[key] = value?.DeepClone();
#else
result[key] = JsonNode.Parse(source[key]!.ToJsonString());
result[key] = value != null ? JsonNode.Parse(value.ToJsonString()) : null;
#endif
}
else
{
result[key] = normalizationBehavior == NormalizationNonExistingPropertyBehavior.UseDefaultValue ? GetDefaultValue(jType) : null;
result[key] = GetDefaultOrNullValue(normalizationBehavior, jType);
}
}
}
Expand All @@ -134,7 +135,7 @@ private static JsonObject CreateEmptyObject(Dictionary<string, JsonValueInfo> sc
}
else
{
obj[key] = normalizationBehavior == NormalizationNonExistingPropertyBehavior.UseDefaultValue ? GetDefaultValue(jType) : null;
obj[key] = GetDefaultOrNullValue(normalizationBehavior, jType);
}
}

Expand All @@ -150,7 +151,25 @@ private static JsonObject CreateEmptyObject(Dictionary<string, JsonValueInfo> 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<bool?>(false),
JsonValueKind.Number => JsonValue.Create<int?>(null),
JsonValueKind.String => JsonValue.Create<string?>(null),
JsonValueKind.True => JsonValue.Create<bool?>(true),
_ => null,
};
}

private static JsonNode? GetDefaultOrNullValue(NormalizationNonExistingPropertyBehavior behavior, JsonValueInfo jType)
{
return behavior == NormalizationNonExistingPropertyBehavior.UseDefaultValue ? GetDefaultValue(jType) : GetNullValue(jType);
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -13,11 +12,13 @@ public class NewtonsoftJsonTests
[
{
"Name": "John",
"Age": 30
"Age": 30,
"IsNull": null
},
{
"Name": "Doe",
"Age": 40
"Age": 40,
"AlsoNull": null
}
]
""";
Expand Down Expand Up @@ -567,6 +568,6 @@ public void NormalizeArray_When_NormalizeIsFalse_ShouldThrow()
Action act = () => JArray.Parse(array).Where(config, "Age >= 30");

// Assert
act.Should().Throw<InvalidOperationException>().WithMessage("The binary operator GreaterThanOrEqual is not defined for the types 'System.Object' and 'System.Int32'.");
act.Should().Throw<InvalidOperationException>().WithMessage("Unable to find property 'Age' on type '<>*");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@ public class SystemTextJsonTests
[
{
"Name": "John",
"Age": 30
"Age": 30,
"IsNull": null
},
{
"Name": "Doe",
"Age": 40
"Age": 40,
"AlsoNull": null
}
]
""";
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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]
Expand Down
Loading