15
15
*/
16
16
package org .springframework .hateoas .mediatype ;
17
17
18
- import lombok .AccessLevel ;
19
- import lombok .RequiredArgsConstructor ;
20
-
18
+ import javax .validation .constraints .NotNull ;
19
+ import javax .validation .constraints .Pattern ;
21
20
import java .beans .PropertyDescriptor ;
22
21
import java .lang .annotation .Annotation ;
23
22
import java .lang .reflect .Field ;
28
27
import java .util .stream .Collectors ;
29
28
import java .util .stream .Stream ;
30
29
31
- import javax .validation .constraints .NotNull ;
32
- import javax .validation .constraints .Pattern ;
33
-
30
+ import com .fasterxml .jackson .annotation .JsonIgnore ;
31
+ import com .fasterxml .jackson .annotation .JsonIgnoreProperties ;
32
+ import com .fasterxml .jackson .annotation .JsonProperty ;
33
+ import com .fasterxml .jackson .annotation .JsonProperty .Access ;
34
+ import com .fasterxml .jackson .annotation .JsonUnwrapped ;
35
+ import lombok .AccessLevel ;
36
+ import lombok .RequiredArgsConstructor ;
34
37
import org .reactivestreams .Publisher ;
35
38
import org .springframework .beans .BeanUtils ;
36
39
import org .springframework .beans .BeanWrapper ;
45
48
import org .springframework .hateoas .EntityModel ;
46
49
import org .springframework .http .HttpEntity ;
47
50
import org .springframework .lang .Nullable ;
48
- import org .springframework .util .Assert ;
49
- import org .springframework .util .ClassUtils ;
50
- import org .springframework .util .ConcurrentReferenceHashMap ;
51
- import org .springframework .util .ReflectionUtils ;
52
- import org .springframework .util .StringUtils ;
53
-
54
- import com .fasterxml .jackson .annotation .JsonIgnore ;
55
- import com .fasterxml .jackson .annotation .JsonIgnoreProperties ;
56
- import com .fasterxml .jackson .annotation .JsonProperty ;
57
- import com .fasterxml .jackson .annotation .JsonProperty .Access ;
51
+ import org .springframework .util .*;
58
52
59
53
/**
60
54
* @author Greg Turnquist
@@ -85,6 +79,10 @@ static List<Class<?>> getTypesToUnwrap() {
85
79
}
86
80
87
81
public static Map <String , Object > extractPropertyValues (@ Nullable Object object ) {
82
+ return extractPropertyValues (object , false );
83
+ }
84
+
85
+ public static Map <String , Object > extractPropertyValues (@ Nullable Object object , boolean unwrapEligibleProperties ) {
88
86
89
87
if (object == null ) {
90
88
return Collections .emptyMap ();
@@ -98,7 +96,10 @@ public static Map<String, Object> extractPropertyValues(@Nullable Object object)
98
96
99
97
return getExposedProperties (object .getClass ()).stream () //
100
98
.map (PropertyMetadata ::getName )
101
- .collect (HashMap ::new , (map , name ) -> map .put (name , wrapper .getPropertyValue (name )), HashMap ::putAll );
99
+ .map (name -> unwrapEligibleProperties ? unwrapPropertyIfNeeded (name , wrapper ) :
100
+ Collections .singletonMap (name , wrapper .getPropertyValue (name )))
101
+ .flatMap (it -> it .entrySet ().stream ())
102
+ .collect (HashMap ::new , (map , it ) -> map .put (it .getKey (), it .getValue ()), HashMap ::putAll );
102
103
}
103
104
104
105
public static <T > T createObjectFromProperties (Class <T > clazz , Map <String , Object > properties ) {
@@ -113,7 +114,6 @@ public static <T> T createObjectFromProperties(Class<T> clazz, Map<String, Objec
113
114
Method writeMethod = property .getWriteMethod ();
114
115
ReflectionUtils .makeAccessible (writeMethod );
115
116
writeMethod .invoke (obj , value );
116
-
117
117
} catch (IllegalAccessException | InvocationTargetException e ) {
118
118
119
119
throw new RuntimeException (e );
@@ -151,6 +151,33 @@ public static InputPayloadMetadata getExposedProperties(@Nullable ResolvableType
151
151
});
152
152
}
153
153
154
+ private static Map <String , Object > unwrapPropertyIfNeeded (String propertyName , BeanWrapper wrapper ) {
155
+ Field descriptorField = ReflectionUtils .findField (wrapper .getWrappedClass (), propertyName );
156
+ Method readMethod = wrapper .getPropertyDescriptor (propertyName ).getReadMethod ();
157
+
158
+ MergedAnnotation <JsonUnwrapped > unwrappedAnnotation =
159
+ Stream .of (descriptorField , readMethod )
160
+ .filter (Objects ::nonNull )
161
+ .map (MergedAnnotations ::from )
162
+ .flatMap (mergedAnnotations -> mergedAnnotations .stream (JsonUnwrapped .class ))
163
+ .filter (it -> it .getBoolean ("enabled" ))
164
+ .findFirst ()
165
+ .orElse (null );
166
+
167
+ Object propertyValue = wrapper .getPropertyValue (propertyName );
168
+ if (unwrappedAnnotation == null ) {
169
+ return Collections .singletonMap (propertyName , propertyValue );
170
+ }
171
+
172
+ String prefix = unwrappedAnnotation .getString ("prefix" );
173
+ String suffix = unwrappedAnnotation .getString ("suffix" );
174
+
175
+ Map <String , Object > properties = new HashMap <>();
176
+ extractPropertyValues (propertyValue , true )
177
+ .forEach ((name , value ) -> properties .put (prefix + name + suffix , value ));
178
+ return properties ;
179
+ }
180
+
154
181
private static ResolvableType unwrapDomainType (ResolvableType type ) {
155
182
156
183
if (!type .hasGenerics ()) {
@@ -169,7 +196,7 @@ private static ResolvableType unwrapDomainType(ResolvableType type) {
169
196
* Replaces the given {@link ResolvableType} with the one produced by the given {@link Supplier} if the former is
170
197
* assignable from one of the types to be unwrapped.
171
198
*
172
- * @param type must not be {@literal null}.
199
+ * @param type must not be {@literal null}.
173
200
* @param mapper must not be {@literal null}.
174
201
* @return
175
202
* @see #TYPES_TO_UNWRAP
@@ -188,8 +215,8 @@ private static Stream<PropertyMetadata> lookupExposedProperties(@Nullable Class<
188
215
return type == null //
189
216
? Stream .empty () //
190
217
: 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 ));
218
+ .map (it -> new AnnotatedProperty (new Property (type , it .getReadMethod (), it .getWriteMethod ())))
219
+ .map (it -> JSR_303_PRESENT ? new Jsr303AwarePropertyMetadata (it ) : new DefaultPropertyMetadata (it ));
193
220
}
194
221
195
222
/**
@@ -359,7 +386,7 @@ public boolean hasWriteMethod() {
359
386
/**
360
387
* Returns the {@link MergedAnnotation} of the given type.
361
388
*
362
- * @param <T> the annotation type.
389
+ * @param <T> the annotation type.
363
390
* @param type must not be {@literal null}.
364
391
* @return the {@link MergedAnnotation} if available or {@link MergedAnnotation#missing()} if not.
365
392
*/
@@ -457,7 +484,6 @@ public ResolvableType getType() {
457
484
public int compareTo (DefaultPropertyMetadata that ) {
458
485
return BY_NAME .compare (this , that );
459
486
}
460
-
461
487
}
462
488
463
489
/**
0 commit comments