diff --git a/infobip-jackson-extension-module/src/main/java/com/infobip/jackson/InfobipJacksonModule.java b/infobip-jackson-extension-module/src/main/java/com/infobip/jackson/InfobipJacksonModule.java index eebcd22..d25bf5c 100644 --- a/infobip-jackson-extension-module/src/main/java/com/infobip/jackson/InfobipJacksonModule.java +++ b/infobip-jackson-extension-module/src/main/java/com/infobip/jackson/InfobipJacksonModule.java @@ -8,5 +8,6 @@ public class InfobipJacksonModule extends SimpleModule { public void setupModule(SetupContext context) { super.setupModule(context); context.insertAnnotationIntrospector(new InfobipJacksonAnnotationIntrospector()); + context.insertAnnotationIntrospector(new SingleArgumentPropertiesCreatorModeAnnotationIntrospector()); } -} \ No newline at end of file +} diff --git a/infobip-jackson-extension-module/src/main/java/com/infobip/jackson/SingleArgumentPropertiesCreatorModeAnnotationIntrospector.java b/infobip-jackson-extension-module/src/main/java/com/infobip/jackson/SingleArgumentPropertiesCreatorModeAnnotationIntrospector.java new file mode 100644 index 0000000..bd1987d --- /dev/null +++ b/infobip-jackson-extension-module/src/main/java/com/infobip/jackson/SingleArgumentPropertiesCreatorModeAnnotationIntrospector.java @@ -0,0 +1,56 @@ +package com.infobip.jackson; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.cfg.MapperConfig; +import com.fasterxml.jackson.databind.introspect.*; + +import java.lang.reflect.Field; +import java.util.Objects; +import java.util.stream.Stream; + +public class SingleArgumentPropertiesCreatorModeAnnotationIntrospector extends NopAnnotationIntrospector { + + @Override + public JsonCreator.Mode findCreatorAnnotation(MapperConfig config, Annotated a) { + if (!(a instanceof AnnotatedConstructor)) { + return null; + } + + AnnotatedConstructor annotatedConstructor = (AnnotatedConstructor) a; + + if (annotatedConstructor.getParameterCount() != 1) { + return null; + } + + Class declaringClass = annotatedConstructor.getDeclaringClass(); + + if (declaringClass.getDeclaredConstructors().length > 1) { + return null; + } + + if (doesntHaveMatchingFieldAndParameter(annotatedConstructor, declaringClass)) { + return null; + } + + return JsonCreator.Mode.PROPERTIES; + } + + private boolean doesntHaveMatchingFieldAndParameter(AnnotatedConstructor annotatedConstructor, + Class declaringClass) { + String parameterName = annotatedConstructor.getAnnotated().getParameters()[0].getName(); + return Stream.of(declaringClass.getDeclaredFields()) + .noneMatch(field -> doesFieldMatchParameter(field, parameterName)); + } + + private boolean doesFieldMatchParameter(Field field, String parameterName) { + + JsonProperty annotation = field.getAnnotation(JsonProperty.class); + + if (Objects.nonNull(annotation) && annotation.value().equals(parameterName)) { + return true; + } + + return field.getName().equals(parameterName); + } +} diff --git a/infobip-jackson-extension-module/src/test/java/com/infobip/jackson/CustomTypeFieldSimpleJsonHierarchyTest.java b/infobip-jackson-extension-module/src/test/java/com/infobip/jackson/CustomTypeFieldSimpleJsonHierarchyTest.java index 77039d1..350a648 100644 --- a/infobip-jackson-extension-module/src/test/java/com/infobip/jackson/CustomTypeFieldSimpleJsonHierarchyTest.java +++ b/infobip-jackson-extension-module/src/test/java/com/infobip/jackson/CustomTypeFieldSimpleJsonHierarchyTest.java @@ -110,14 +110,12 @@ interface FooBar extends SimpleJsonHierarchy { FooBarType getType(); } - @AllArgsConstructor(onConstructor_ = @JsonCreator) @Value static class Foo implements FooBar { private final String foo; private final FooBarType type = FooBarType.FOO; } - @AllArgsConstructor(onConstructor_ = @JsonCreator) @Value static class Bar implements FooBar { private final String bar; @@ -132,4 +130,4 @@ enum FooBarType implements TypeProvider { private final Class type; } -} \ No newline at end of file +} diff --git a/infobip-jackson-extension-module/src/test/java/com/infobip/jackson/JsonTypedAbstractClassDeserializerTest.java b/infobip-jackson-extension-module/src/test/java/com/infobip/jackson/JsonTypedAbstractClassDeserializerTest.java index be25be6..e722226 100644 --- a/infobip-jackson-extension-module/src/test/java/com/infobip/jackson/JsonTypedAbstractClassDeserializerTest.java +++ b/infobip-jackson-extension-module/src/test/java/com/infobip/jackson/JsonTypedAbstractClassDeserializerTest.java @@ -1,6 +1,5 @@ package com.infobip.jackson; -import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.core.JsonProcessingException; import lombok.*; import org.junit.jupiter.api.Test; @@ -48,14 +47,12 @@ public Class resolve(Map json) { } } - @AllArgsConstructor(onConstructor_ = @JsonCreator) @Value static class Foo extends FooBar { private final String foo; private final FooBarType type = FooBarType.FOO; } - @AllArgsConstructor(onConstructor_ = @JsonCreator) @Value static class Bar extends FooBar { private final String bar; @@ -70,4 +67,4 @@ enum FooBarType { private final Class type; } -} \ No newline at end of file +} diff --git a/infobip-jackson-extension-module/src/test/java/com/infobip/jackson/JsonTypedDeserializerTest.java b/infobip-jackson-extension-module/src/test/java/com/infobip/jackson/JsonTypedDeserializerTest.java index 75c2e22..b7d4d5b 100644 --- a/infobip-jackson-extension-module/src/test/java/com/infobip/jackson/JsonTypedDeserializerTest.java +++ b/infobip-jackson-extension-module/src/test/java/com/infobip/jackson/JsonTypedDeserializerTest.java @@ -1,6 +1,5 @@ package com.infobip.jackson; -import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import lombok.*; @@ -113,14 +112,12 @@ public Class resolve(Map json) { } } - @AllArgsConstructor(onConstructor_ = @JsonCreator) @Value static class Foo implements FooBar { private final String foo; private final FooBarType type = FooBarType.FOO; } - @AllArgsConstructor(onConstructor_ = @JsonCreator) @Value static class Bar implements FooBar { private final String bar; @@ -135,4 +132,4 @@ enum FooBarType { private final Class type; } -} \ No newline at end of file +} diff --git a/infobip-jackson-extension-module/src/test/java/com/infobip/jackson/LowerCaseTypeSimpleJsonHierarchyTest.java b/infobip-jackson-extension-module/src/test/java/com/infobip/jackson/LowerCaseTypeSimpleJsonHierarchyTest.java index f9e9d7d..7b3bd8b 100644 --- a/infobip-jackson-extension-module/src/test/java/com/infobip/jackson/LowerCaseTypeSimpleJsonHierarchyTest.java +++ b/infobip-jackson-extension-module/src/test/java/com/infobip/jackson/LowerCaseTypeSimpleJsonHierarchyTest.java @@ -1,6 +1,5 @@ package com.infobip.jackson; -import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; @@ -108,14 +107,12 @@ interface FooBar extends SimpleJsonHierarchy { FooBarType getType(); } - @AllArgsConstructor(onConstructor_ = @JsonCreator) @Value static class Foo implements FooBar { private final String foo; private final FooBarType type = FooBarType.FOO; } - @AllArgsConstructor(onConstructor_ = @JsonCreator) @Value static class Bar implements FooBar { private final String bar; @@ -135,4 +132,4 @@ String getValue() { return toString().toLowerCase(); } } -} \ No newline at end of file +} diff --git a/infobip-jackson-extension-module/src/test/java/com/infobip/jackson/MultiHierarchyJsonTypedDeserializerTest.java b/infobip-jackson-extension-module/src/test/java/com/infobip/jackson/MultiHierarchyJsonTypedDeserializerTest.java index 144c81f..46fb526 100644 --- a/infobip-jackson-extension-module/src/test/java/com/infobip/jackson/MultiHierarchyJsonTypedDeserializerTest.java +++ b/infobip-jackson-extension-module/src/test/java/com/infobip/jackson/MultiHierarchyJsonTypedDeserializerTest.java @@ -1,6 +1,5 @@ package com.infobip.jackson; -import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import lombok.*; @@ -123,7 +122,6 @@ public MammalJsonTypeResolver() { } } - @AllArgsConstructor(onConstructor_ = @JsonCreator) @Value static class Human implements Mammal { private final String name; @@ -146,4 +144,4 @@ enum MammalType implements TypeProvider { private final Class type; } -} \ No newline at end of file +} diff --git a/infobip-jackson-extension-module/src/test/java/com/infobip/jackson/MultiHierarchyWithOneTypeFieldJsonTypedDeserializerTest.java b/infobip-jackson-extension-module/src/test/java/com/infobip/jackson/MultiHierarchyWithOneTypeFieldJsonTypedDeserializerTest.java index f436f3d..76d0e3b 100644 --- a/infobip-jackson-extension-module/src/test/java/com/infobip/jackson/MultiHierarchyWithOneTypeFieldJsonTypedDeserializerTest.java +++ b/infobip-jackson-extension-module/src/test/java/com/infobip/jackson/MultiHierarchyWithOneTypeFieldJsonTypedDeserializerTest.java @@ -1,6 +1,5 @@ package com.infobip.jackson; -import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import lombok.*; @@ -106,7 +105,6 @@ interface Animal extends SimpleJsonHierarchy { interface Mammal extends Animal { } - @AllArgsConstructor(onConstructor_ = @JsonCreator) @Value static class Human implements Mammal { private final String name; @@ -124,4 +122,4 @@ enum AnimalType implements TypeProvider { private final Class type; } -} \ No newline at end of file +} diff --git a/infobip-jackson-extension-module/src/test/java/com/infobip/jackson/PresentPropertyCaseFormatDeserializerTest.java b/infobip-jackson-extension-module/src/test/java/com/infobip/jackson/PresentPropertyCaseFormatDeserializerTest.java index 7ac5563..dfd6ddb 100644 --- a/infobip-jackson-extension-module/src/test/java/com/infobip/jackson/PresentPropertyCaseFormatDeserializerTest.java +++ b/infobip-jackson-extension-module/src/test/java/com/infobip/jackson/PresentPropertyCaseFormatDeserializerTest.java @@ -1,6 +1,5 @@ package com.infobip.jackson; -import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.PropertyNamingStrategy; @@ -117,14 +116,12 @@ public LowerUnderscorePresentPropertyJsonTypeResolver(Class type) { } } - @AllArgsConstructor(onConstructor_ = @JsonCreator) @Value static class RoadBike implements Bike { private final String roadBike; } - @AllArgsConstructor(onConstructor_ = @JsonCreator) @Value static class MountainBike implements Bike { diff --git a/infobip-jackson-extension-module/src/test/java/com/infobip/jackson/PresentPropertyDeserializerTest.java b/infobip-jackson-extension-module/src/test/java/com/infobip/jackson/PresentPropertyDeserializerTest.java index ba7eb9f..0a3c62c 100644 --- a/infobip-jackson-extension-module/src/test/java/com/infobip/jackson/PresentPropertyDeserializerTest.java +++ b/infobip-jackson-extension-module/src/test/java/com/infobip/jackson/PresentPropertyDeserializerTest.java @@ -1,6 +1,5 @@ package com.infobip.jackson; -import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import lombok.*; @@ -103,14 +102,12 @@ interface Bike extends PresentPropertyJsonHierarchy { } - @AllArgsConstructor(onConstructor_ = @JsonCreator) @Value static class RoadBike implements Bike { private final String roadBike; } - @AllArgsConstructor(onConstructor_ = @JsonCreator) @Value static class Bmx implements Bike { diff --git a/infobip-jackson-extension-module/src/test/java/com/infobip/jackson/PresentPropertyResolverWithDefaultConstructorTest.java b/infobip-jackson-extension-module/src/test/java/com/infobip/jackson/PresentPropertyResolverWithDefaultConstructorTest.java index 22a39ff..2afbad0 100644 --- a/infobip-jackson-extension-module/src/test/java/com/infobip/jackson/PresentPropertyResolverWithDefaultConstructorTest.java +++ b/infobip-jackson-extension-module/src/test/java/com/infobip/jackson/PresentPropertyResolverWithDefaultConstructorTest.java @@ -1,6 +1,5 @@ package com.infobip.jackson; -import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.core.JsonProcessingException; import com.google.common.base.CaseFormat; import lombok.*; @@ -43,14 +42,12 @@ public BikeTypeResolver() { } } - @AllArgsConstructor(onConstructor_ = @JsonCreator) @Value static class RoadBike implements Bike { private final String roadBike; } - @AllArgsConstructor(onConstructor_ = @JsonCreator) @Value static class MountainBike implements Bike { diff --git a/infobip-jackson-extension-module/src/test/java/com/infobip/jackson/SimpleJsonHierarchyDeserializerTest.java b/infobip-jackson-extension-module/src/test/java/com/infobip/jackson/SimpleJsonHierarchyDeserializerTest.java index fe08905..ea87744 100644 --- a/infobip-jackson-extension-module/src/test/java/com/infobip/jackson/SimpleJsonHierarchyDeserializerTest.java +++ b/infobip-jackson-extension-module/src/test/java/com/infobip/jackson/SimpleJsonHierarchyDeserializerTest.java @@ -1,6 +1,5 @@ package com.infobip.jackson; -import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import lombok.*; @@ -105,14 +104,12 @@ void shouldDeserializeFooAsFooFromJson() throws JsonProcessingException { interface FooBar extends SimpleJsonHierarchy { } - @AllArgsConstructor(onConstructor_ = @JsonCreator) @Value static class Foo implements FooBar { private final String foo; private final FooBarType type = FooBarType.FOO; } - @AllArgsConstructor(onConstructor_ = @JsonCreator) @Value static class Bar implements FooBar { private final String bar; @@ -127,4 +124,4 @@ enum FooBarType implements TypeProvider { private final Class type; } -} \ No newline at end of file +} diff --git a/infobip-jackson-extension-module/src/test/java/com/infobip/jackson/SingleArgumentPropertiesCreatorModeAnnotationIntrospectorTest.java b/infobip-jackson-extension-module/src/test/java/com/infobip/jackson/SingleArgumentPropertiesCreatorModeAnnotationIntrospectorTest.java new file mode 100644 index 0000000..29884f3 --- /dev/null +++ b/infobip-jackson-extension-module/src/test/java/com/infobip/jackson/SingleArgumentPropertiesCreatorModeAnnotationIntrospectorTest.java @@ -0,0 +1,179 @@ +package com.infobip.jackson; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.exc.MismatchedInputException; +import lombok.*; +import org.assertj.core.api.BDDAssertions; +import org.junit.jupiter.api.Test; + +import java.util.Map; + +import static org.assertj.core.api.BDDAssertions.then; + +class SingleArgumentPropertiesCreatorModeAnnotationIntrospectorTest extends TestBase { + + @Test + void shouldDeserializeClassWithSingleFieldAndOnlySingleParameterConstructor() throws JsonProcessingException { + // given + String givenJson = "{'foo':'givenFoo'}"; + + // when + ClassWithSingleFieldAndOnlySingleParameterConstructor actual = objectMapper.readValue(givenJson, + ClassWithSingleFieldAndOnlySingleParameterConstructor.class); + + // then + then(actual).isEqualTo(new ClassWithSingleFieldAndOnlySingleParameterConstructor("givenFoo")); + } + + @Test + void shouldDeserializeClassWithSingleFieldAnnotatedWithJsonPropertyAndOnlySingleParameterConstructor() throws JsonProcessingException { + // given + String givenJson = "{'bar':'givenBar'}"; + + // when + ClassWithSingleFieldAnnotatedWithJsonPropertyAndOnlySingleParameterConstructor actual = objectMapper.readValue(givenJson, + ClassWithSingleFieldAnnotatedWithJsonPropertyAndOnlySingleParameterConstructor.class); + + // then + then(actual).isEqualTo(new ClassWithSingleFieldAnnotatedWithJsonPropertyAndOnlySingleParameterConstructor("givenBar")); + } + + @Test + void shouldDeserializeClassWithMultipleProperties() throws JsonProcessingException { + // given + String givenJson = "{'foo':'givenFoo', 'bar':'givenBar'}"; + + // when + ClassWithMultipleProperties actual = objectMapper.readValue(givenJson, ClassWithMultipleProperties.class); + + // then + then(actual).isEqualTo(new ClassWithMultipleProperties("givenFoo", "givenBar")); + } + + @Test + void shouldDeserializeClassWithMultipleFieldsAndOnlySingleParameterConstructor() throws + JsonProcessingException { + // given + String givenJson = "{'foo':'givenFoo'}"; + + // when + ClassWithMultipleFieldsAndOnlySingleParameterConstructor actual = objectMapper.readValue(givenJson, + ClassWithMultipleFieldsAndOnlySingleParameterConstructor.class); + + // then + then(actual).isEqualTo(new ClassWithMultipleFieldsAndOnlySingleParameterConstructor("givenFoo")); + } + + @Test + void shouldDeserializeClassWithDelegatingCreatorConstructor() throws + JsonProcessingException { + // given + String givenJson = "{'foo':'givenFoo'}"; + + // when + ClassWithMultipleFieldsAndOnlySingleParameterConstructor actual = objectMapper.readValue(givenJson, + ClassWithMultipleFieldsAndOnlySingleParameterConstructor.class); + + // then + then(actual).isEqualTo(new ClassWithMultipleFieldsAndOnlySingleParameterConstructor("givenFoo")); + } + + @Test + void shouldFailToDeserializeClassWithMultipleConstructors() { + // given + String givenJson = "{'foo':'givenFoo'}"; + + // when + Throwable actual = BDDAssertions.catchThrowable( + () -> objectMapper.readValue(givenJson, ClassWithMultipleConstructors.class)); + + // then + then(actual).isInstanceOf(MismatchedInputException.class) + .hasMessage("Cannot construct instance of `com.infobip.jackson.SingleArgumentPropertiesCreatorModeAnnotationIntrospectorTest$ClassWithMultipleConstructors` (although at least one Creator exists): cannot deserialize from Object value (no delegate- or property-based Creator)\n" + + " at [Source: (String)\"{'foo':'givenFoo'}\"; line: 1, column: 2]"); + } + + @Test + void shouldFailToDeserializeClassWithMismatchingParameterName() { + // given + String givenJson = "{'foo':'givenFooBar'}"; + + // when + Throwable actual = BDDAssertions.catchThrowable(() -> objectMapper.readValue(givenJson, + ClassWithMismatchingParameterName.class)); + + // then + then(actual).isInstanceOf(MismatchedInputException.class) + .hasMessage( + "Cannot construct instance of `com.infobip.jackson.SingleArgumentPropertiesCreatorModeAnnotationIntrospectorTest$ClassWithMismatchingParameterName` (although at least one Creator exists): cannot deserialize from Object value (no delegate- or property-based Creator)\n" + + " at [Source: (String)\"{'foo':'givenFooBar'}\"; line: 1, column: 2]"); + } + + @Value + static class ClassWithSingleFieldAndOnlySingleParameterConstructor { + + private final String foo; + } + + @Value + static class ClassWithSingleFieldAnnotatedWithJsonPropertyAndOnlySingleParameterConstructor { + + @JsonProperty("bar") + private final String foo; + + public ClassWithSingleFieldAnnotatedWithJsonPropertyAndOnlySingleParameterConstructor(String bar) { + this.foo = bar; + } + } + + @Value + static class ClassWithMultipleProperties { + + private final String foo; + private final String bar; + } + + @Value + static class ClassWithMultipleFieldsAndOnlySingleParameterConstructor { + + private final String foo; + private final String bar = "bar"; + } + + @Value + static class ClassWithDelegatingCreatorConstructor { + + private final String foo; + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public ClassWithDelegatingCreatorConstructor(Map foo) { + this.foo = foo.get("foo"); + } + } + + @ToString + @EqualsAndHashCode + static class ClassWithMultipleConstructors { + + private final String foo; + + public ClassWithMultipleConstructors(String foo) { + this.foo = foo; + } + + public ClassWithMultipleConstructors(Integer bar) { + this.foo = null; + } + } + + static class ClassWithMismatchingParameterName { + + private final String foo; + + public ClassWithMismatchingParameterName(String bar) { + this.foo = bar; + } + } +} diff --git a/pom.xml b/pom.xml index f66039e..2a6a1b9 100644 --- a/pom.xml +++ b/pom.xml @@ -52,7 +52,7 @@ - 2.3.2.RELEASE + 2.4.4 3.7.0 @@ -177,7 +177,7 @@ - ossrh-releases + ossrh SonatypeReleases https://oss.sonatype.org/service/local/staging/deploy/maven2