26
26
import java .util .Map ;
27
27
import java .util .Optional ;
28
28
import java .util .Set ;
29
- import java .util .function .Predicate ;
30
29
31
30
import org .springframework .data .mapping .PersistentEntity ;
32
31
import org .springframework .data .mapping .PersistentProperty ;
33
32
import org .springframework .util .Assert ;
34
33
34
+ import com .fasterxml .jackson .annotation .JsonAnySetter ;
35
35
import com .fasterxml .jackson .databind .BeanDescription ;
36
36
import com .fasterxml .jackson .databind .DeserializationConfig ;
37
37
import com .fasterxml .jackson .databind .ObjectMapper ;
38
38
import com .fasterxml .jackson .databind .SerializationConfig ;
39
- import com .fasterxml .jackson .databind .introspect .BasicClassIntrospector ;
40
39
import com .fasterxml .jackson .databind .introspect .BeanPropertyDefinition ;
41
40
import com .fasterxml .jackson .databind .introspect .ClassIntrospector ;
42
41
50
49
@ RequiredArgsConstructor (access = AccessLevel .PRIVATE )
51
50
class MappedProperties {
52
51
53
- private static final ClassIntrospector INTROSPECTOR = new BasicClassIntrospector ();
54
-
55
52
private final Map <PersistentProperty <?>, BeanPropertyDefinition > propertyToFieldName ;
56
53
private final Map <String , PersistentProperty <?>> fieldNameToProperty ;
57
54
private final Set <BeanPropertyDefinition > unmappedProperties ;
55
+ private final Set <String > ignoredPropertyNames ;
56
+ private final boolean anySetterFound ;
58
57
59
58
/**
60
59
* Creates a new {@link MappedProperties} instance for the given {@link PersistentEntity} and {@link BeanDescription}.
61
60
*
62
61
* @param entity must not be {@literal null}.
63
62
* @param description must not be {@literal null}.
64
63
*/
65
- private MappedProperties (PersistentEntity <?, ? extends PersistentProperty <?>> entity , BeanDescription description ,
66
- Predicate <PersistentProperty <?>> filter ) {
64
+ private MappedProperties (PersistentEntity <?, ? extends PersistentProperty <?>> entity , BeanDescription description ) {
67
65
68
66
Assert .notNull (entity , "Entity must not be null!" );
69
67
Assert .notNull (description , "BeanDescription must not be null!" );
70
68
71
- this .propertyToFieldName = new HashMap <PersistentProperty <?>, BeanPropertyDefinition >();
72
- this .fieldNameToProperty = new HashMap <String , PersistentProperty <?>>();
73
- this .unmappedProperties = new HashSet <BeanPropertyDefinition >();
69
+ this .propertyToFieldName = new HashMap <>();
70
+ this .fieldNameToProperty = new HashMap <>();
71
+ this .unmappedProperties = new HashSet <>();
72
+
73
+ this .anySetterFound = description .findAnySetterAccessor () != null ;
74
+
75
+ // We need to call this method after findAnySetterAccessor above as that triggers the
76
+ // collection of ignored properties in the first place. See
77
+ // https://github.com/FasterXML/jackson-databind/issues/2531
78
+
79
+ this .ignoredPropertyNames = description .getIgnoredPropertyNames ();
74
80
75
81
for (BeanPropertyDefinition property : description .findProperties ()) {
76
82
77
- if (description . getIgnoredPropertyNames () .contains (property .getName ())) {
83
+ if (ignoredPropertyNames .contains (property .getName ())) {
78
84
continue ;
79
85
}
80
86
81
87
Optional <? extends PersistentProperty <?>> persistentProperty = //
82
88
Optional .ofNullable (entity .getPersistentProperty (property .getInternalName ()));
83
89
84
- persistentProperty //
85
- .filter (filter ) //
90
+ persistentProperty //
86
91
.ifPresent (it -> {
87
92
propertyToFieldName .put (it , property );
88
93
fieldNameToProperty .put (property .getName (), it );
@@ -105,10 +110,11 @@ private MappedProperties(PersistentEntity<?, ? extends PersistentProperty<?>> en
105
110
public static MappedProperties forDeserialization (PersistentEntity <?, ?> entity , ObjectMapper mapper ) {
106
111
107
112
DeserializationConfig config = mapper .getDeserializationConfig ();
108
- BeanDescription description = INTROSPECTOR .forDeserialization (config , mapper .constructType (entity .getType ()),
113
+ ClassIntrospector introspector = config .getClassIntrospector ();
114
+ BeanDescription description = introspector .forDeserialization (config , mapper .constructType (entity .getType ()),
109
115
config );
110
116
111
- return new MappedProperties (entity , description , it -> it . isWritable () );
117
+ return new MappedProperties (entity , description );
112
118
}
113
119
114
120
/**
@@ -122,13 +128,15 @@ public static MappedProperties forDeserialization(PersistentEntity<?, ?> entity,
122
128
public static MappedProperties forSerialization (PersistentEntity <?, ?> entity , ObjectMapper mapper ) {
123
129
124
130
SerializationConfig config = mapper .getSerializationConfig ();
125
- BeanDescription description = INTROSPECTOR .forSerialization (config , mapper .constructType (entity .getType ()), config );
131
+ ClassIntrospector introspector = config .getClassIntrospector ();
132
+ BeanDescription description = introspector .forSerialization (config , mapper .constructType (entity .getType ()), config );
126
133
127
- return new MappedProperties (entity , description , it -> true );
134
+ return new MappedProperties (entity , description );
128
135
}
129
136
130
137
public static MappedProperties none () {
131
- return new MappedProperties (Collections .emptyMap (), Collections .emptyMap (), Collections .emptySet ());
138
+ return new MappedProperties (Collections .emptyMap (), Collections .emptyMap (), Collections .emptySet (),
139
+ Collections .emptySet (), false );
132
140
}
133
141
134
142
/**
@@ -196,4 +204,24 @@ public boolean isMappedProperty(PersistentProperty<?> property) {
196
204
197
205
return propertyToFieldName .containsKey (property );
198
206
}
207
+
208
+ /**
209
+ * Returns whether the property is actually writable. I.e. whether there's a non-read-only property on the target type
210
+ * or there's a catch all method annotated with {@link JsonAnySetter}.
211
+ *
212
+ * @param name must not be {@literal null} or empty.
213
+ * @return
214
+ */
215
+ public boolean isWritableProperty (String name ) {
216
+
217
+ Assert .hasText (name , "Property name must not be null or empty!" );
218
+
219
+ if (ignoredPropertyNames .contains (name )) {
220
+ return false ;
221
+ }
222
+
223
+ PersistentProperty <?> property = fieldNameToProperty .get (name );
224
+
225
+ return property != null ? property .isWritable () : anySetterFound ;
226
+ }
199
227
}
0 commit comments