diff --git a/src/main/java/tools/jackson/databind/deser/BasicDeserializerFactory.java b/src/main/java/tools/jackson/databind/deser/BasicDeserializerFactory.java index 5cec73050e..0016ac51e1 100644 --- a/src/main/java/tools/jackson/databind/deser/BasicDeserializerFactory.java +++ b/src/main/java/tools/jackson/databind/deser/BasicDeserializerFactory.java @@ -13,6 +13,7 @@ import tools.jackson.databind.cfg.*; import tools.jackson.databind.deser.bean.CreatorCandidate; import tools.jackson.databind.deser.bean.CreatorCollector; +import tools.jackson.databind.deser.impl.UnwrappedPropertyHandler; import tools.jackson.databind.deser.jackson.JsonNodeDeserializer; import tools.jackson.databind.deser.jackson.TokenBufferDeserializer; import tools.jackson.databind.deser.jdk.*; @@ -48,12 +49,6 @@ public abstract class BasicDeserializerFactory private final static Class CLASS_MAP_ENTRY = Map.Entry.class; private final static Class CLASS_SERIALIZABLE = Serializable.class; - /** - * We need a placeholder for creator properties that don't have name - * but are marked with `@JsonWrapped` annotation. - */ - protected final static PropertyName UNWRAPPED_CREATOR_PARAM_NAME = new PropertyName("@JsonUnwrapped"); - /* /********************************************************************** /* Config @@ -370,11 +365,8 @@ private void _addImplicitDelegatingConstructors(DeserializationContext ctxt, } NameTransformer unwrapper = intr.findUnwrappingNameTransformer(config, param); if (unwrapper != null) { - _reportUnwrappedCreatorProperty(ctxt, beanDesc, param); - /* - properties[i] = constructCreatorProperty(ctxt, beanDesc, UNWRAPPED_CREATOR_PARAM_NAME, i, param, null); - ++explicitNameCount; - */ + properties[i] = constructCreatorProperty(ctxt, beanDesc, + UnwrappedPropertyHandler.creatorParamName(i), i, param, null); } } @@ -496,7 +488,8 @@ private void _addSelectedPropertiesBasedCreator(DeserializationContext ctxt, // as that will not work with Creators well at all NameTransformer unwrapper = intr.findUnwrappingNameTransformer(config, param); if (unwrapper != null) { - _reportUnwrappedCreatorProperty(ctxt, beanDesc, param); + properties[i] = constructCreatorProperty(ctxt, beanDesc, + UnwrappedPropertyHandler.creatorParamName(i), i, param, null); } // Must be injectable or have name; without either won't work if ((name == null) && (injectId == null)) { @@ -563,16 +556,6 @@ private boolean _handleSingleArgumentCreator(CreatorCollector creators, return false; } - // 01-Dec-2016, tatu: As per [databind#265] we cannot yet support passing - // of unwrapped values through creator properties, so fail fast - private void _reportUnwrappedCreatorProperty(DeserializationContext ctxt, - BeanDescription beanDesc, AnnotatedParameter param) - { - ctxt.reportBadTypeDefinition(beanDesc, -"Cannot define Creator parameter %d as `@JsonUnwrapped`: combination not yet supported", - param.getIndex()); - } - /** * Method that will construct a property object that represents * a logical property passed via Creator (constructor or static diff --git a/src/main/java/tools/jackson/databind/deser/SettableBeanProperty.java b/src/main/java/tools/jackson/databind/deser/SettableBeanProperty.java index a99f62b806..fc8371401d 100644 --- a/src/main/java/tools/jackson/databind/deser/SettableBeanProperty.java +++ b/src/main/java/tools/jackson/databind/deser/SettableBeanProperty.java @@ -3,6 +3,7 @@ import java.lang.annotation.Annotation; import tools.jackson.core.*; +import tools.jackson.core.util.InternCache; import tools.jackson.databind.*; import tools.jackson.databind.deser.bean.BeanDeserializer; import tools.jackson.databind.deser.impl.FailingDeserializer; @@ -12,6 +13,7 @@ import tools.jackson.databind.jsontype.TypeDeserializer; import tools.jackson.databind.util.Annotations; import tools.jackson.databind.util.ClassUtil; +import tools.jackson.databind.util.NameTransformer; import tools.jackson.databind.util.ViewMatcher; /** @@ -577,6 +579,28 @@ public final Object deserializeWith(JsonParser p, DeserializationContext ctxt, return value; } + /** + * Returns a copy of this property, unwrapped using given {@link NameTransformer}. + * + * @since 2.19 + */ + public SettableBeanProperty unwrapped(DeserializationContext ctxt, NameTransformer xf) + { + String newName = xf.transform(getName()); + newName = InternCache.instance.intern(newName); + SettableBeanProperty renamed = withSimpleName(newName); + ValueDeserializer deser = renamed.getValueDeserializer(); + if (deser != null) { + @SuppressWarnings("unchecked") + ValueDeserializer newDeser = (ValueDeserializer) + deser.unwrappingDeserializer(ctxt, xf); + if (newDeser != deser) { + renamed = renamed.withValueDeserializer(newDeser); + } + } + return renamed; + } + /* /********************************************************************** /* Helper methods diff --git a/src/main/java/tools/jackson/databind/deser/bean/BeanDeserializer.java b/src/main/java/tools/jackson/databind/deser/bean/BeanDeserializer.java index f4f237d1f5..6741aa5a1d 100644 --- a/src/main/java/tools/jackson/databind/deser/bean/BeanDeserializer.java +++ b/src/main/java/tools/jackson/databind/deser/bean/BeanDeserializer.java @@ -1101,6 +1101,11 @@ protected Object deserializeUsingPropertyBasedWithUnwrapped(JsonParser p, Deseri } } + // We could still have some not-yet-set creator properties that are unwrapped. + // These have to be processed last, because 'tokens' contains all properties + // that remain after regular deserialization. + buffer = _unwrappedPropertyHandler.processUnwrappedCreatorProperties(p, ctxt, buffer, tokens); + // We hit END_OBJECT, so: Object bean; try { diff --git a/src/main/java/tools/jackson/databind/deser/bean/BeanDeserializerBase.java b/src/main/java/tools/jackson/databind/deser/bean/BeanDeserializerBase.java index b954bb978e..e7714f5c20 100644 --- a/src/main/java/tools/jackson/databind/deser/bean/BeanDeserializerBase.java +++ b/src/main/java/tools/jackson/databind/deser/bean/BeanDeserializerBase.java @@ -322,6 +322,7 @@ protected BeanDeserializerBase(BeanDeserializerBase src, _unwrappedPropertyHandler = unwrapHandler; _beanProperties = renamedProperties; + _needViewProcesing = src._needViewProcesing; _serializationShape = src._serializationShape; @@ -550,7 +551,13 @@ public void resolve(DeserializationContext ctxt) if (unwrapped == null) { unwrapped = new UnwrappedPropertyHandler(); } - unwrapped.addProperty(prop); + + if (prop instanceof CreatorProperty) { + unwrapped.addCreatorProperty(prop); + } else { + unwrapped.addProperty(prop); + } + // 12-Dec-2014, tatu: As per [databind#647], we will have problems if // the original property is left in place. So let's remove it now. // 25-Mar-2017, tatu: Wonder if this could be problematic wrt creators? @@ -973,13 +980,6 @@ protected NameTransformer _findPropertyUnwrapper(DeserializationContext ctxt, NameTransformer unwrapper = ctxt.getAnnotationIntrospector().findUnwrappingNameTransformer( ctxt.getConfig(), am); if (unwrapper != null) { - // 01-Dec-2016, tatu: As per [databind#265] we cannot yet support passing - // of unwrapped values through creator properties, so fail fast - if (prop instanceof CreatorProperty) { - ctxt.reportBadDefinition(getValueType(), String.format( - "Cannot define Creator property \"%s\" as `@JsonUnwrapped`: combination not yet supported", - prop.getName())); - } return unwrapper; } } diff --git a/src/main/java/tools/jackson/databind/deser/bean/BeanPropertyMap.java b/src/main/java/tools/jackson/databind/deser/bean/BeanPropertyMap.java index d3ac879c81..143f6abe7a 100644 --- a/src/main/java/tools/jackson/databind/deser/bean/BeanPropertyMap.java +++ b/src/main/java/tools/jackson/databind/deser/bean/BeanPropertyMap.java @@ -4,11 +4,9 @@ import tools.jackson.core.TokenStreamFactory; import tools.jackson.core.sym.PropertyNameMatcher; -import tools.jackson.core.util.InternCache; import tools.jackson.core.util.Named; import tools.jackson.databind.DeserializationContext; import tools.jackson.databind.PropertyName; -import tools.jackson.databind.ValueDeserializer; import tools.jackson.databind.cfg.MapperConfig; import tools.jackson.databind.deser.SettableBeanProperty; import tools.jackson.databind.util.IgnorePropertiesUtil; @@ -176,7 +174,7 @@ public BeanPropertyMap renameAll(DeserializationContext ctxt, ArrayList newProps = new ArrayList(_propsInOrder.length); for (int i = 0; i < len; ++i) { SettableBeanProperty orig = _propsInOrder[i]; - SettableBeanProperty prop = _rename(ctxt, orig, transformer); + SettableBeanProperty prop = orig.unwrapped(ctxt, transformer); newProps.add(prop); } // 26-Feb-2017, tatu: Probably SHOULD handle renaming wrt Aliases? @@ -187,26 +185,6 @@ public BeanPropertyMap renameAll(DeserializationContext ctxt, .initMatcher(ctxt.tokenStreamFactory()); } - private SettableBeanProperty _rename(DeserializationContext ctxt, - SettableBeanProperty prop, NameTransformer xf) - { - if (prop != null) { - String newName = xf.transform(prop.getName()); - newName = InternCache.instance.intern(newName); - prop = prop.withSimpleName(newName); - ValueDeserializer deser = prop.getValueDeserializer(); - if (deser != null) { - @SuppressWarnings("unchecked") - ValueDeserializer newDeser = (ValueDeserializer) - deser.unwrappingDeserializer(ctxt, xf); - if (newDeser != deser) { - prop = prop.withValueDeserializer(newDeser); - } - } - } - return prop; - } - /** * Mutant factory method that will use this instance as the base, and * construct an instance that is otherwise same except for excluding diff --git a/src/main/java/tools/jackson/databind/deser/bean/PropertyBasedCreator.java b/src/main/java/tools/jackson/databind/deser/bean/PropertyBasedCreator.java index 40d5bec8a0..52b4b74dc0 100644 --- a/src/main/java/tools/jackson/databind/deser/bean/PropertyBasedCreator.java +++ b/src/main/java/tools/jackson/databind/deser/bean/PropertyBasedCreator.java @@ -9,6 +9,7 @@ import tools.jackson.databind.deser.SettableBeanProperty; import tools.jackson.databind.deser.ValueInstantiator; import tools.jackson.databind.deser.impl.ObjectIdReader; +import tools.jackson.databind.util.NameTransformer; /** * Object that is used to collect arguments for non-default creator @@ -92,6 +93,19 @@ protected PropertyBasedCreator(DeserializationContext ctxt, } } + /** + * @since 2.19 + */ + protected PropertyBasedCreator(PropertyBasedCreator base, + HashMap propertyLookup, + SettableBeanProperty[] allProperties) + { + _propertyCount = base._propertyCount; + _valueInstantiator = base._valueInstantiator; + _propertyLookup = propertyLookup; + _propertiesInOrder = allProperties; + } + /** * Factory method used for building actual instances to be used with POJOS: * resolves deserializers, checks for "null values". @@ -143,6 +157,47 @@ public static PropertyBasedCreator construct(DeserializationContext ctxt, caseInsensitive, false); } + /** + * Mutant factory method for constructing a map where the names of all properties + * are transformed using the given {@link NameTransformer}. + * + * @since 2.19 + */ + public PropertyBasedCreator renameAll(DeserializationContext ctxt, + NameTransformer transformer) + { + if (transformer == null || (transformer == NameTransformer.NOP)) { + return this; + } + + final int len = _propertiesInOrder.length; + HashMap newLookup = new HashMap<>(_propertyLookup); + List newProps = new ArrayList<>(len); + + for (SettableBeanProperty prop : _propertiesInOrder) { + if (prop == null) { + newProps.add(null); + continue; + } + + SettableBeanProperty renamedProperty = prop.unwrapped(ctxt, transformer); + String oldName = prop.getName(); + String newName = renamedProperty.getName(); + + newProps.add(renamedProperty); + + if (!oldName.equals(newName) && newLookup.containsKey(oldName)) { + newLookup.remove(oldName); + newLookup.put(newName, renamedProperty); + } + } + + return new PropertyBasedCreator(this, + newLookup, + newProps.toArray(new SettableBeanProperty[0]) + ); + } + /* /********************************************************************** /* Accessors diff --git a/src/main/java/tools/jackson/databind/deser/impl/UnwrappedPropertyHandler.java b/src/main/java/tools/jackson/databind/deser/impl/UnwrappedPropertyHandler.java index 574f111060..9d21216369 100644 --- a/src/main/java/tools/jackson/databind/deser/impl/UnwrappedPropertyHandler.java +++ b/src/main/java/tools/jackson/databind/deser/impl/UnwrappedPropertyHandler.java @@ -3,10 +3,10 @@ import java.util.*; import tools.jackson.core.*; -import tools.jackson.core.util.InternCache; import tools.jackson.databind.DeserializationContext; -import tools.jackson.databind.ValueDeserializer; +import tools.jackson.databind.PropertyName; import tools.jackson.databind.deser.SettableBeanProperty; +import tools.jackson.databind.deser.bean.PropertyValueBuffer; import tools.jackson.databind.util.NameTransformer; import tools.jackson.databind.util.TokenBuffer; @@ -17,16 +17,35 @@ */ public class UnwrappedPropertyHandler { + /** + * @since 2.19 + */ + public static final String JSON_UNWRAPPED_NAME_PREFIX = "@JsonUnwrapped/"; + + /** + * @since 2.19 + */ + protected final List _creatorProperties; protected final List _properties; public UnwrappedPropertyHandler() { - _properties = new ArrayList(); + _creatorProperties = new ArrayList<>(); + _properties = new ArrayList<>(); } - protected UnwrappedPropertyHandler(List props) { + protected UnwrappedPropertyHandler(List creatorProps, + List props) { + _creatorProperties = creatorProps; _properties = props; } + /** + * @since 2.19 + */ + public void addCreatorProperty(SettableBeanProperty property) { + _creatorProperties.add(property); + } + public void addProperty(SettableBeanProperty property) { _properties.add(property); } @@ -34,41 +53,60 @@ public void addProperty(SettableBeanProperty property) { public UnwrappedPropertyHandler renameAll(DeserializationContext ctxt, NameTransformer transformer) { - ArrayList newProps = new ArrayList(_properties.size()); - for (SettableBeanProperty prop : _properties) { - String newName = transformer.transform(prop.getName()); - newName = InternCache.instance.intern(newName); - prop = prop.withSimpleName(newName); - ValueDeserializer deser = prop.getValueDeserializer(); - if (deser != null) { - @SuppressWarnings("unchecked") - ValueDeserializer newDeser = (ValueDeserializer) - deser.unwrappingDeserializer(ctxt, transformer); - if (newDeser != deser) { - prop = prop.withValueDeserializer(newDeser); - } + return new UnwrappedPropertyHandler( + renameProperties(ctxt,_creatorProperties, transformer), + renameProperties(ctxt, _properties, transformer) + ); + } + + private List renameProperties(DeserializationContext ctxt, + Collection properties, + NameTransformer transformer + ) { + List newProps = new ArrayList<>(properties.size()); + for (SettableBeanProperty prop : properties) { + if (prop == null) { + newProps.add(null); + continue; } - newProps.add(prop); + + newProps.add(prop.unwrapped(ctxt, transformer)); } - return new UnwrappedPropertyHandler(newProps); + return newProps; } - /* - public List getHandledProperties() { - return Collections.unmodifiableList(_properties); + /** + * @since 2.19 + */ + public PropertyValueBuffer processUnwrappedCreatorProperties(JsonParser originalParser, + DeserializationContext ctxt, PropertyValueBuffer values, TokenBuffer buffered) + { + for (SettableBeanProperty prop : _creatorProperties) { + JsonParser p = buffered.asParserOnFirstToken(ctxt); + values.assignParameter(prop, prop.deserialize(p, ctxt)); + } + + return values; } - */ @SuppressWarnings("resource") public Object processUnwrapped(JsonParser originalParser, DeserializationContext ctxt, Object bean, TokenBuffer buffered) - throws JacksonException { - for (int i = 0, len = _properties.size(); i < len; ++i) { - SettableBeanProperty prop = _properties.get(i); + for (SettableBeanProperty prop : _properties) { JsonParser p = buffered.asParserOnFirstToken(ctxt); prop.deserializeAndSet(p, ctxt, bean); } return bean; } + + /** + * Generates a placeholder name for creator properties that don't have a name, + * but are marked with `@JsonWrapped` annotation. + * + * @since 2.19 + */ + public static PropertyName creatorParamName(int index) { + return new PropertyName(JSON_UNWRAPPED_NAME_PREFIX + index); + } } diff --git a/src/main/java/tools/jackson/databind/introspect/POJOPropertiesCollector.java b/src/main/java/tools/jackson/databind/introspect/POJOPropertiesCollector.java index 4fc22cfa05..cb4ee5bcb7 100644 --- a/src/main/java/tools/jackson/databind/introspect/POJOPropertiesCollector.java +++ b/src/main/java/tools/jackson/databind/introspect/POJOPropertiesCollector.java @@ -11,6 +11,7 @@ import tools.jackson.databind.cfg.ConstructorDetector; import tools.jackson.databind.cfg.HandlerInstantiator; import tools.jackson.databind.cfg.MapperConfig; +import tools.jackson.databind.deser.impl.UnwrappedPropertyHandler; import tools.jackson.databind.jdk14.JDK14Util; import tools.jackson.databind.util.ClassUtil; @@ -982,10 +983,21 @@ private void _addCreatorParams(Map props, final boolean hasExplicit = (explName != null); final POJOPropertyBuilder prop; + // neither implicit nor explicit name? if (!hasExplicit && (implName == null)) { - // Important: if neither implicit nor explicit name, cannot make use of - // this creator parameter -- may or may not be a problem, verified at a later point. - prop = null; + boolean isUnwrapping = _annotationIntrospector.findUnwrappingNameTransformer(_config, param) != null; + + if (isUnwrapping) { + // If unwrapping, can use regardless of name; we will use a placeholder name + // anyway to try to avoid name conflicts. + PropertyName name = UnwrappedPropertyHandler.creatorParamName(param.getIndex()); + prop = _property(props, name); + prop.addCtor(param, name, false, true, false); + } else { + // Without name, cannot make use of this creator parameter -- may or may not + // be a problem, verified at a later point. + prop = null; + } } else { // 27-Dec-2019, tatu: [databind#2527] may need to rename according to field if (implName != null) { diff --git a/src/test-jdk17/java/tools/jackson/databind/records/RecordWithJsonUnwrappedTest.java b/src/test-jdk17/java/tools/jackson/databind/records/RecordWithJsonUnwrappedTest.java new file mode 100644 index 0000000000..93bf33f4a2 --- /dev/null +++ b/src/test-jdk17/java/tools/jackson/databind/records/RecordWithJsonUnwrappedTest.java @@ -0,0 +1,39 @@ +package tools.jackson.databind.records; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.annotation.JsonUnwrapped; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.node.ObjectNode; +import tools.jackson.databind.testutil.DatabindTestUtil; + +import static org.junit.jupiter.api.Assertions.*; + +public class RecordWithJsonUnwrappedTest extends DatabindTestUtil +{ + record RecordWithJsonUnwrapped(String unrelated, @JsonUnwrapped Inner inner) { + } + + record Inner(String property1, String property2) { + } + + private final ObjectMapper MAPPER = newJsonMapper(); + + @Test + public void testUnwrappedWithRecord() throws Exception + { + RecordWithJsonUnwrapped initial = new RecordWithJsonUnwrapped("unrelatedValue", new Inner("value1", "value2")); + + ObjectNode tree = MAPPER.valueToTree(initial); + + assertEquals("unrelatedValue", tree.get("unrelated").textValue()); + assertEquals("value1", tree.get("property1").textValue()); + assertEquals("value2", tree.get("property2").textValue()); + + RecordWithJsonUnwrapped outer = MAPPER.treeToValue(tree, RecordWithJsonUnwrapped.class); + + assertEquals("unrelatedValue", outer.unrelated()); + assertEquals("value1", outer.inner().property1()); + assertEquals("value2", outer.inner().property2()); + } +} diff --git a/src/test/java/tools/jackson/databind/deser/builder/BuilderErrorHandling.java b/src/test/java/tools/jackson/databind/deser/builder/BuilderErrorHandlingTest.java similarity index 95% rename from src/test/java/tools/jackson/databind/deser/builder/BuilderErrorHandling.java rename to src/test/java/tools/jackson/databind/deser/builder/BuilderErrorHandlingTest.java index 3ae67f3dd0..dd1c38225e 100644 --- a/src/test/java/tools/jackson/databind/deser/builder/BuilderErrorHandling.java +++ b/src/test/java/tools/jackson/databind/deser/builder/BuilderErrorHandlingTest.java @@ -8,12 +8,11 @@ import tools.jackson.databind.annotation.JsonDeserialize; import tools.jackson.databind.exc.MismatchedInputException; import tools.jackson.databind.exc.ValueInstantiationException; +import tools.jackson.databind.testutil.DatabindTestUtil; import static org.junit.jupiter.api.Assertions.*; -import static tools.jackson.databind.testutil.DatabindTestUtil.*; - -public class BuilderErrorHandling +public class BuilderErrorHandlingTest extends DatabindTestUtil { @JsonDeserialize(builder=SimpleBuilderXY.class) static class ValueClassXY @@ -116,7 +115,9 @@ public void testUnknownProperty() throws Exception // first, default failure String json = a2q("{'x':1,'z':2,'y':4}"); try { - MAPPER.readValue(json, ValueClassXY.class); + MAPPER.readerFor(ValueClassXY.class) + .with(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .readValue(json); fail("Should not pass"); } catch (MismatchedInputException e) { verifyException(e, "Unrecognized property "); diff --git a/src/test/java/tools/jackson/databind/struct/UnwrappedCreatorParam265Test.java b/src/test/java/tools/jackson/databind/struct/UnwrappedCreatorParam265Test.java deleted file mode 100644 index b4a7005e14..0000000000 --- a/src/test/java/tools/jackson/databind/struct/UnwrappedCreatorParam265Test.java +++ /dev/null @@ -1,108 +0,0 @@ -package tools.jackson.databind.struct; - -import org.junit.jupiter.api.Test; - -import com.fasterxml.jackson.annotation.*; - -import tools.jackson.databind.*; -import tools.jackson.databind.exc.InvalidDefinitionException; -import tools.jackson.databind.testutil.DatabindTestUtil; - -import static org.junit.jupiter.api.Assertions.*; - -public class UnwrappedCreatorParam265Test extends DatabindTestUtil -{ - static class JAddress { - public String address; - public String city; - public String state; - - protected JAddress() { } - - public JAddress(String address, String city, String state) { - this.address = address; - this.city = city; - this.state = state; - } - } - - static class JPersonWithoutName - { - public String name; - - protected JAddress _address; - - @JsonCreator - public JPersonWithoutName(@JsonProperty("name") String name, - @JsonUnwrapped JAddress address) - { - this.name = name; - _address = address; - } - - @JsonUnwrapped - public JAddress getAddress() { return _address; } - } - - static class JPersonWithName - { - public String name; - - protected JAddress _address; - - @JsonCreator - public JPersonWithName(@JsonProperty("name") String name, - @JsonUnwrapped - @JsonProperty("address") - JAddress address) - { - this.name = name; - _address = address; - } - - @JsonUnwrapped - public JAddress getAddress() { return _address; } - } - - /* - /********************************************************** - /* Test methods - /********************************************************** - */ - - // For [databind#265]: handle problem by throwing exception - @Test - public void testUnwrappedWithUnnamedCreatorParam() throws Exception - { - JPersonWithoutName person = new JPersonWithoutName("MyName", new JAddress("main street", "springfield", "WA")); - ObjectMapper mapper = newJsonMapper(); - // serialization should be fine as far as that goes - String json = mapper.writeValueAsString(person); - - // but not deserialization: - try { - /*JPersonWithoutName result =*/ mapper.readValue(json, JPersonWithoutName.class); - fail("Should not pass"); - } catch (InvalidDefinitionException e) { - verifyException(e, "Cannot define Creator property"); - verifyException(e, "@JsonUnwrapped"); - } - } - - // For [databind#265]: handle problem by throwing exception - @Test - public void testUnwrappedWithNamedCreatorParam() throws Exception - { - JPersonWithName person = new JPersonWithName("MyName", new JAddress("main street", "springfield", "WA")); - ObjectMapper mapper = newJsonMapper(); - // serialization should be fine as far as that goes - String json = mapper.writeValueAsString(person); - try { - /*JPersonWithName result =*/ mapper.readValue(json, JPersonWithName.class); - fail("Should not pass"); - } catch (InvalidDefinitionException e) { - verifyException(e, "Cannot define Creator property \"address\""); - verifyException(e, "@JsonUnwrapped"); - } - } -} diff --git a/src/test/java/tools/jackson/databind/tofix/UnwrappedWithCreator1467Test.java b/src/test/java/tools/jackson/databind/tofix/UnwrappedWithCreator1467Test.java new file mode 100644 index 0000000000..baa7004f98 --- /dev/null +++ b/src/test/java/tools/jackson/databind/tofix/UnwrappedWithCreator1467Test.java @@ -0,0 +1,174 @@ +package tools.jackson.databind.tofix; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.annotation.*; + +import tools.jackson.databind.*; +import tools.jackson.databind.testutil.DatabindTestUtil; +import tools.jackson.databind.testutil.failure.JacksonTestFailureExpected; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests to verify [databind#1467]. + */ +public class UnwrappedWithCreator1467Test extends DatabindTestUtil +{ + static class ExplicitWithoutName { + private final String _unrelated; + private final Inner _inner; + + @JsonCreator + public ExplicitWithoutName(@JsonProperty("unrelated") String unrelated, @JsonUnwrapped Inner inner) { + _unrelated = unrelated; + _inner = inner; + } + + public String getUnrelated() { + return _unrelated; + } + + @JsonUnwrapped + public Inner getInner() { + return _inner; + } + } + + static class ExplicitWithName { + private final String _unrelated; + private final Inner _inner; + + @JsonCreator + public ExplicitWithName(@JsonProperty("unrelated") String unrelated, @JsonProperty("inner") @JsonUnwrapped Inner inner) { + _unrelated = unrelated; + _inner = inner; + } + + public String getUnrelated() { + return _unrelated; + } + + public Inner getInner() { + return _inner; + } + } + + static class ImplicitWithName { + private final String _unrelated; + private final Inner _inner; + + public ImplicitWithName(@JsonProperty("unrelated") String unrelated, @JsonProperty("inner") @JsonUnwrapped Inner inner) { + _unrelated = unrelated; + _inner = inner; + } + + public String getUnrelated() { + return _unrelated; + } + + public Inner getInner() { + return _inner; + } + } + + static class WithTwoUnwrappedProperties { + private final String _unrelated; + private final Inner _inner1; + private final Inner _inner2; + + public WithTwoUnwrappedProperties( + @JsonProperty("unrelated") String unrelated, + @JsonUnwrapped(prefix = "first-") Inner inner1, + @JsonUnwrapped(prefix = "second-") Inner inner2 + ) { + _unrelated = unrelated; + _inner1 = inner1; + _inner2 = inner2; + } + + public String getUnrelated() { + return _unrelated; + } + + @JsonUnwrapped(prefix = "first-") + public Inner getInner1() { + return _inner1; + } + + @JsonUnwrapped(prefix = "second-") + public Inner getInner2() { + return _inner2; + } + } + + static class Inner { + private final String _property1; + private final String _property2; + + public Inner(@JsonProperty("property1") String property1, @JsonProperty("property2") String property2) { + _property1 = property1; + _property2 = property2; + } + + public String getProperty1() { + return _property1; + } + + public String getProperty2() { + return _property2; + } + } + + private final ObjectMapper MAPPER = newJsonMapper(); + + @Test + public void testUnwrappedWithJsonCreatorWithExplicitWithoutName() throws Exception + { + String json = "{\"unrelated\": \"unrelatedValue\", \"property1\": \"value1\", \"property2\": \"value2\"}"; + ExplicitWithoutName outer = MAPPER.readValue(json, ExplicitWithoutName.class); + + assertEquals("unrelatedValue", outer.getUnrelated()); + assertEquals("value1", outer.getInner().getProperty1()); + assertEquals("value2", outer.getInner().getProperty2()); + } + + @Test + public void testUnwrappedWithJsonCreatorExplicitWithName() throws Exception + { + String json = "{\"unrelated\": \"unrelatedValue\", \"property1\": \"value1\", \"property2\": \"value2\"}"; + ExplicitWithName outer = MAPPER.readValue(json, ExplicitWithName.class); + + assertEquals("unrelatedValue", outer.getUnrelated()); + assertEquals("value1", outer.getInner().getProperty1()); + assertEquals("value2", outer.getInner().getProperty2()); + } + + @Test + public void testUnwrappedWithJsonCreatorImplicitWithName() throws Exception + { + String json = "{\"unrelated\": \"unrelatedValue\", \"property1\": \"value1\", \"property2\": \"value2\"}"; + ImplicitWithName outer = MAPPER.readValue(json, ImplicitWithName.class); + + assertEquals("unrelatedValue", outer.getUnrelated()); + assertEquals("value1", outer.getInner().getProperty1()); + assertEquals("value2", outer.getInner().getProperty2()); + } + + // [databind#1467]: works for 2.19+, fails for 3.0 for some reason + @JacksonTestFailureExpected + @Test + public void testUnwrappedWithTwoUnwrappedProperties() throws Exception + { + String json = "{\"unrelated\": \"unrelatedValue\", " + + "\"first-property1\": \"first-value1\", \"first-property2\": \"first-value2\", " + + "\"second-property1\": \"second-value1\", \"second-property2\": \"second-value2\"}"; + WithTwoUnwrappedProperties outer = MAPPER.readValue(json, WithTwoUnwrappedProperties.class); + + assertEquals("unrelatedValue", outer.getUnrelated()); + assertEquals("first-value1", outer.getInner1().getProperty1()); + assertEquals("first-value2", outer.getInner1().getProperty2()); + assertEquals("second-value1", outer.getInner2().getProperty1()); + assertEquals("second-value2", outer.getInner2().getProperty2()); + } +} diff --git a/src/test/java/tools/jackson/databind/tofix/UnwrappedWithPrefixCreator1467Test.java b/src/test/java/tools/jackson/databind/tofix/UnwrappedWithPrefixCreator1467Test.java new file mode 100644 index 0000000000..2fe43baf4c --- /dev/null +++ b/src/test/java/tools/jackson/databind/tofix/UnwrappedWithPrefixCreator1467Test.java @@ -0,0 +1,44 @@ +package tools.jackson.databind.tofix; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonUnwrapped; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.testutil.DatabindTestUtil; +import tools.jackson.databind.testutil.failure.JacksonTestFailureExpected; + +import static org.junit.jupiter.api.Assertions.*; + +// [databind#1467]: works for 2.19+, fails for 3.0 for some reason +public class UnwrappedWithPrefixCreator1467Test extends DatabindTestUtil +{ + static class Outer { + @JsonUnwrapped(prefix = "inner-") + Inner inner; + } + + static class Inner { + private final String _property; + + public Inner(@JsonProperty("property") String property) { + _property = property; + } + + public String getProperty() { + return _property; + } + } + + private final ObjectMapper MAPPER = newJsonMapper(); + + @JacksonTestFailureExpected + @Test + public void testUnwrappedWithJsonCreatorWithExplicitWithoutName() throws Exception + { + String json = "{\"inner-property\": \"value\"}"; + Outer outer = MAPPER.readValue(json, Outer.class); + + assertEquals("value", outer.inner.getProperty()); + } +}