@@ -78,10 +78,14 @@ public static EntityProjectionIntrospector create(ProjectionFactory projectionFa
7878	 * <p> 
7979	 * Nested properties (direct types, within maps, collections) are introspected for nested projections and contain 
8080	 * property paths for closed projections. 
81+ 	 * <p> 
82+ 	 * Deeply nested types (e.g. {@code Map<?, List<Person>>}) are represented with a property path that uses 
83+ 	 * the unwrapped type and no longer the root domain type {@code D}. 
8184	 * 
82- 	 * @param mappedType 
83- 	 * @param domainType 
84- 	 * @return 
85+ 	 * @param mappedType must not be {@literal null}. 
86+ 	 * @param domainType must not be {@literal null}. 
87+ 	 * @return the introspection result. 
88+ 	 * @see org.springframework.data.mapping.context.EntityProjection.ContainerPropertyProjection 
8589	 */ 
8690	public  <M , D > EntityProjection <M , D > introspect (Class <M > mappedType , Class <D > domainType ) {
8791
@@ -103,8 +107,7 @@ public <M, D> EntityProjection<M, D> introspect(Class<M> mappedType, Class<D> do
103107
104108		PersistentEntity <?, ?> persistentEntity  = mappingContext .getRequiredPersistentEntity (domainType );
105109		List <EntityProjection .PropertyProjection <?, ?>> propertyDescriptors  = getProperties (null , projectionInformation ,
106- 				returnedTypeInformation ,
107- 				persistentEntity , null );
110+ 				returnedTypeInformation , persistentEntity , null );
108111
109112		return  EntityProjection .projecting (returnedTypeInformation , domainTypeInformation , propertyDescriptors , true );
110113	}
@@ -127,44 +130,71 @@ public <M, D> EntityProjection<M, D> introspect(Class<M> mappedType, Class<D> do
127130			CycleGuard  cycleGuardToUse  = cycleGuard  != null  ? cycleGuard  : new  CycleGuard ();
128131
129132			TypeInformation <?> property  = projectionTypeInformation .getRequiredProperty (inputProperty .getName ());
133+ 			TypeInformation <?> actualType  = property .getRequiredActualType ();
134+ 
135+ 			boolean  container  = isContainer (actualType );
130136
131137			PropertyPath  nestedPropertyPath  = propertyPath  == null 
132138					? PropertyPath .from (persistentProperty .getName (), persistentEntity .getTypeInformation ())
133139					: propertyPath .nested (persistentProperty .getName ());
134140
135- 			TypeInformation <?> returnedType  = property .getRequiredActualType ();
136- 			TypeInformation <?> domainType  = persistentProperty .getTypeInformation ().getRequiredActualType ();
141+ 			TypeInformation <?> unwrappedReturnedType  = unwrapContainerType (property .getRequiredActualType ());
142+ 			TypeInformation <?> unwrappedDomainType  = unwrapContainerType (
143+ 					persistentProperty .getTypeInformation ().getRequiredActualType ());
137144
138- 			if  (isProjection (returnedType ,  domainType )) {
145+ 			if  (isProjection (unwrappedReturnedType ,  unwrappedDomainType )) {
139146
140147				List <EntityProjection .PropertyProjection <?, ?>> nestedPropertyDescriptors ;
141148
142149				if  (cycleGuardToUse .isCycleFree (persistentProperty )) {
143- 					nestedPropertyDescriptors  = getProjectedProperties (nestedPropertyPath ,  returnedType ,  domainType ,
144- 							cycleGuardToUse );
150+ 					nestedPropertyDescriptors  = getProjectedProperties (container  ?  null  :  nestedPropertyPath ,
151+ 							unwrappedReturnedType ,  unwrappedDomainType ,  cycleGuardToUse );
145152				} else  {
146153					nestedPropertyDescriptors  = Collections .emptyList ();
147154				}
148155
149- 				propertyDescriptors .add (EntityProjection .PropertyProjection .projecting (nestedPropertyPath , property ,
150- 						persistentProperty .getTypeInformation (),
151- 						nestedPropertyDescriptors , projectionInformation .isClosed ()));
156+ 				if  (container ) {
157+ 					propertyDescriptors .add (EntityProjection .ContainerPropertyProjection .projecting (nestedPropertyPath , property ,
158+ 							persistentProperty .getTypeInformation (), nestedPropertyDescriptors , projectionInformation .isClosed ()));
159+ 				} else  {
160+ 					propertyDescriptors .add (EntityProjection .PropertyProjection .projecting (nestedPropertyPath , property ,
161+ 							persistentProperty .getTypeInformation (), nestedPropertyDescriptors , projectionInformation .isClosed ()));
162+ 				}
163+ 
152164			} else  {
153- 				propertyDescriptors 
154- 						.add (EntityProjection .PropertyProjection .nonProjecting (nestedPropertyPath , property ,
155- 								persistentProperty .getTypeInformation ()));
165+ 				if  (container ) {
166+ 					propertyDescriptors .add (EntityProjection .ContainerPropertyProjection .nonProjecting (nestedPropertyPath ,
167+ 							property , persistentProperty .getTypeInformation ()));
168+ 				} else  {
169+ 					propertyDescriptors .add (EntityProjection .PropertyProjection .nonProjecting (nestedPropertyPath , property ,
170+ 							persistentProperty .getTypeInformation ()));
171+ 				}
156172			}
157173		}
158174
159175		return  propertyDescriptors ;
160176	}
161177
178+ 	private  static  TypeInformation <?> unwrapContainerType (TypeInformation <?> type ) {
179+ 
180+ 		TypeInformation <?> unwrapped  = type ;
181+ 		while  (isContainer (unwrapped )) {
182+ 			unwrapped  = unwrapped .getRequiredActualType ();
183+ 		}
184+ 
185+ 		return  unwrapped ;
186+ 	}
187+ 
188+ 	private  static  boolean  isContainer (TypeInformation <?> actualType ) {
189+ 		return  actualType .isCollectionLike () || actualType .isMap ();
190+ 	}
191+ 
162192	private  boolean  isProjection (TypeInformation <?> returnedType , TypeInformation <?> domainType ) {
163193		return  projectionPredicate .test (returnedType .getRequiredActualType ().getType (),
164194				domainType .getRequiredActualType ().getType ());
165195	}
166196
167- 	private  List <EntityProjection .PropertyProjection <?, ?>> getProjectedProperties (PropertyPath  propertyPath ,
197+ 	private  List <EntityProjection .PropertyProjection <?, ?>> getProjectedProperties (@ Nullable   PropertyPath  propertyPath ,
168198			TypeInformation <?> returnedType , TypeInformation <?> domainType , CycleGuard  cycleGuard ) {
169199
170200		ProjectionInformation  projectionInformation  = projectionFactory .getProjectionInformation (returnedType .getType ());
0 commit comments