|
12 | 12 | import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
|
13 | 13 | import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
|
14 | 14 | import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
|
| 15 | +import com.fasterxml.jackson.databind.node.JsonNodeFactory; |
| 16 | +import com.fasterxml.jackson.databind.node.ObjectNode; |
15 | 17 | import com.fasterxml.jackson.databind.util.ClassUtil;
|
16 | 18 |
|
17 | 19 | /**
|
18 | 20 | * Class that represents a "wildcard" set method which can be used
|
19 | 21 | * to generically set values of otherwise unmapped (aka "unknown")
|
20 | 22 | * properties read from JSON content.
|
21 | 23 | *<p>
|
22 |
| - * !!! Note: might make sense to refactor to share some code |
23 |
| - * with {@link SettableBeanProperty}? |
| 24 | + * Note: starting with 2.14, is {@code abstract} class with multiple |
| 25 | + * concrete implementations |
24 | 26 | */
|
25 |
| -public class SettableAnyProperty |
| 27 | +public abstract class SettableAnyProperty |
26 | 28 | implements java.io.Serializable
|
27 | 29 | {
|
28 | 30 | private static final long serialVersionUID = 1L;
|
@@ -70,11 +72,54 @@ public SettableAnyProperty(BeanProperty property, AnnotatedMember setter, JavaTy
|
70 | 72 | _setterIsField = setter instanceof AnnotatedField;
|
71 | 73 | }
|
72 | 74 |
|
73 |
| - public SettableAnyProperty withValueDeserializer(JsonDeserializer<Object> deser) { |
74 |
| - return new SettableAnyProperty(_property, _setter, _type, |
75 |
| - _keyDeserializer, deser, _valueTypeDeserializer); |
| 75 | + /** |
| 76 | + * @since 2.14 |
| 77 | + */ |
| 78 | + public static SettableAnyProperty constructForMethod(DeserializationContext ctxt, |
| 79 | + BeanProperty property, |
| 80 | + AnnotatedMember field, JavaType valueType, |
| 81 | + KeyDeserializer keyDeser, |
| 82 | + JsonDeserializer<Object> valueDeser, TypeDeserializer typeDeser) { |
| 83 | + return new MethodAnyProperty(property, field, valueType, |
| 84 | + keyDeser, valueDeser, typeDeser); |
| 85 | + } |
| 86 | + |
| 87 | + /** |
| 88 | + * @since 2.14 |
| 89 | + */ |
| 90 | + public static SettableAnyProperty constructForMapField(DeserializationContext ctxt, |
| 91 | + BeanProperty property, |
| 92 | + AnnotatedMember field, JavaType valueType, |
| 93 | + KeyDeserializer keyDeser, |
| 94 | + JsonDeserializer<Object> valueDeser, TypeDeserializer typeDeser) |
| 95 | + { |
| 96 | + Class<?> mapType = field.getRawType(); |
| 97 | + // 02-Aug-2022, tatu: Ideally would be resolved to a concrete type by caller but |
| 98 | + // alas doesn't appear to happen. Nor does `BasicDeserializerFactory` expose method |
| 99 | + // for finding default or explicit mappings. |
| 100 | + if (mapType == Map.class) { |
| 101 | + mapType = LinkedHashMap.class; |
| 102 | + } |
| 103 | + ValueInstantiator vi = JDKValueInstantiators.findStdValueInstantiator(ctxt.getConfig(), mapType); |
| 104 | + return new MapFieldAnyProperty(property, field, valueType, |
| 105 | + keyDeser, valueDeser, typeDeser, |
| 106 | + vi); |
76 | 107 | }
|
77 | 108 |
|
| 109 | + /** |
| 110 | + * @since 2.14 |
| 111 | + */ |
| 112 | + public static SettableAnyProperty constructForJsonNodeField(DeserializationContext ctxt, |
| 113 | + BeanProperty property, |
| 114 | + AnnotatedMember field, JavaType valueType, JsonDeserializer<Object> valueDeser) { |
| 115 | + return new JsonNodeFieldAnyProperty(property, field, valueType, |
| 116 | + valueDeser, |
| 117 | + ctxt.getNodeFactory()); |
| 118 | + } |
| 119 | + |
| 120 | + // Abstract @since 2.14 |
| 121 | + public abstract SettableAnyProperty withValueDeserializer(JsonDeserializer<Object> deser); |
| 122 | + |
78 | 123 | public void fixAccess(DeserializationConfig config) {
|
79 | 124 | _setter.fixAccess(
|
80 | 125 | config.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS));
|
@@ -109,6 +154,11 @@ Object readResolve() {
|
109 | 154 |
|
110 | 155 | public JavaType getType() { return _type; }
|
111 | 156 |
|
| 157 | + /** |
| 158 | + * @since 2.14 |
| 159 | + */ |
| 160 | + public String getPropertyName() { return _property.getName(); } |
| 161 | + |
112 | 162 | /*
|
113 | 163 | /**********************************************************
|
114 | 164 | /* Public API, deserialization
|
@@ -148,53 +198,19 @@ public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOEx
|
148 | 198 | return _valueDeserializer.deserialize(p, ctxt);
|
149 | 199 | }
|
150 | 200 |
|
151 |
| - @SuppressWarnings("unchecked") |
| 201 | + // Default implementation since 2.14 |
152 | 202 | public void set(Object instance, Object propName, Object value) throws IOException
|
153 | 203 | {
|
154 | 204 | try {
|
155 |
| - // if annotation in the field (only map is supported now) |
156 |
| - if (_setterIsField) { |
157 |
| - AnnotatedField field = (AnnotatedField) _setter; |
158 |
| - Map<Object,Object> val = (Map<Object,Object>) field.getValue(instance); |
159 |
| - // 01-Aug-2022, tatu: [databind#3559] Will try to create and assign an |
160 |
| - // instance. |
161 |
| - if (val == null) { |
162 |
| - val = _createAndSetMap(null, field, instance, propName); |
163 |
| - } |
164 |
| - // add the property key and value |
165 |
| - val.put(propName, value); |
166 |
| - } else { |
167 |
| - // note: cannot use 'setValue()' due to taking 2 args |
168 |
| - ((AnnotatedMethod) _setter).callOnWith(instance, propName, value); |
169 |
| - } |
| 205 | + _set(instance, propName, value); |
170 | 206 | } catch (IOException e) {
|
171 | 207 | throw e;
|
172 | 208 | } catch (Exception e) {
|
173 | 209 | _throwAsIOE(e, propName, value);
|
174 | 210 | }
|
175 | 211 | }
|
176 | 212 |
|
177 |
| - @SuppressWarnings("unchecked") |
178 |
| - protected Map<Object, Object> _createAndSetMap(DeserializationContext ctxt, AnnotatedField field, |
179 |
| - Object instance, Object propName) |
180 |
| - throws IOException |
181 |
| - { |
182 |
| - Class<?> mapType = field.getRawType(); |
183 |
| - // Ideally would be resolved to a concrete type but if not: |
184 |
| - if (mapType == Map.class) { |
185 |
| - mapType = LinkedHashMap.class; |
186 |
| - } |
187 |
| - // We know that DeserializationContext not actually required: |
188 |
| - ValueInstantiator vi = JDKValueInstantiators.findStdValueInstantiator(null, mapType); |
189 |
| - if (vi == null) { |
190 |
| - throw JsonMappingException.from(ctxt, String.format( |
191 |
| - "Cannot create an instance of %s for use as \"any-setter\" '%s'", |
192 |
| - ClassUtil.nameOf(mapType), _property.getName())); |
193 |
| - } |
194 |
| - Map<Object,Object> map = (Map<Object,Object>) vi.createUsingDefault(ctxt); |
195 |
| - field.setValue(instance, map); |
196 |
| - return map; |
197 |
| - } |
| 213 | + protected abstract void _set(Object instance, Object propName, Object value) throws Exception; |
198 | 214 |
|
199 | 215 | /*
|
200 | 216 | /**********************************************************
|
@@ -259,4 +275,145 @@ public void handleResolvedForwardReference(Object id, Object value)
|
259 | 275 | _parent.set(_pojo, _propName, value);
|
260 | 276 | }
|
261 | 277 | }
|
| 278 | + |
| 279 | + /* |
| 280 | + /********************************************************************** |
| 281 | + /* Concrete implementations |
| 282 | + /********************************************************************** |
| 283 | + */ |
| 284 | + |
| 285 | + /** |
| 286 | + * @since 2.14 |
| 287 | + */ |
| 288 | + protected static class MethodAnyProperty extends SettableAnyProperty |
| 289 | + implements java.io.Serializable |
| 290 | + { |
| 291 | + private static final long serialVersionUID = 1L; |
| 292 | + |
| 293 | + public MethodAnyProperty(BeanProperty property, |
| 294 | + AnnotatedMember field, JavaType valueType, |
| 295 | + KeyDeserializer keyDeser, |
| 296 | + JsonDeserializer<Object> valueDeser, TypeDeserializer typeDeser) { |
| 297 | + super(property, field, valueType, |
| 298 | + keyDeser, valueDeser, typeDeser); |
| 299 | + } |
| 300 | + |
| 301 | + @Override |
| 302 | + protected void _set(Object instance, Object propName, Object value) throws Exception |
| 303 | + { |
| 304 | + // note: cannot use 'setValue()' due to taking 2 args |
| 305 | + ((AnnotatedMethod) _setter).callOnWith(instance, propName, value); |
| 306 | + } |
| 307 | + |
| 308 | + @Override |
| 309 | + public SettableAnyProperty withValueDeserializer(JsonDeserializer<Object> deser) { |
| 310 | + return new MethodAnyProperty(_property, _setter, _type, |
| 311 | + _keyDeserializer, deser, _valueTypeDeserializer); |
| 312 | + } |
| 313 | + } |
| 314 | + |
| 315 | + /** |
| 316 | + * @since 2.14 |
| 317 | + */ |
| 318 | + protected static class MapFieldAnyProperty extends SettableAnyProperty |
| 319 | + implements java.io.Serializable |
| 320 | + { |
| 321 | + private static final long serialVersionUID = 1L; |
| 322 | + |
| 323 | + protected final ValueInstantiator _valueInstantiator; |
| 324 | + |
| 325 | + public MapFieldAnyProperty(BeanProperty property, |
| 326 | + AnnotatedMember field, JavaType valueType, |
| 327 | + KeyDeserializer keyDeser, |
| 328 | + JsonDeserializer<Object> valueDeser, TypeDeserializer typeDeser, |
| 329 | + ValueInstantiator inst) { |
| 330 | + super(property, field, valueType, |
| 331 | + keyDeser, valueDeser, typeDeser); |
| 332 | + _valueInstantiator = inst; |
| 333 | + } |
| 334 | + |
| 335 | + @Override |
| 336 | + public SettableAnyProperty withValueDeserializer(JsonDeserializer<Object> deser) { |
| 337 | + return new MapFieldAnyProperty(_property, _setter, _type, |
| 338 | + _keyDeserializer, deser, _valueTypeDeserializer, |
| 339 | + _valueInstantiator); |
| 340 | + } |
| 341 | + |
| 342 | + @SuppressWarnings("unchecked") |
| 343 | + @Override |
| 344 | + protected void _set(Object instance, Object propName, Object value) throws Exception |
| 345 | + { |
| 346 | + AnnotatedField field = (AnnotatedField) _setter; |
| 347 | + Map<Object,Object> val = (Map<Object,Object>) field.getValue(instance); |
| 348 | + // 01-Aug-2022, tatu: [databind#3559] Will try to create and assign an |
| 349 | + // instance. |
| 350 | + if (val == null) { |
| 351 | + val = _createAndSetMap(null, field, instance, propName); |
| 352 | + } |
| 353 | + // add the property key and value |
| 354 | + val.put(propName, value); |
| 355 | + } |
| 356 | + |
| 357 | + @SuppressWarnings("unchecked") |
| 358 | + protected Map<Object, Object> _createAndSetMap(DeserializationContext ctxt, AnnotatedField field, |
| 359 | + Object instance, Object propName) |
| 360 | + throws IOException |
| 361 | + { |
| 362 | + if (_valueInstantiator == null) { |
| 363 | + throw JsonMappingException.from(ctxt, String.format( |
| 364 | + "Cannot create an instance of %s for use as \"any-setter\" '%s'", |
| 365 | + ClassUtil.nameOf(_type.getRawClass()), _property.getName())); |
| 366 | + } |
| 367 | + Map<Object,Object> map = (Map<Object,Object>) _valueInstantiator.createUsingDefault(ctxt); |
| 368 | + field.setValue(instance, map); |
| 369 | + return map; |
| 370 | + } |
| 371 | + } |
| 372 | + |
| 373 | + /** |
| 374 | + * @since 2.14 |
| 375 | + */ |
| 376 | + protected static class JsonNodeFieldAnyProperty extends SettableAnyProperty |
| 377 | + implements java.io.Serializable |
| 378 | + { |
| 379 | + private static final long serialVersionUID = 1L; |
| 380 | + |
| 381 | + protected final JsonNodeFactory _nodeFactory; |
| 382 | + |
| 383 | + public JsonNodeFieldAnyProperty(BeanProperty property, |
| 384 | + AnnotatedMember field, JavaType valueType, |
| 385 | + JsonDeserializer<Object> valueDeser, |
| 386 | + JsonNodeFactory nodeFactory) { |
| 387 | + super(property, field, valueType, null, valueDeser, null); |
| 388 | + _nodeFactory = nodeFactory; |
| 389 | + } |
| 390 | + |
| 391 | + @Override |
| 392 | + protected void _set(Object instance, Object propName, Object value) throws Exception |
| 393 | + { |
| 394 | + AnnotatedField field = (AnnotatedField) _setter; |
| 395 | + Object val0 = field.getValue(instance); |
| 396 | + ObjectNode objectNode; |
| 397 | + |
| 398 | + if (val0 == null) { |
| 399 | + objectNode = _nodeFactory.objectNode(); |
| 400 | + field.setValue(instance, objectNode); |
| 401 | + } else if (!(val0 instanceof ObjectNode)) { |
| 402 | + throw JsonMappingException.from((DeserializationContext) null, String.format( |
| 403 | + "Value \"any-setter\" '%s' not `ObjectNode` but %s", |
| 404 | + getPropertyName(), |
| 405 | + ClassUtil.nameOf(val0.getClass()))); |
| 406 | + } else { |
| 407 | + objectNode = (ObjectNode) val0; |
| 408 | + } |
| 409 | + // add the property key and value |
| 410 | + objectNode.set(String.valueOf(propName), (JsonNode) value); |
| 411 | + } |
| 412 | + |
| 413 | + // Should not get called but... |
| 414 | + @Override |
| 415 | + public SettableAnyProperty withValueDeserializer(JsonDeserializer<Object> deser) { |
| 416 | + return this; |
| 417 | + } |
| 418 | + } |
262 | 419 | }
|
0 commit comments