Skip to content

Commit

Permalink
Fix #3002
Browse files Browse the repository at this point in the history
  • Loading branch information
cowtowncoder committed Feb 2, 2021
1 parent 64ba6ce commit 6a66bb9
Show file tree
Hide file tree
Showing 5 changed files with 159 additions and 6 deletions.
2 changes: 2 additions & 0 deletions release-notes/VERSION-2.x
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ Project: jackson-databind

#2828: Add `DatabindException` as intermediate subtype of `JsonMappingException`
#3001: Add mechanism for setting default `ContextAttributes` for `ObjectMapper`
#3002: Add `DeserializationContext.readTreeAsValue()` methods for more convenient
conversions for deserializers to use
#3035: Add `removeMixIn()` method in `MapperBuilder`
#3036: Backport `MapperBuilder` lambda-taking methods: `withConfigOverride()`,
`withCoercionConfig()`, `withCoercionConfigDefaults()`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
import com.fasterxml.jackson.databind.jsontype.TypeIdResolver;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.TreeTraversingParser;
import com.fasterxml.jackson.databind.type.LogicalType;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.fasterxml.jackson.databind.util.*;
Expand Down Expand Up @@ -912,6 +913,10 @@ public <T> T readValue(JsonParser p, JavaType type) throws IOException {
* for reading one-off values for the composite type, taking into account
* annotations that the property (passed to this method -- usually property that
* has custom serializer that called this method) has.
*
* @param p Parser that points to the first token of the value to read
* @param prop Logical property of a POJO being type
* @return Value of type {@code type} that was read
*
* @since 2.4
*/
Expand All @@ -920,6 +925,10 @@ public <T> T readPropertyValue(JsonParser p, BeanProperty prop, Class<T> type) t
}

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

