diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java b/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java index ec99b8f7f7..7606ef2e64 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java @@ -1,22 +1,10 @@ package com.fasterxml.jackson.databind.deser; -import java.io.Serializable; -import java.math.BigDecimal; -import java.math.BigInteger; -import java.util.*; -import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicReference; - import com.fasterxml.jackson.annotation.*; - import com.fasterxml.jackson.core.JsonParser; - import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.cfg.*; -import com.fasterxml.jackson.databind.deser.impl.CreatorCandidate; -import com.fasterxml.jackson.databind.deser.impl.CreatorCollector; -import com.fasterxml.jackson.databind.deser.impl.JDKValueInstantiators; -import com.fasterxml.jackson.databind.deser.impl.JavaUtilCollectionsDeserializers; +import com.fasterxml.jackson.databind.deser.impl.*; import com.fasterxml.jackson.databind.deser.std.*; import com.fasterxml.jackson.databind.exc.InvalidDefinitionException; import com.fasterxml.jackson.databind.ext.OptionalHandlerFactory; @@ -27,6 +15,15 @@ import com.fasterxml.jackson.databind.type.*; import com.fasterxml.jackson.databind.util.*; +import java.io.Serializable; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.*; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicReference; + /** * Abstract factory base class that can provide deserializers for standard * JDK classes, including collection classes and simple heuristics for @@ -49,12 +46,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 @@ -406,11 +397,7 @@ private void _addImplicitDelegatingConstructors(DeserializationContext ctxt, } NameTransformer unwrapper = intr.findUnwrappingNameTransformer(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); } } @@ -427,7 +414,7 @@ private void _addImplicitDelegatingConstructors(DeserializationContext ctxt, */ } } - + private void _addImplicitDelegatingFactories(DeserializationContext ctxt, VisibilityChecker vchecker, CreatorCollector creators, @@ -528,7 +515,7 @@ private void _addSelectedPropertiesBasedCreator(DeserializationContext ctxt, // as that will not work with Creators well at all NameTransformer unwrapper = ctxt.getAnnotationIntrospector().findUnwrappingNameTransformer(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)) { @@ -595,17 +582,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) - throws JsonMappingException - { - 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/com/fasterxml/jackson/databind/deser/BeanDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java index f7f5bb1ca4..cafd0253c5 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java @@ -934,6 +934,10 @@ protected Object deserializeUsingPropertyBasedWithUnwrapped(JsonParser p, Deseri } } + // We could still have some unset creator properties that are unwrapped. These have to be processed last, because 'tokens' contains + // all the 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/com/fasterxml/jackson/databind/deser/BeanDeserializerBase.java b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBase.java index 3e63a4a6b7..a8ef62ad77 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBase.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBase.java @@ -308,7 +308,6 @@ protected BeanDeserializerBase(BeanDeserializerBase src, NameTransformer unwrapp _valueInstantiator = src._valueInstantiator; _delegateDeserializer = src._delegateDeserializer; _arrayDelegateDeserializer = src._arrayDelegateDeserializer; - _propertyBasedCreator = src._propertyBasedCreator; _backRefs = src._backRefs; _ignorableProps = src._ignorableProps; @@ -320,6 +319,7 @@ protected BeanDeserializerBase(BeanDeserializerBase src, NameTransformer unwrapp _nonStandardCreation = src._nonStandardCreation; UnwrappedPropertyHandler uph = src._unwrappedPropertyHandler; + PropertyBasedCreator pbc = src._propertyBasedCreator; if (unwrapper != null) { // delegate further unwraps, if any @@ -327,8 +327,10 @@ protected BeanDeserializerBase(BeanDeserializerBase src, NameTransformer unwrapp uph = uph.renameAll(unwrapper); } // and handle direct unwrapping as well: + _propertyBasedCreator = pbc != null ? pbc.renameAll(unwrapper) : null; _beanProperties = src._beanProperties.renameAll(unwrapper); } else { + _propertyBasedCreator = pbc; _beanProperties = src._beanProperties; } _unwrappedPropertyHandler = uph; @@ -579,7 +581,13 @@ public void resolve(DeserializationContext ctxt) throws JsonMappingException 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? @@ -1005,20 +1013,11 @@ protected SettableBeanProperty _resolvedObjectIdProperty(DeserializationContext * property: these require special handling. */ protected NameTransformer _findPropertyUnwrapper(DeserializationContext ctxt, - SettableBeanProperty prop) - throws JsonMappingException - { + SettableBeanProperty prop) { AnnotatedMember am = prop.getMember(); if (am != null) { NameTransformer unwrapper = ctxt.getAnnotationIntrospector().findUnwrappingNameTransformer(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/com/fasterxml/jackson/databind/deser/SettableBeanProperty.java b/src/main/java/com/fasterxml/jackson/databind/deser/SettableBeanProperty.java index 6335e27dfa..d62b531784 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/SettableBeanProperty.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/SettableBeanProperty.java @@ -13,6 +13,7 @@ import com.fasterxml.jackson.databind.jsontype.TypeDeserializer; import com.fasterxml.jackson.databind.util.Annotations; import com.fasterxml.jackson.databind.util.ClassUtil; +import com.fasterxml.jackson.databind.util.NameTransformer; import com.fasterxml.jackson.databind.util.ViewMatcher; /** @@ -584,6 +585,25 @@ public final Object deserializeWith(JsonParser p, DeserializationContext ctxt, return value; } + /** + * Returns a copy of this property, unwrapped using the given {@link NameTransformer}. + */ + public SettableBeanProperty unwrapped(NameTransformer xf) + { + String newName = xf.transform(getName()); + SettableBeanProperty renamed = withSimpleName(newName); + JsonDeserializer deser = renamed.getValueDeserializer(); + if (deser != null) { + @SuppressWarnings("unchecked") + JsonDeserializer newDeser = (JsonDeserializer) + deser.unwrappingDeserializer(xf); + if (newDeser != deser) { + renamed = renamed.withValueDeserializer(newDeser); + } + } + return renamed; + } + /* /********************************************************** /* Helper methods diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/impl/BeanPropertyMap.java b/src/main/java/com/fasterxml/jackson/databind/deser/impl/BeanPropertyMap.java index ac4a7ede80..5c98497c60 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/impl/BeanPropertyMap.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/impl/BeanPropertyMap.java @@ -362,7 +362,7 @@ public BeanPropertyMap renameAll(NameTransformer transformer) newProps.add(prop); continue; } - newProps.add(_rename(prop, transformer)); + newProps.add(prop.unwrapped(transformer)); } // should we try to re-index? Ordering probably changed but caller probably doesn't want changes... // 26-Feb-2017, tatu: Probably SHOULD handle renaming wrt Aliases? @@ -713,25 +713,6 @@ public String toString() /********************************************************** */ - protected SettableBeanProperty _rename(SettableBeanProperty prop, NameTransformer xf) - { - if (prop == null) { - return prop; - } - String newName = xf.transform(prop.getName()); - prop = prop.withSimpleName(newName); - JsonDeserializer deser = prop.getValueDeserializer(); - if (deser != null) { - @SuppressWarnings("unchecked") - JsonDeserializer newDeser = (JsonDeserializer) - deser.unwrappingDeserializer(xf); - if (newDeser != deser) { - prop = prop.withValueDeserializer(newDeser); - } - } - return prop; - } - protected void wrapAndThrow(Throwable t, Object bean, String fieldName, DeserializationContext ctxt) throws IOException { diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java b/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java index b11d2111b5..c0d319f659 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java @@ -9,6 +9,7 @@ import com.fasterxml.jackson.databind.deser.SettableAnyProperty; import com.fasterxml.jackson.databind.deser.SettableBeanProperty; import com.fasterxml.jackson.databind.deser.ValueInstantiator; +import com.fasterxml.jackson.databind.util.NameTransformer; /** * Object that is used to collect arguments for non-default creator @@ -93,6 +94,21 @@ protected PropertyBasedCreator(DeserializationContext ctxt, } } + /** + * @since 2.18 + */ + protected PropertyBasedCreator( + int propertyCount, + ValueInstantiator valueInstantiator, + HashMap propertyLookup, + SettableBeanProperty[] allProperties + ) { + _propertyCount = propertyCount; + _valueInstantiator = valueInstantiator; + _propertyLookup = propertyLookup; + _allProperties = allProperties; + } + /** * Factory method used for building actual instances to be used with POJOS: * resolves deserializers, checks for "null values". @@ -159,6 +175,46 @@ public static PropertyBasedCreator construct(DeserializationContext ctxt, ctxt.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES)); } + /** + * Mutant factory method for constructing a map where the names of all properties + * are transformed using the given {@link NameTransformer}. + */ + public PropertyBasedCreator renameAll(NameTransformer transformer) + { + if (transformer == null || (transformer == NameTransformer.NOP)) { + return this; + } + + final int len = _allProperties.length; + HashMap newLookup = new HashMap<>(_propertyLookup); + ArrayList newProps = new ArrayList<>(len); + + for (SettableBeanProperty prop : _allProperties) { + if (prop == null) { + newProps.add(null); + continue; + } + + SettableBeanProperty renamedProperty = prop.unwrapped(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( + _propertyCount, + _valueInstantiator, + newLookup, + newProps.toArray(new SettableBeanProperty[newProps.size()]) + ); + } + /* /********************************************************** /* Accessors diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/impl/UnwrappedPropertyHandler.java b/src/main/java/com/fasterxml/jackson/databind/deser/impl/UnwrappedPropertyHandler.java index 5dadec1271..236062e083 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/impl/UnwrappedPropertyHandler.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/impl/UnwrappedPropertyHandler.java @@ -6,6 +6,7 @@ import com.fasterxml.jackson.core.*; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.PropertyName; import com.fasterxml.jackson.databind.deser.SettableBeanProperty; import com.fasterxml.jackson.databind.util.NameTransformer; import com.fasterxml.jackson.databind.util.TokenBuffer; @@ -17,51 +18,83 @@ */ public class UnwrappedPropertyHandler { + 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; } + public void addCreatorProperty(SettableBeanProperty property) { + _creatorProperties.add(property); + } + public void addProperty(SettableBeanProperty property) { _properties.add(property); } - public UnwrappedPropertyHandler renameAll(NameTransformer transformer) - { - ArrayList newProps = new ArrayList(_properties.size()); - for (SettableBeanProperty prop : _properties) { - String newName = transformer.transform(prop.getName()); - prop = prop.withSimpleName(newName); - JsonDeserializer deser = prop.getValueDeserializer(); - if (deser != null) { - @SuppressWarnings("unchecked") - JsonDeserializer newDeser = (JsonDeserializer) - deser.unwrappingDeserializer(transformer); - if (newDeser != deser) { - prop = prop.withValueDeserializer(newDeser); - } + public UnwrappedPropertyHandler renameAll(NameTransformer transformer) { + return new UnwrappedPropertyHandler( + renameProperties(_creatorProperties, transformer), + renameProperties(_properties, transformer) + ); + } + + private List renameProperties( + 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(transformer)); + } + return newProps; + } + + public PropertyValueBuffer processUnwrappedCreatorProperties( + JsonParser originalParser, + DeserializationContext ctxt, + PropertyValueBuffer values, + TokenBuffer buffered + ) throws IOException { + for (SettableBeanProperty prop : _creatorProperties) { + JsonParser p = buffered.asParser(originalParser.streamReadConstraints()); + p.nextToken(); + Object deserialized = prop.deserialize(p, ctxt); + values.assignParameter(prop, deserialized); } - return new UnwrappedPropertyHandler(newProps); + + return values; } - @SuppressWarnings("resource") public Object processUnwrapped(JsonParser originalParser, DeserializationContext ctxt, Object bean, TokenBuffer buffered) throws IOException { - for (int i = 0, len = _properties.size(); i < len; ++i) { - SettableBeanProperty prop = _properties.get(i); + for (SettableBeanProperty prop : _properties) { JsonParser p = buffered.asParser(originalParser.streamReadConstraints()); p.nextToken(); prop.deserializeAndSet(p, ctxt, bean); } return bean; } + + /** + * We need a placeholder for creator properties that don't have a name, + * but are marked with `@JsonWrapped` annotation. + */ + public static PropertyName creatorParamName(int index) { + return new PropertyName("@JsonUnwrapped/" + index); + } } diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertiesCollector.java b/src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertiesCollector.java index e547995e54..175d63f267 100644 --- a/src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertiesCollector.java +++ b/src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertiesCollector.java @@ -11,6 +11,7 @@ import com.fasterxml.jackson.databind.cfg.ConstructorDetector; import com.fasterxml.jackson.databind.cfg.HandlerInstantiator; import com.fasterxml.jackson.databind.cfg.MapperConfig; +import com.fasterxml.jackson.databind.deser.impl.UnwrappedPropertyHandler; import com.fasterxml.jackson.databind.jdk14.JDK14Util; import com.fasterxml.jackson.databind.util.ClassUtil; @@ -1021,9 +1022,19 @@ private void _addCreatorParams(Map props, final POJOPropertyBuilder prop; 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(param) != null; + + if (isUnwrapping) { + // We can still use this property, as the name does not matter for unwrapping properties. + // We just use a placeholder name instead. + PropertyName name = UnwrappedPropertyHandler.creatorParamName(param._index); + prop = _property(props, name); + prop.addCtor(param, name, false, true, false); + } else { + // 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; + } } else { // 27-Dec-2019, tatu: [databind#2527] may need to rename according to field if (implName != null) { diff --git a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordWithJsonUnwrappedTest.java b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordWithJsonUnwrappedTest.java new file mode 100644 index 0000000000..726066503b --- /dev/null +++ b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordWithJsonUnwrappedTest.java @@ -0,0 +1,37 @@ +package com.fasterxml.jackson.databind.records; + +import com.fasterxml.jackson.annotation.JsonUnwrapped; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.testutil.DatabindTestUtil; +import org.junit.jupiter.api.Test; + +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/com/fasterxml/jackson/databind/struct/UnwrappedCreatorParam265Test.java b/src/test/java/com/fasterxml/jackson/databind/struct/UnwrappedCreatorParam265Test.java deleted file mode 100644 index 4bfaf972c6..0000000000 --- a/src/test/java/com/fasterxml/jackson/databind/struct/UnwrappedCreatorParam265Test.java +++ /dev/null @@ -1,108 +0,0 @@ -package com.fasterxml.jackson.databind.struct; - -import org.junit.jupiter.api.Test; - -import com.fasterxml.jackson.annotation.*; - -import com.fasterxml.jackson.databind.*; -import com.fasterxml.jackson.databind.exc.InvalidDefinitionException; -import com.fasterxml.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 parameter"); - 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/com/fasterxml/jackson/databind/struct/UnwrappedPropertyBasedCreatorWithPrefixTest.java b/src/test/java/com/fasterxml/jackson/databind/struct/UnwrappedPropertyBasedCreatorWithPrefixTest.java new file mode 100644 index 0000000000..5f13d53d18 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/struct/UnwrappedPropertyBasedCreatorWithPrefixTest.java @@ -0,0 +1,40 @@ +package com.fasterxml.jackson.databind.struct; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonUnwrapped; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.testutil.DatabindTestUtil; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class UnwrappedPropertyBasedCreatorWithPrefixTest 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(); + + @Test + public void testUnwrappedWithJsonCreatorWithExplicitWithoutName() throws Exception + { + String json = "{\"inner-property\": \"value\"}"; + Outer outer = MAPPER.readValue(json, Outer.class); + + assertEquals("value", outer.inner.getProperty()); + } +} diff --git a/src/test/java/com/fasterxml/jackson/databind/struct/UnwrappedWithCreatorTest.java b/src/test/java/com/fasterxml/jackson/databind/struct/UnwrappedWithCreatorTest.java new file mode 100644 index 0000000000..f6e624bc2e --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/struct/UnwrappedWithCreatorTest.java @@ -0,0 +1,169 @@ +package com.fasterxml.jackson.databind.struct; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.databind.testutil.DatabindTestUtil; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests to verify [databind#1467]. + */ +public class UnwrappedWithCreatorTest 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()); + } + + @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()); + } +}