diff --git a/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java b/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java index a3622f2490..1975a1b708 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java +++ b/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java @@ -3596,7 +3596,7 @@ public T convertValue(Object fromValue, Class toValueType) public T convertValue(Object fromValue, TypeReference toValueTypeRef) throws IllegalArgumentException { - return (T) convertValue(fromValue, _typeFactory.constructType(toValueTypeRef)); + return (T) _convert(fromValue, _typeFactory.constructType(toValueTypeRef)); } /** @@ -3625,10 +3625,9 @@ protected Object _convert(Object fromValue, JavaType toValueType) // This defaults primitives and fires deserializer getNullValue hooks. if (fromValue != null) { // also, as per [databind#11], consider case for simple cast - /* But with caveats: one is that while everything is Object.class, we don't - * want to "optimize" that out; and the other is that we also do not want - * to lose conversions of generic types. - */ + // But with caveats: one is that while everything is Object.class, we don't + // want to "optimize" that out; and the other is that we also do not want + // to lose conversions of generic types. Class targetType = toValueType.getRawClass(); if (targetType != Object.class && !toValueType.hasGenericTypes() @@ -3673,6 +3672,68 @@ protected Object _convert(Object fromValue, JavaType toValueType) } } + /** + * Convenience method similar to {@link #convertValue(Object, JavaType)} but one + * in which an existing value (`valueToUpdate`) is modified based on contents + * of serialization of another object (`updateWithValue`). + *

