Skip to content

Commit 84ea210

Browse files
committed
json: fix logic when property is not found
1 parent 1816c3b commit 84ea210

File tree

6 files changed

+86
-7
lines changed

6 files changed

+86
-7
lines changed

src/System.Linq.Dynamic.Core/DynamicClass.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,16 @@ public object? this[string name]
123123
}
124124
}
125125

126+
/// <summary>
127+
/// Determines whether a property with the specified name exists in the collection.
128+
/// </summary>
129+
/// <param name="name">The name of the property to locate. Cannot be null.</param>
130+
/// <returns><c>true</c> if a property with the specified name exists; otherwise, <c>false</c>.</returns>
131+
public bool ContainsProperty(string name)
132+
{
133+
return Properties.ContainsKey(name);
134+
}
135+
126136
/// <summary>
127137
/// Returns the enumeration of all dynamic member names.
128138
/// </summary>

src/System.Linq.Dynamic.Core/DynamicClass.uap.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public class DynamicClass : DynamicObject
1212
{
1313
internal const string IndexerName = "System_Linq_Dynamic_Core_DynamicClass_Indexer";
1414

15-
private readonly Dictionary<string, object> _properties = new();
15+
private readonly Dictionary<string, object?> _properties = new();
1616

1717
/// <summary>
1818
/// Initializes a new instance of the <see cref="DynamicClass"/> class.
@@ -35,7 +35,7 @@ public DynamicClass(params KeyValuePair<string, object>[] propertylist)
3535
/// <param name="name">The name.</param>
3636
/// <returns>Value from the property.</returns>
3737
[IndexerName(IndexerName)]
38-
public object this[string name]
38+
public object? this[string name]
3939
{
4040
get
4141
{
@@ -59,6 +59,16 @@ public object this[string name]
5959
}
6060
}
6161

62+
/// <summary>
63+
/// Determines whether a property with the specified name exists in the collection.
64+
/// </summary>
65+
/// <param name="name">The name of the property to locate. Cannot be null.</param>
66+
/// <returns><c>true</c> if a property with the specified name exists; otherwise, <c>false</c>.</returns>
67+
public bool ContainsProperty(string name)
68+
{
69+
return _properties.ContainsKey(name);
70+
}
71+
6272
/// <summary>
6373
/// Returns the enumeration of all dynamic member names.
6474
/// </summary>

src/System.Linq.Dynamic.Core/Parser/ExpressionHelper.cs

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ namespace System.Linq.Dynamic.Core.Parser;
1111

1212
internal class ExpressionHelper : IExpressionHelper
1313
{
14+
private readonly Expression _nullExpression = Expression.Constant(null);
1415
private readonly IConstantExpressionWrapper _constantExpressionWrapper = new ConstantExpressionWrapper();
1516
private readonly ParsingConfig _parsingConfig;
1617

@@ -340,7 +341,7 @@ public bool TryGenerateAndAlsoNotNullExpression(Expression sourceExpression, boo
340341
// Convert all expressions into '!= null' expressions (only if the type can be null)
341342
var binaryExpressions = expressions
342343
.Where(expression => TypeHelper.TypeCanBeNull(expression.Type))
343-
.Select(expression => Expression.NotEqual(expression, Expression.Constant(null)))
344+
.Select(expression => Expression.NotEqual(expression, _nullExpression))
344345
.ToArray();
345346

346347
// Convert all binary expressions into `AndAlso(...)`
@@ -393,16 +394,46 @@ public bool TryConvertTypes(ref Expression left, ref Expression right)
393394

394395
if (left.Type == typeof(object))
395396
{
397+
if (left is ConditionalExpression ce)
398+
{
399+
var rightTypeAsNullableType = TypeHelper.GetNullableType(right.Type);
400+
401+
right = Expression.Convert(right, rightTypeAsNullableType);
402+
403+
left = Expression.Condition(
404+
ce.Test,
405+
Expression.Convert(ce.IfTrue, rightTypeAsNullableType),
406+
Expression.Convert(_nullExpression, rightTypeAsNullableType)
407+
);
408+
409+
return true;
410+
}
411+
396412
left = Expression.Condition(
397-
Expression.Equal(left, Expression.Constant(null, typeof(object))),
413+
Expression.Equal(left, _nullExpression),
398414
GenerateDefaultExpression(right.Type),
399415
Expression.Convert(left, right.Type)
400416
);
401417
}
402418
else if (right.Type == typeof(object))
403419
{
420+
if (right is ConditionalExpression ce)
421+
{
422+
var leftTypeAsNullableType = TypeHelper.GetNullableType(left.Type);
423+
424+
left = Expression.Convert(left, leftTypeAsNullableType);
425+
426+
right = Expression.Condition(
427+
ce.Test,
428+
Expression.Convert(ce.IfTrue, leftTypeAsNullableType),
429+
Expression.Convert(_nullExpression, leftTypeAsNullableType)
430+
);
431+
432+
return true;
433+
}
434+
404435
right = Expression.Condition(
405-
Expression.Equal(right, Expression.Constant(null, typeof(object))),
436+
Expression.Equal(right, _nullExpression),
406437
GenerateDefaultExpression(left.Type),
407438
Expression.Convert(right, left.Type)
408439
);

src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1921,11 +1921,35 @@ private Expression ParseMemberAccess(Type? type, Expression? expression, string?
19211921

19221922
if (!_parsingConfig.DisableMemberAccessToIndexAccessorFallback && extraCheck)
19231923
{
1924-
var indexerName = TypeHelper.IsDynamicClass(type!) ? DynamicClass.IndexerName : "Item";
1924+
var isDynamicClass = TypeHelper.IsDynamicClass(type!);
1925+
var indexerName = isDynamicClass ? DynamicClass.IndexerName : "Item";
1926+
1927+
// Try to get the indexer property "Item" or "DynamicClass_Indexer" which takes a string as parameter
19251928
var indexerMethod = expression?.Type.GetMethod($"get_{indexerName}", [typeof(string)]);
19261929
if (indexerMethod != null)
19271930
{
1928-
return Expression.Call(expression, indexerMethod, Expression.Constant(id));
1931+
if (!isDynamicClass)
1932+
{
1933+
return Expression.Call(expression, indexerMethod, Expression.Constant(id));
1934+
}
1935+
1936+
var containsPropertyMethod = typeof(DynamicClass).GetMethod("ContainsProperty");
1937+
if (containsPropertyMethod == null)
1938+
{
1939+
return Expression.Call(expression, indexerMethod, Expression.Constant(id));
1940+
}
1941+
1942+
var callContainsPropertyExpression = Expression.Call(
1943+
expression!,
1944+
containsPropertyMethod,
1945+
Expression.Constant(id)
1946+
);
1947+
1948+
return Expression.Condition(
1949+
Expression.Equal(callContainsPropertyExpression, Expression.Constant(true)),
1950+
Expression.Call(expression, indexerMethod, Expression.Constant(id)),
1951+
Expression.Constant(null)
1952+
);
19291953
}
19301954
}
19311955

test/System.Linq.Dynamic.Core.NewtonsoftJson.Tests/NewtonsoftJsonTests.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,12 +517,14 @@ public void Where_With_Select()
517517
[InlineData("notExisting == \"1\"")]
518518
[InlineData("notExisting == \"something\"")]
519519
[InlineData("notExisting > 1")]
520+
[InlineData("notExisting < 1")]
520521
[InlineData("true == notExisting")]
521522
[InlineData("\"true\" == notExisting")]
522523
[InlineData("1 == notExisting")]
523524
[InlineData("\"1\" == notExisting")]
524525
[InlineData("\"something\" == notExisting")]
525526
[InlineData("1 < notExisting")]
527+
[InlineData("1 > notExisting")]
526528
public void Where_NonExistingMember_EmptyResult(string predicate)
527529
{
528530
// Arrange

test/System.Linq.Dynamic.Core.SystemTextJson.Tests/SystemTextJsonTests.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -546,12 +546,14 @@ public void Where_With_Select()
546546
[InlineData("notExisting == \"1\"")]
547547
[InlineData("notExisting == \"something\"")]
548548
[InlineData("notExisting > 1")]
549+
[InlineData("notExisting < 1")]
549550
[InlineData("true == notExisting")]
550551
[InlineData("\"true\" == notExisting")]
551552
[InlineData("1 == notExisting")]
552553
[InlineData("\"1\" == notExisting")]
553554
[InlineData("\"something\" == notExisting")]
554555
[InlineData("1 < notExisting")]
556+
[InlineData("1 > notExisting")]
555557
public void Where_NonExistingMember_EmptyResult(string predicate)
556558
{
557559
// Act

0 commit comments

Comments
 (0)