Skip to content

Commit 07c9763

Browse files
committed
Allow properties or fields to contain periods
1 parent b45ef61 commit 07c9763

File tree

1 file changed

+98
-17
lines changed

1 file changed

+98
-17
lines changed

src/Mapster/Utils/ExpressionEx.cs

Lines changed: 98 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
using System.Linq.Expressions;
2-
using System;
1+
using System;
32
using System.Collections;
43
using System.Collections.Generic;
4+
using System.Diagnostics.CodeAnalysis;
55
using System.Linq;
6+
using System.Linq.Expressions;
67
using System.Reflection;
78

89
namespace Mapster.Utils
@@ -19,32 +20,112 @@ public static Expression Assign(Expression left, Expression right)
1920

2021
public static Expression PropertyOrFieldPath(Expression expr, string path)
2122
{
22-
var props = path.Split('.');
23-
return props.Aggregate(expr, PropertyOrField);
23+
Expression current = expr;
24+
string[] props = path.Split('.');
25+
26+
for (int i = 0; i < props.Length; i++)
27+
{
28+
if (IsDictionaryKey(current, props[i], out Expression? next))
29+
{
30+
current = next;
31+
continue;
32+
}
33+
34+
if (IsPropertyOrField(current, props[i], out next))
35+
{
36+
current = next;
37+
continue;
38+
}
39+
40+
// For dynamically built types, it is possible to have periods in the property name.
41+
// Rejoin an incrementing number of parts with periods to try and find a property match.
42+
if (IsPropertyOrFieldPathWithPeriods(current, props[i..], out next, out int combinationLength))
43+
{
44+
current = next;
45+
i += combinationLength - 1;
46+
continue;
47+
}
48+
49+
throw new ArgumentException($"'{props[i]}' is not a member of type '{current.Type.FullName}'", nameof(path));
50+
}
51+
52+
return current;
2453
}
2554

26-
private static Expression PropertyOrField(Expression expr, string prop)
55+
private static bool IsPropertyOrFieldPathWithPeriods(Expression expr, string[] path, [NotNullWhen(true)] out Expression? propExpr, out int combinationLength)
56+
{
57+
if (path.Length < 2)
58+
{
59+
propExpr = null;
60+
combinationLength = 0;
61+
return false;
62+
}
63+
64+
for (int count = 2; count <= path.Length; count++)
65+
{
66+
string prop = string.Join('.', path[..count]);
67+
if (IsPropertyOrField(expr, prop, out propExpr))
68+
{
69+
combinationLength = count;
70+
return true;
71+
}
72+
}
73+
74+
propExpr = null;
75+
combinationLength = 0;
76+
return false;
77+
}
78+
79+
private static bool IsDictionaryKey(Expression expr, string prop, [NotNullWhen(true)] out Expression? propExpr)
2780
{
2881
var type = expr.Type;
2982
var dictType = type.GetDictionaryType();
30-
if (dictType?.GetGenericArguments()[0] == typeof(string))
31-
{
32-
var method = typeof(MapsterHelper).GetMethods()
33-
.First(m => m.Name == nameof(MapsterHelper.GetValueOrDefault) && m.GetParameters()[0].ParameterType.Name == dictType.Name)
34-
.MakeGenericMethod(dictType.GetGenericArguments());
3583

36-
return Expression.Call(method, expr.To(type), Expression.Constant(prop));
84+
if (dictType?.GetGenericArguments()[0] != typeof(string))
85+
{
86+
propExpr = null;
87+
return false;
3788
}
3889

90+
var method = typeof(MapsterHelper).GetMethods()
91+
.First(m => m.Name == nameof(MapsterHelper.GetValueOrDefault) && m.GetParameters()[0].ParameterType.Name == dictType.Name)
92+
.MakeGenericMethod(dictType.GetGenericArguments());
93+
94+
propExpr = Expression.Call(method, expr.To(type), Expression.Constant(prop));
95+
return true;
96+
}
97+
98+
private static bool IsPropertyOrField(Expression expr, string prop, [NotNullWhen(true)] out Expression? propExpr)
99+
{
100+
Type type = expr.Type;
101+
39102
if (type.GetTypeInfo().IsInterface)
40103
{
41104
var allTypes = type.GetAllInterfaces();
42105
var flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
43106
var interfaceType = allTypes.FirstOrDefault(it => it.GetProperty(prop, flags) != null || it.GetField(prop, flags) != null);
44107
if (interfaceType != null)
108+
{
45109
expr = Expression.Convert(expr, interfaceType);
110+
type = expr.Type;
111+
}
46112
}
47-
return Expression.PropertyOrField(expr, prop);
113+
114+
MemberInfo? propertyOrField = type
115+
.GetMember(
116+
prop,
117+
MemberTypes.Field | MemberTypes.Property,
118+
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy)
119+
.FirstOrDefault();
120+
121+
propExpr = propertyOrField?.MemberType switch
122+
{
123+
MemberTypes.Property => Expression.Property(expr, (PropertyInfo)propertyOrField),
124+
MemberTypes.Field => Expression.Field(expr, (FieldInfo)propertyOrField),
125+
_ => null
126+
};
127+
128+
return propExpr != null;
48129
}
49130

50131
private static bool IsReferenceAssignableFrom(this Type destType, Type srcType)
@@ -136,14 +217,14 @@ public static Delegate Compile(this LambdaExpression exp, CompileArgument arg)
136217
if (source.Type.IsArray)
137218
{
138219
return source.Type.GetArrayRank() == 1
139-
? (Expression?) Expression.ArrayLength(source)
220+
? (Expression?)Expression.ArrayLength(source)
140221
: Expression.Property(source, "Length");
141222
}
142223
else
143224
{
144225
var countProperty = GetCount(source.Type);
145-
return countProperty != null
146-
? Expression.Property(source, countProperty)
226+
return countProperty != null
227+
? Expression.Property(source, countProperty)
147228
: null;
148229
}
149230
}
@@ -331,11 +412,11 @@ public static Expression ApplyNullPropagation(this Expression getter)
331412
var result = getter;
332413
while (current.NodeType == ExpressionType.MemberAccess)
333414
{
334-
var memEx = (MemberExpression) current;
415+
var memEx = (MemberExpression)current;
335416
var expr = memEx.Expression;
336417
if (expr == null)
337418
break;
338-
if (expr.NodeType == ExpressionType.Parameter)
419+
if (expr.NodeType == ExpressionType.Parameter)
339420
return result;
340421

341422
if (expr.CanBeNull())

0 commit comments

Comments
 (0)