Skip to content

Commit 6a66bb9

Browse files
committed
Fix #3002
1 parent 64ba6ce commit 6a66bb9

File tree

5 files changed

+159
-6
lines changed

5 files changed

+159
-6
lines changed

release-notes/VERSION-2.x

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ Project: jackson-databind
88

99
#2828: Add `DatabindException` as intermediate subtype of `JsonMappingException`
1010
#3001: Add mechanism for setting default `ContextAttributes` for `ObjectMapper`
11+
#3002: Add `DeserializationContext.readTreeAsValue()` methods for more convenient
12+
conversions for deserializers to use
1113
#3035: Add `removeMixIn()` method in `MapperBuilder`
1214
#3036: Backport `MapperBuilder` lambda-taking methods: `withConfigOverride()`,
1315
`withCoercionConfig()`, `withCoercionConfigDefaults()`

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

+81
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
3232
import com.fasterxml.jackson.databind.jsontype.TypeIdResolver;
3333
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
34+
import com.fasterxml.jackson.databind.node.TreeTraversingParser;
3435
import com.fasterxml.jackson.databind.type.LogicalType;
3536
import com.fasterxml.jackson.databind.type.TypeFactory;
3637
import com.fasterxml.jackson.databind.util.*;
@@ -912,6 +913,10 @@ public <T> T readValue(JsonParser p, JavaType type) throws IOException {
912913
* for reading one-off values for the composite type, taking into account
913914
* annotations that the property (passed to this method -- usually property that
914915
* has custom serializer that called this method) has.
916+
*
917+
* @param p Parser that points to the first token of the value to read
918+
* @param prop Logical property of a POJO being type
919+
* @return Value of type {@code type} that was read
915920
*
916921
* @since 2.4
917922
*/
@@ -920,6 +925,10 @@ public <T> T readPropertyValue(JsonParser p, BeanProperty prop, Class<T> type) t
920925
}
921926

922927
/**
928+
* Same as {@link #readPropertyValue(JsonParser, BeanProperty, Class)} but with
929+
* fully resolved {@link JavaType} as target: needs to be used for generic types,
930+
* for example.
931+
*
923932
* @since 2.4
924933
*/
925934
@SuppressWarnings("unchecked")
@@ -934,6 +943,13 @@ public <T> T readPropertyValue(JsonParser p, BeanProperty prop, JavaType type) t
934943
}
935944

