Skip to content

Commit d8f7693

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

File tree

1 file changed

+88
-6
lines changed

1 file changed

+88
-6
lines changed

src/Mapster/Utils/ExpressionEx.cs

Lines changed: 88 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
using System;
33
using System.Collections;
44
using System.Collections.Generic;
5+
using System.Diagnostics.CodeAnalysis;
56
using System.Linq;
7+
using System.Linq.Expressions;
68
using System.Reflection;
79

810
namespace Mapster.Utils
@@ -19,32 +21,112 @@ public static Expression Assign(Expression left, Expression right)
1921

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

26-
private static Expression PropertyOrField(Expression expr, string prop)
56+
private static bool IsPropertyOrFieldPathWithPeriods(Expression expr, string[] path, [NotNullWhen(true)] out Expression? propExpr, out int combinationLength)
57+
{
58+
if (path.Length < 2)
59+
{
60+
propExpr = null;
61+
combinationLength = 0;
62+
return false;
63+
}
64+
65+
for (int count = 2; count <= path.Length; count++)
66+
{
67+
string prop = string.Join('.', path[..count]);
68+
if (IsPropertyOrField(expr, prop, out propExpr))
69+
{
70+
combinationLength = count;
71+
return true;
72+
}
73+
}
74+
75+
propExpr = null;
76+
combinationLength = 0;
77+
return false;
78+
}
79+
80+
private static bool IsDictionaryKey(Expression expr, string prop, [NotNullWhen(true)] out Expression? propExpr)
2781
{
2882
var type = expr.Type;
2983
var dictType = type.GetDictionaryType();
30-
if (dictType?.GetGenericArguments()[0] == typeof(string))
84+
85+
if (dictType?.GetGenericArguments()[0] != typeof(string))
3186
{
87+
propExpr = null;
88+
return false;
89+
}
90+
3291
var method = typeof(MapsterHelper).GetMethods()
3392
.First(m => m.Name == nameof(MapsterHelper.GetValueOrDefault) && m.GetParameters()[0].ParameterType.Name == dictType.Name)
3493
.MakeGenericMethod(dictType.GetGenericArguments());
3594

36-
return Expression.Call(method, expr.To(type), Expression.Constant(prop));
95+
propExpr = Expression.Call(method, expr.To(type), Expression.Constant(prop));
96+
return true;
3797
}
3898

99+
private static bool IsPropertyOrField(Expression expr, string prop, [NotNullWhen(true)] out Expression? propExpr)
100+
{
101+
Type type = expr.Type;
102+
39103
if (type.GetTypeInfo().IsInterface)
40104
{
41105
var allTypes = type.GetAllInterfaces();
42106
var flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
43107
var interfaceType = allTypes.FirstOrDefault(it => it.GetProperty(prop, flags) != null || it.GetField(prop, flags) != null);
44108
if (interfaceType != null)
109+
{
45110
expr = Expression.Convert(expr, interfaceType);
111+
type = expr.Type;
112+
}
46113
}
47-
return Expression.PropertyOrField(expr, prop);
114+
115+
MemberInfo? propertyOrField = type
116+
.GetMember(
117+
prop,
118+
MemberTypes.Field | MemberTypes.Property,
119+
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy)
120+
.FirstOrDefault();
121+
122+
propExpr = propertyOrField?.MemberType switch
123+
{
124+
MemberTypes.Property => Expression.Property(expr, (PropertyInfo)propertyOrField),
125+
MemberTypes.Field => Expression.Field(expr, (FieldInfo)propertyOrField),
126+
_ => null
127+
};
128+
129+
return propExpr != null;
48130
}
49131

50132
private static bool IsReferenceAssignableFrom(this Type destType, Type srcType)

0 commit comments

Comments
 (0)