Skip to content

Commit 804f4e3

Browse files
committed
Merge branch '2.14' into tatu/2.14/3394-any-setter-jsonnode
2 parents 6f54812 + 52ed66a commit 804f4e3

File tree

4 files changed

+77
-21
lines changed

4 files changed

+77
-21
lines changed

release-notes/VERSION-2.x

+1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ Project: jackson-databind
4848
#3530: Change LRUMap to just evict one entry when maxEntries reached
4949
(contributed by @pjfanning)
5050
#3535: Replace `JsonNode.with()` with `JsonNode.withObject()`
51+
#3559: Support `null`-valued `Map` fields with "any setter"
5152

5253
2.13.4 (not yet released)
5354

src/main/java/com/fasterxml/jackson/databind/JsonMappingException.java

+14-4
Original file line numberDiff line numberDiff line change
@@ -294,21 +294,26 @@ public static JsonMappingException from(JsonGenerator g, String msg, Throwable p
294294
* @since 2.7
295295
*/
296296
public static JsonMappingException from(DeserializationContext ctxt, String msg) {
297-
return new JsonMappingException(ctxt.getParser(), msg);
297+
return new JsonMappingException(_parser(ctxt), msg);
298298
}
299299

300300
/**
301301
* @since 2.7
302302
*/
303303
public static JsonMappingException from(DeserializationContext ctxt, String msg, Throwable t) {
304-
return new JsonMappingException(ctxt.getParser(), msg, t);
304+
return new JsonMappingException(_parser(ctxt), msg, t);
305+
}
306+
307+
// @since 2.14
308+
private static JsonParser _parser(DeserializationContext ctxt) {
309+
return (ctxt == null) ? null : ctxt.getParser();
305310
}
306311

307312
/**
308313
* @since 2.7
309314
*/
310315
public static JsonMappingException from(SerializerProvider ctxt, String msg) {
311-
return new JsonMappingException(ctxt.getGenerator(), msg);
316+
return new JsonMappingException(_generator(ctxt), msg);
312317
}
313318

314319
/**
@@ -318,7 +323,12 @@ public static JsonMappingException from(SerializerProvider ctxt, String msg, Thr
318323
/* 17-Aug-2015, tatu: As per [databind#903] this is bit problematic as
319324
* SerializerProvider instance does not currently hold on to generator...
320325
*/
321-
return new JsonMappingException(ctxt.getGenerator(), msg, problem);
326+
return new JsonMappingException(_generator(ctxt), msg, problem);
327+
}
328+
329+
// @since 2.14
330+
private static JsonGenerator _generator(SerializerProvider ctxt) {
331+
return (ctxt == null) ? null : ctxt.getGenerator();
322332
}
323333

