22using System ;
33using System . Collections ;
44using System . Collections . Generic ;
5+ using System . Diagnostics . CodeAnalysis ;
56using System . Linq ;
7+ using System . Linq . Expressions ;
68using System . Reflection ;
79
810namespace 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