/**
* Convenience method for reading the value that passed {@link JsonParser}
* points to as a {@link JsonNode}.
*
* @param p Parser that points to the first token of the value to read
*
* @return Value read as {@link JsonNode}
*
* @since 2.10
*/
public JsonNode readTree(JsonParser p) throws IOException {
Expand All @@ -951,6 +967,71 @@ public JsonNode readTree(JsonParser p) throws IOException {
.deserialize(p, this);
}

/**
* Helper method similar to {@link ObjectReader#treeToValue(TreeNode, Class)}
* which will read contents of given tree ({@link JsonNode})
* and bind them into specified target type. This is often used in two-phase
* deserialization in which content is first read as a tree, then manipulated
* (adding and/or removing properties of Object values, for example),
* and finally converted into actual target type using default deserialization
* logic for the type.
*<p>
* NOTE: deserializer implementations should be careful not to try to recursively
* deserialize into target type deserializer has registered itself to handle.
*
* @param n Tree value to convert, if not {@code null}: if {@code null}, will simply
* return {@code null}
* @param targetType Type to deserialize contents of {@code n} into (if {@code n} not {@code null})
*
* @return Either {@code null} (if {@code n} was {@code null} or a value of
* type {@code type} that was read from non-{@code null} {@code n} argument
*
* @since 2.13
*/
public <T> T readTreeAsValue(JsonNode n, Class<T> targetType) throws IOException
{
if (n == null) {
return null;
}
try (TreeTraversingParser p = _treeAsTokens(n)) {
return readValue(p, targetType);
}
}

/**
* Same as {@link #readTreeAsValue(JsonNode, Class)} but will fully resolved
* {@link JavaType} as {@code targetType}
*<p>
* NOTE: deserializer implementations should be careful not to try to recursively
* deserialize into target type deserializer has registered itself to handle.
*
* @param n Tree value to convert
* @param targetType Type to deserialize contents of {@code n} into
*
* @return Value of type {@code type} that was read
*
* @since 2.13
*/
public <T> T readTreeAsValue(JsonNode n, JavaType targetType) throws IOException
{
if (n == null) {
return null;
}
try (TreeTraversingParser p = _treeAsTokens(n)) {
return readValue(p, targetType);
}
}

private TreeTraversingParser _treeAsTokens(JsonNode n) throws IOException
{
// Not perfect but has to do...
ObjectCodec codec = (_parser == null) ? null : _parser.getCodec();
TreeTraversingParser p = new TreeTraversingParser(n, codec);
// important: must initialize...
p.nextToken();
return p;
}

/*
/**********************************************************
/* Methods for problem handling
Expand Down
15 changes: 14 additions & 1 deletion src/main/java/com/fasterxml/jackson/databind/ObjectReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -1973,7 +1973,20 @@ public <T> T treeToValue(TreeNode n, Class<T> valueType) throws JsonProcessingEx
} catch (IOException e) { // should not occur, no real i/o...
throw JsonMappingException.fromUnexpectedIOE(e);
}
}
}

// @since 2.13
public <T> T treeToValue(TreeNode n, JavaType valueType) throws JsonProcessingException
{
_assertNotNull("n", n);
try {
return readValue(treeAsTokens(n), valueType);
} catch (JsonProcessingException e) {
throw e;
} catch (IOException e) { // should not occur, no real i/o...
throw JsonMappingException.fromUnexpectedIOE(e);
}
}

@Override
public void writeValue(JsonGenerator gen, Object value) throws IOException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -447,8 +447,13 @@ public void testTreeToValue() throws Exception
ObjectReader r = MAPPER.readerFor(String.class);
List<?> list = r.treeToValue(n, List.class);
assertEquals(1, list.size());

// since 2.13:
String[] arr = r.treeToValue(n, MAPPER.constructType(String[].class));
assertEquals(1, arr.length);
assertEquals("xyz", arr[0]);
}

public void testCodecUnsupportedWrites() throws Exception
{
ObjectReader r = MAPPER.readerFor(String.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -313,13 +313,42 @@ public Object deserialize(JsonParser p, DeserializationContext ctxt)
}
}

@JsonDeserialize(using = NamedPointDeserializer.class)
static class NamedPoint
{
public Point point;
public String name;

public NamedPoint(String name, Point point) {
this.point = point;
this.name = name;
}
}

static class NamedPointDeserializer extends StdDeserializer<NamedPoint>
{
public NamedPointDeserializer() {
super(NamedPoint.class);
}

@Override
public NamedPoint deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException
{
JsonNode tree = ctxt.readTree(p);
String name = tree.path("name").asText(null);
Point point = ctxt.readTreeAsValue(tree.get("point"), Point.class);
return new NamedPoint(name, point);
}
}

/*
/**********************************************************
/* Unit tests
/**********************************************************
*/

final ObjectMapper MAPPER = objectMapper();
private final ObjectMapper MAPPER = newJsonMapper();

public void testCustomBeanDeserializer() throws Exception
{
Expand Down Expand Up @@ -388,7 +417,6 @@ public Immutable convert(JsonNode value)
// [databind#623]
public void testJsonNodeDelegating() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule("test", Version.unknownVersion());
module.addDeserializer(Immutable.class,
new StdNodeBasedDeserializer<Immutable>(Immutable.class) {
Expand All @@ -399,7 +427,9 @@ public Immutable convert(JsonNode root, DeserializationContext ctxt) throws IOEx
return new Immutable(x, y);
}
});
mapper.registerModule(module);
ObjectMapper mapper = jsonMapperBuilder()
.addModule(module)
.build();
Immutable imm = mapper.readValue("{\"x\":-10,\"y\":3}", Immutable.class);
assertEquals(-10, imm.x);
assertEquals(3, imm.y);
Expand Down Expand Up @@ -485,7 +515,7 @@ public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config,
}

// [databind#2452]
public void testCustomSerializerWithReadTree() throws Exception
public void testCustomDeserializerWithReadTree() throws Exception
{
ObjectMapper mapper = jsonMapperBuilder()
.addModule(new SimpleModule()
Expand All @@ -499,4 +529,26 @@ public void testCustomSerializerWithReadTree() throws Exception
assertEquals(3, n.size());
assertEquals(123, n.get(2).intValue());
}

// [databind#3002]
public void testCustomDeserializerWithReadTreeAsValue() throws Exception
{
final String json = a2q("{'point':{'x':13, 'y':-4}, 'name':'Foozibald' }");
NamedPoint result = MAPPER.readValue(json, NamedPoint.class);
assertNotNull(result);
assertEquals("Foozibald", result.name);
assertEquals(new Point(13, -4), result.point);

// and with JavaType variant too
result = MAPPER.readValue(json, MAPPER.constructType(NamedPoint.class));
assertNotNull(result);
assertEquals("Foozibald", result.name);
assertEquals(new Point(13, -4), result.point);

// also, try some edge conditions
result = MAPPER.readValue(a2q("{'name':4})"), NamedPoint.class);
assertNotNull(result);
assertEquals("4", result.name);
assertNull(result.point);
}
}

0 comments on commit 6a66bb9

Please sign in to comment.