324334
/**

src/main/java/com/fasterxml/jackson/databind/deser/SettableAnyProperty.java

+33-9
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package com.fasterxml.jackson.databind.deser;
22

33
import java.io.IOException;
4+
import java.util.LinkedHashMap;
45
import java.util.Map;
56

67
import com.fasterxml.jackson.core.*;
78
import com.fasterxml.jackson.databind.*;
9+
import com.fasterxml.jackson.databind.deser.impl.JDKValueInstantiators;
810
import com.fasterxml.jackson.databind.deser.impl.ReadableObjectId.Referring;
911
import com.fasterxml.jackson.databind.introspect.AnnotatedField;
1012
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
@@ -154,24 +156,46 @@ public void set(Object instance, Object propName, Object value) throws IOExcepti
154156
if (_setterIsField) {
155157
AnnotatedField field = (AnnotatedField) _setter;
156158
Map<Object,Object> val = (Map<Object,Object>) field.getValue(instance);
157-
/* 01-Jun-2016, tatu: At this point it is not quite clear what to do if
158-
* field is `null` -- we cannot necessarily count on zero-args
159-
* constructor except for a small set of types, so for now just
160-
* ignore if null. May need to figure out something better in future.
161-
*/
162-
if (val != null) {
163-
// add the property key and value
164-
val.put(propName, value);
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);
165163
}
164+
// add the property key and value
165+
val.put(propName, value);
166166
} else {
167167
// note: cannot use 'setValue()' due to taking 2 args
168168
((AnnotatedMethod) _setter).callOnWith(instance, propName, value);
169169
}
170+
} catch (IOException e) {
171+
throw e;
170172
} catch (Exception e) {
171173
_throwAsIOE(e, propName, value);
172174
}
173175
}
174176

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+
}
198+
175199
/*
176200
/**********************************************************
177201
/* Helper methods
@@ -188,7 +212,7 @@ protected void _throwAsIOE(Exception e, Object propName, Object value)
188212
{
189213
if (e instanceof IllegalArgumentException) {
190214
String actType = ClassUtil.classNameOf(value);
191-
StringBuilder msg = new StringBuilder("Problem deserializing \"any\" property '").append(propName);
215+
StringBuilder msg = new StringBuilder("Problem deserializing \"any-property\" '").append(propName);
192216
msg.append("' of class "+getClassName()+" (expected type: ").append(_type);
193217
msg.append("; actual type: ").append(actType).append(")");
194218
String origMsg = ClassUtil.exceptionMessage(e);

src/test/java/com/fasterxml/jackson/databind/deser/AnySetterTest.java

+29-8
Original file line numberDiff line numberDiff line change
@@ -163,14 +163,22 @@ static class JsonAnySetterOnNullMap {
163163
public int id;
164164

165165
@JsonAnySetter
166-
protected HashMap<String, String> other;
166+
protected Map<String, String> other;
167167

168168
@JsonAnyGetter
169169
public Map<String, String> any() {
170170
return other;
171171
}
172172
}
173173

174+
@SuppressWarnings("serial")
175+
static class CustomMap extends LinkedHashMap<String, String> { }
176+
177+
static class JsonAnySetterOnCustomNullMap {
178+
@JsonAnySetter
179+
public CustomMap other;
180+
}
181+
174182
static class MyGeneric<T>
175183
{
176184
private String staticallyMappedProperty;
@@ -346,18 +354,31 @@ public void testPolymorphic() throws Exception
346354
}
347355

348356
public void testJsonAnySetterOnMap() throws Exception {
349-
JsonAnySetterOnMap result = MAPPER.readValue("{\"id\":2,\"name\":\"Joe\", \"city\":\"New Jersey\"}",
350-
JsonAnySetterOnMap.class);
351-
assertEquals(2, result.id);
352-
assertEquals("Joe", result.other.get("name"));
353-
assertEquals("New Jersey", result.other.get("city"));
357+
JsonAnySetterOnMap result = MAPPER.readValue("{\"id\":2,\"name\":\"Joe\", \"city\":\"New Jersey\"}",
358+
JsonAnySetterOnMap.class);
359+
assertEquals(2, result.id);
360+
assertEquals("Joe", result.other.get("name"));
361+
assertEquals("New Jersey", result.other.get("city"));
354362
}
355363

356364
public void testJsonAnySetterOnNullMap() throws Exception {
357-
JsonAnySetterOnNullMap result = MAPPER.readValue("{\"id\":2,\"name\":\"Joe\", \"city\":\"New Jersey\"}",
365+
final String DOC = a2q("{'id':2,'name':'Joe', 'city':'New Jersey'}");
366+
JsonAnySetterOnNullMap result = MAPPER.readValue(DOC,
358367
JsonAnySetterOnNullMap.class);
359368
assertEquals(2, result.id);
360-
assertNull(result.other);
369+
// 01-Aug-2022, tatu: As per [databind#3559] should "just work"...
370+
assertNotNull(result.other);
371+
assertEquals("Joe", result.other.get("name"));
372+
assertEquals("New Jersey", result.other.get("city"));
373+
374+
// But not with unknown "special" maps
375+
try {
376+
MAPPER.readValue(DOC, JsonAnySetterOnCustomNullMap.class);
377+
fail("Should not pass");
378+
} catch (DatabindException e) {
379+
verifyException(e, "Cannot create an instance of");
380+
verifyException(e, "for use as \"any-setter\" 'other'");
381+
}
361382
}
362383

363384
final static String UNWRAPPED_JSON_349 = a2q(

0 commit comments

Comments
 (0)