@@ -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