+ * Implementation is approximately as follows: + *

    + *
  1. Serialize `updateWithValue` into {@link TokenBuffer}
  2. + *
  3. Construct {@link ObjectReader} with `valueToUpdate` (using {@link #readerForUpdating(Object)}) + *
  4. + *
  5. Construct {@link JsonParser} (using {@link TokenBuffer#asParser()}) + *
  6. + *
  7. Update using {@link ObjectReader#readValue(JsonParser)}. + *
  8. + *
  9. Return `valueToUpdate` + *
  10. + *
+ *

+ * Note that update is "shallow" in that only first level of properties (or, immediate contents + * of container to update) are modified, unless properties themselves indicate that + * merging should be applied for contents. Such merging can be specified using + * annotations (see JsonMerge) as well as using "config overrides" (see + * {@link #configOverride(Class)} and {@link #setDefaultMergeable(Boolean)}). + * + * @param valueToUpdate Object to update + * @param updateWithValue Object to conceptually serialize and merge into value to + * update; can be thought of as a provider for overrides to apply. + * + * @return First argument, that is, `valueToUpdate` + * + * @throws JsonMappingException if there are structural incompatibilities that prevent update + * + * @since 2.9 + */ + public T updateValue(T valueToUpdate, Object updateWithValue) + throws JsonMappingException + { + if ((valueToUpdate == null) || (updateWithValue == null)) { + return valueToUpdate; + } + @SuppressWarnings("resource") + TokenBuffer buf = new TokenBuffer(this, false); + if (isEnabled(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS)) { + buf = buf.forceUseOfBigDecimal(true); + } + try { + SerializationConfig config = getSerializationConfig(). + without(SerializationFeature.WRAP_ROOT_VALUE); + _serializerProvider(config).serializeValue(buf, updateWithValue); + JsonParser p = buf.asParser(); + readerForUpdating(valueToUpdate).readValue(p); + p.close(); + } catch (IOException e) { // should not occur, no real i/o... + if (e instanceof JsonMappingException) { + throw (JsonMappingException) e; + } + // 17-Mar-2017, tatu: Really ought not happen... + throw JsonMappingException.fromUnexpectedIOE(e); + } + return valueToUpdate; + } + /* /********************************************************** /* Extended Public API: JSON Schema generation diff --git a/src/main/java/com/fasterxml/jackson/databind/ObjectReader.java b/src/main/java/com/fasterxml/jackson/databind/ObjectReader.java index c905878e08..05eff3ffd5 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ObjectReader.java +++ b/src/main/java/com/fasterxml/jackson/databind/ObjectReader.java @@ -1777,7 +1777,7 @@ protected JsonNode _detectBindAndCloseAsTree(InputStream in) throws IOException * of given input */ protected void _reportUnkownFormat(DataFormatReaders detector, DataFormatReaders.Match match) - throws JsonProcessingException + throws JsonProcessingException { // 17-Aug-2015, tatu: Unfortunately, no parser/generator available so: throw new JsonParseException(null, "Can not detect format from input, does not look like any of detectable formats " diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/StringDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/StringDeserializer.java index a2155071a7..b6303dc11e 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/StringDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/StringDeserializer.java @@ -16,7 +16,7 @@ public class StringDeserializer extends StdScalarDeserializer // non-fin * @since 2.2 */ public final static StringDeserializer instance = new StringDeserializer(); - + public StringDeserializer() { super(String.class); } // since 2.6, slightly faster lookups for this very common type diff --git a/src/test/java/com/fasterxml/jackson/databind/convert/TestUpdateValue.java b/src/test/java/com/fasterxml/jackson/databind/convert/TestUpdateViaObjectReader.java similarity index 96% rename from src/test/java/com/fasterxml/jackson/databind/convert/TestUpdateValue.java rename to src/test/java/com/fasterxml/jackson/databind/convert/TestUpdateViaObjectReader.java index 030223bc74..f125c1d6bc 100644 --- a/src/test/java/com/fasterxml/jackson/databind/convert/TestUpdateValue.java +++ b/src/test/java/com/fasterxml/jackson/databind/convert/TestUpdateViaObjectReader.java @@ -15,14 +15,8 @@ * Unit tests for verifying that "updating reader" works as * expected. */ -public class TestUpdateValue extends BaseMapTest +public class TestUpdateViaObjectReader extends BaseMapTest { - /* - /******************************************************** - /* Helper types - /******************************************************** - */ - static class Bean { public String a = "a"; public String b = "b"; @@ -36,7 +30,6 @@ static class XYBean { public int x, y; } - // [JACKSON-824] public class TextView {} public class NumView {} @@ -85,7 +78,7 @@ public DataA deserialize(JsonParser p, DeserializationContext ctxt) throws IOExc /* /******************************************************** - /* Unit tests + /* Test methods /******************************************************** */ diff --git a/src/test/java/com/fasterxml/jackson/databind/convert/UpdateValueTest.java b/src/test/java/com/fasterxml/jackson/databind/convert/UpdateValueTest.java new file mode 100644 index 0000000000..ffd7c08de8 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/convert/UpdateValueTest.java @@ -0,0 +1,99 @@ +package com.fasterxml.jackson.databind.convert; + +import java.util.*; + +import com.fasterxml.jackson.databind.*; + +/** + * Tests for {@link ObjectMapper#updateValue}. + * + * @since 2.9 + */ +public class UpdateValueTest extends BaseMapTest +{ + /* + /******************************************************** + /* Test methods; simple containers + /******************************************************** + */ + + private final ObjectMapper MAPPER = new ObjectMapper(); + + public void testMapUpdate() throws Exception + { + Map base = new LinkedHashMap<>(); + base.put("a", 345); + Map overrides = new LinkedHashMap<>(); + overrides.put("xyz", Boolean.TRUE); + overrides.put("foo", "bar"); + + Map ob = MAPPER.updateValue(base, overrides); + // first: should return first argument + assertSame(base, ob); + assertEquals(3, ob.size()); + assertEquals(Integer.valueOf(345), ob.get("a")); + assertEquals("bar", ob.get("foo")); + assertEquals(Boolean.TRUE, ob.get("xyz")); + } + + public void testListUpdate() throws Exception + { + List base = new ArrayList<>(); + base.add(123456); + base.add(Boolean.FALSE); + Object[] overrides = new Object[] { Boolean.TRUE, "zoink!" }; + + List ob = MAPPER.updateValue(base, overrides); + // first: should return first argument + assertSame(base, ob); + assertEquals(4, ob.size()); + assertEquals(Integer.valueOf(123456), ob.get(0)); + assertEquals(Boolean.FALSE, ob.get(1)); + assertEquals(overrides[0], ob.get(2)); + assertEquals(overrides[1], ob.get(3)); + } + + // What are expected array semantics? + /* + public void testArrayUpdate() throws Exception + { + // Since Arrays are immutable, not sure what "right answer" ought to be + Object[] base = new Object[] { Boolean.FALSE, Integer.valueOf(3) }; + Object[] overrides = new Object[] { Boolean.TRUE, "zoink!" }; + + Object[] ob = MAPPER.updateValue(base, overrides); + } + */ + + /* + /******************************************************** + /* Test methods; POJOs + /******************************************************** + */ + + public void testPOJO() throws Exception + { + Point base = new Point(42, 28); + Map overrides = new LinkedHashMap<>(); + overrides.put("y", 1234); + Point result = MAPPER.updateValue(base, overrides); + assertSame(base, result); + assertEquals(42, result.x); + assertEquals(1234, result.y); + } + + /* + /******************************************************** + /* Test methods; other + /******************************************************** + */ + + public void testMisc() throws Exception + { + // if either is `null`, should return first arg + assertNull(MAPPER.updateValue(null, "foo")); + List input = new ArrayList<>(); + assertSame(input, MAPPER.updateValue(input, null)); + } + +}