936945
/**
946+
* Convenience method for reading the value that passed {@link JsonParser}
947+
* points to as a {@link JsonNode}.
948+
*
949+
* @param p Parser that points to the first token of the value to read
950+
*
951+
* @return Value read as {@link JsonNode}
952+
*
937953
* @since 2.10
938954
*/
939955
public JsonNode readTree(JsonParser p) throws IOException {
@@ -951,6 +967,71 @@ public JsonNode readTree(JsonParser p) throws IOException {
951967
.deserialize(p, this);
952968
}
953969

970+
/**
971+
* Helper method similar to {@link ObjectReader#treeToValue(TreeNode, Class)}
972+
* which will read contents of given tree ({@link JsonNode})
973+
* and bind them into specified target type. This is often used in two-phase
974+
* deserialization in which content is first read as a tree, then manipulated
975+
* (adding and/or removing properties of Object values, for example),
976+
* and finally converted into actual target type using default deserialization
977+
* logic for the type.
978+
*<p>
979+
* NOTE: deserializer implementations should be careful not to try to recursively
980+
* deserialize into target type deserializer has registered itself to handle.
981+
*
982+
* @param n Tree value to convert, if not {@code null}: if {@code null}, will simply
983+
* return {@code null}
984+
* @param targetType Type to deserialize contents of {@code n} into (if {@code n} not {@code null})
985+
*
986+
* @return Either {@code null} (if {@code n} was {@code null} or a value of
987+
* type {@code type} that was read from non-{@code null} {@code n} argument
988+
*
989+
* @since 2.13
990+
*/
991+
public <T> T readTreeAsValue(JsonNode n, Class<T> targetType) throws IOException
992+
{
993+
if (n == null) {
994+
return null;
995+
}
996+
try (TreeTraversingParser p = _treeAsTokens(n)) {
997+
return readValue(p, targetType);
998+
}
999+
}
1000+
1001+
/**
1002+
* Same as {@link #readTreeAsValue(JsonNode, Class)} but will fully resolved
1003+
* {@link JavaType} as {@code targetType}
1004+
*<p>
1005+
* NOTE: deserializer implementations should be careful not to try to recursively
1006+
* deserialize into target type deserializer has registered itself to handle.
1007+
*
1008+
* @param n Tree value to convert
1009+
* @param targetType Type to deserialize contents of {@code n} into
1010+
*
1011+
* @return Value of type {@code type} that was read
1012+
*
1013+
* @since 2.13
1014+
*/
1015+
public <T> T readTreeAsValue(JsonNode n, JavaType targetType) throws IOException
1016+
{
1017+
if (n == null) {
1018+
return null;
1019+
}
1020+
try (TreeTraversingParser p = _treeAsTokens(n)) {
1021+
return readValue(p, targetType);
1022+
}
1023+
}
1024+
1025+
private TreeTraversingParser _treeAsTokens(JsonNode n) throws IOException
1026+
{
1027+
// Not perfect but has to do...
1028+
ObjectCodec codec = (_parser == null) ? null : _parser.getCodec();
1029+
TreeTraversingParser p = new TreeTraversingParser(n, codec);
1030+
// important: must initialize...
1031+
p.nextToken();
1032+
return p;
1033+
}
1034+
9541035
/*
9551036
/**********************************************************
9561037
/* Methods for problem handling

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

+14-1
Original file line numberDiff line numberDiff line change
@@ -1973,7 +1973,20 @@ public <T> T treeToValue(TreeNode n, Class<T> valueType) throws JsonProcessingEx
19731973
} catch (IOException e) { // should not occur, no real i/o...
19741974
throw JsonMappingException.fromUnexpectedIOE(e);
19751975
}
1976-
}
1976+
}
1977+
1978+
// @since 2.13
1979+
public <T> T treeToValue(TreeNode n, JavaType valueType) throws JsonProcessingException
1980+
{
1981+
_assertNotNull("n", n);
1982+
try {
1983+
return readValue(treeAsTokens(n), valueType);
1984+
} catch (JsonProcessingException e) {
1985+
throw e;
1986+
} catch (IOException e) { // should not occur, no real i/o...
1987+
throw JsonMappingException.fromUnexpectedIOE(e);
1988+
}
1989+
}
19771990

19781991
@Override
19791992
public void writeValue(JsonGenerator gen, Object value) throws IOException {

src/test/java/com/fasterxml/jackson/databind/ObjectReaderTest.java

+6-1
Original file line numberDiff line numberDiff line change
@@ -447,8 +447,13 @@ public void testTreeToValue() throws Exception
447447
ObjectReader r = MAPPER.readerFor(String.class);
448448
List<?> list = r.treeToValue(n, List.class);
449449
assertEquals(1, list.size());
450+
451+
// since 2.13:
452+
String[] arr = r.treeToValue(n, MAPPER.constructType(String[].class));
453+
assertEquals(1, arr.length);
454+
assertEquals("xyz", arr[0]);
450455
}
451-
456+
452457
public void testCodecUnsupportedWrites() throws Exception
453458
{
454459
ObjectReader r = MAPPER.readerFor(String.class);

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

+56-4
Original file line numberDiff line numberDiff line change
@@ -313,13 +313,42 @@ public Object deserialize(JsonParser p, DeserializationContext ctxt)
313313
}
314314
}
315315

316+
@JsonDeserialize(using = NamedPointDeserializer.class)
317+
static class NamedPoint
318+
{
319+
public Point point;
320+
public String name;
321+
322+
public NamedPoint(String name, Point point) {
323+
this.point = point;
324+
this.name = name;
325+
}
326+
}
327+
328+
static class NamedPointDeserializer extends StdDeserializer<NamedPoint>
329+
{
330+
public NamedPointDeserializer() {
331+
super(NamedPoint.class);
332+
}
333+
334+
@Override
335+
public NamedPoint deserialize(JsonParser p, DeserializationContext ctxt)
336+
throws IOException
337+
{
338+
JsonNode tree = ctxt.readTree(p);
339+
String name = tree.path("name").asText(null);
340+
Point point = ctxt.readTreeAsValue(tree.get("point"), Point.class);
341+
return new NamedPoint(name, point);
342+
}
343+
}
344+
316345
/*
317346
/**********************************************************
318347
/* Unit tests
319348
/**********************************************************
320349
*/
321350

322-
final ObjectMapper MAPPER = objectMapper();
351+
private final ObjectMapper MAPPER = newJsonMapper();
323352

324353
public void testCustomBeanDeserializer() throws Exception
325354
{
@@ -388,7 +417,6 @@ public Immutable convert(JsonNode value)
388417
// [databind#623]
389418
public void testJsonNodeDelegating() throws Exception
390419
{
391-
ObjectMapper mapper = new ObjectMapper();
392420
SimpleModule module = new SimpleModule("test", Version.unknownVersion());
393421
module.addDeserializer(Immutable.class,
394422
new StdNodeBasedDeserializer<Immutable>(Immutable.class) {
@@ -399,7 +427,9 @@ public Immutable convert(JsonNode root, DeserializationContext ctxt) throws IOEx
399427
return new Immutable(x, y);
400428
}
401429
});
402-
mapper.registerModule(module);
430+
ObjectMapper mapper = jsonMapperBuilder()
431+
.addModule(module)
432+
.build();
403433
Immutable imm = mapper.readValue("{\"x\":-10,\"y\":3}", Immutable.class);
404434
assertEquals(-10, imm.x);
405435
assertEquals(3, imm.y);
@@ -485,7 +515,7 @@ public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config,
485515
}
486516

487517
// [databind#2452]
488-
public void testCustomSerializerWithReadTree() throws Exception
518+
public void testCustomDeserializerWithReadTree() throws Exception
489519
{
490520
ObjectMapper mapper = jsonMapperBuilder()
491521
.addModule(new SimpleModule()
@@ -499,4 +529,26 @@ public void testCustomSerializerWithReadTree() throws Exception
499529
assertEquals(3, n.size());
500530
assertEquals(123, n.get(2).intValue());
501531
}
532+
533+
// [databind#3002]
534+
public void testCustomDeserializerWithReadTreeAsValue() throws Exception
535+
{
536+
final String json = a2q("{'point':{'x':13, 'y':-4}, 'name':'Foozibald' }");
537+
NamedPoint result = MAPPER.readValue(json, NamedPoint.class);
538+
assertNotNull(result);
539+
assertEquals("Foozibald", result.name);
540+
assertEquals(new Point(13, -4), result.point);
541+
542+
// and with JavaType variant too
543+
result = MAPPER.readValue(json, MAPPER.constructType(NamedPoint.class));
544+
assertNotNull(result);
545+
assertEquals("Foozibald", result.name);
546+
assertEquals(new Point(13, -4), result.point);
547+
548+
// also, try some edge conditions
549+
result = MAPPER.readValue(a2q("{'name':4})"), NamedPoint.class);
550+
assertNotNull(result);
551+
assertEquals("4", result.name);
552+
assertNull(result.point);
553+
}
502554
}

0 commit comments

Comments
 (0)