15
15
*/
16
16
package org .springframework .hateoas .mediatype ;
17
17
18
+ import com .fasterxml .jackson .annotation .JsonUnwrapped ;
18
19
import lombok .AccessLevel ;
19
20
import lombok .RequiredArgsConstructor ;
20
21
@@ -85,6 +86,10 @@ static List<Class<?>> getTypesToUnwrap() {
85
86
}
86
87
87
88
public static Map <String , Object > extractPropertyValues (@ Nullable Object object ) {
89
+ return extractPropertyValues (object , false );
90
+ }
91
+
92
+ public static Map <String , Object > extractPropertyValues (@ Nullable Object object , boolean unwrapEligibleProperties ) {
88
93
89
94
if (object == null ) {
90
95
return Collections .emptyMap ();
@@ -98,7 +103,10 @@ public static Map<String, Object> extractPropertyValues(@Nullable Object object)
98
103
99
104
return getExposedProperties (object .getClass ()).stream () //
100
105
.map (PropertyMetadata ::getName )
101
- .collect (HashMap ::new , (map , name ) -> map .put (name , wrapper .getPropertyValue (name )), HashMap ::putAll );
106
+ .map (name -> unwrapEligibleProperties ? unwrapPropertyIfNeeded (name , wrapper ) :
107
+ Collections .singletonMap (name , wrapper .getPropertyValue (name )))
108
+ .flatMap (it -> it .entrySet ().stream ())
109
+ .collect (HashMap ::new , (map , it ) -> map .put (it .getKey (), it .getValue ()), HashMap ::putAll );
102
110
}
103
111
104
112
public static <T > T createObjectFromProperties (Class <T > clazz , Map <String , Object > properties ) {
@@ -113,7 +121,6 @@ public static <T> T createObjectFromProperties(Class<T> clazz, Map<String, Objec
113
121
Method writeMethod = property .getWriteMethod ();
114
122
ReflectionUtils .makeAccessible (writeMethod );
115
123
writeMethod .invoke (obj , value );
116
-
117
124
} catch (IllegalAccessException | InvocationTargetException e ) {
118
125
119
126
throw new RuntimeException (e );
@@ -151,6 +158,33 @@ public static InputPayloadMetadata getExposedProperties(@Nullable ResolvableType
151
158
});
152
159
}
153
160
161
+ private static Map <String , Object > unwrapPropertyIfNeeded (String propertyName , BeanWrapper wrapper ) {
162
+ Field descriptorField = ReflectionUtils .findField (wrapper .getWrappedClass (), propertyName );
163
+ Method readMethod = wrapper .getPropertyDescriptor (propertyName ).getReadMethod ();
164
+
165
+ MergedAnnotation <JsonUnwrapped > unwrappedAnnotation =
166
+ Stream .of (descriptorField , readMethod )
167
+ .filter (Objects ::nonNull )
168
+ .map (MergedAnnotations ::from )
169
+ .flatMap (mergedAnnotations -> mergedAnnotations .stream (JsonUnwrapped .class ))
170
+ .filter (it -> it .getBoolean ("enabled" ))
171
+ .findFirst ()
172
+ .orElse (null );
173
+
174
+ Object propertyValue = wrapper .getPropertyValue (propertyName );
175
+ if (unwrappedAnnotation == null ) {
176
+ return Collections .singletonMap (propertyName , propertyValue );
177
+ }
178
+
179
+ String prefix = unwrappedAnnotation .getString ("prefix" );
180
+ String suffix = unwrappedAnnotation .getString ("suffix" );
181
+
182
+ Map <String , Object > properties = new HashMap <>();
183
+ extractPropertyValues (propertyValue , true )
184
+ .forEach ((name , value ) -> properties .put (prefix + name + suffix , value ));
185
+ return properties ;
186
+ }
187
+
154
188
private static ResolvableType unwrapDomainType (ResolvableType type ) {
155
189
156
190
if (!type .hasGenerics ()) {
@@ -169,7 +203,7 @@ private static ResolvableType unwrapDomainType(ResolvableType type) {
169
203
* Replaces the given {@link ResolvableType} with the one produced by the given {@link Supplier} if the former is
170
204
* assignable from one of the types to be unwrapped.
171
205
*
172
- * @param type must not be {@literal null}.
206
+ * @param type must not be {@literal null}.
173
207
* @param mapper must not be {@literal null}.
174
208
* @return
175
209
* @see #TYPES_TO_UNWRAP
@@ -188,8 +222,8 @@ private static Stream<PropertyMetadata> lookupExposedProperties(@Nullable Class<
188
222
return type == null //
189
223
? Stream .empty () //
190
224
: getPropertyDescriptors (type ) //
191
- .map (it -> new AnnotatedProperty (new Property (type , it .getReadMethod (), it .getWriteMethod ())))
192
- .map (it -> JSR_303_PRESENT ? new Jsr303AwarePropertyMetadata (it ) : new DefaultPropertyMetadata (it ));
225
+ .map (it -> new AnnotatedProperty (new Property (type , it .getReadMethod (), it .getWriteMethod ())))
226
+ .map (it -> JSR_303_PRESENT ? new Jsr303AwarePropertyMetadata (it ) : new DefaultPropertyMetadata (it ));
193
227
}
194
228
195
229
/**
@@ -359,7 +393,7 @@ public boolean hasWriteMethod() {
359
393
/**
360
394
* Returns the {@link MergedAnnotation} of the given type.
361
395
*
362
- * @param <T> the annotation type.
396
+ * @param <T> the annotation type.
363
397
* @param type must not be {@literal null}.
364
398
* @return the {@link MergedAnnotation} if available or {@link MergedAnnotation#missing()} if not.
365
399
*/
@@ -457,7 +491,6 @@ public ResolvableType getType() {
457
491
public int compareTo (DefaultPropertyMetadata that ) {
458
492
return BY_NAME .compare (this , that );
459
493
}
460
-
461
494
}
462
495
463
496
/**
0 commit comments