From 8ea673b6d17d691fb9af00b4c5d0a70bfdca1663 Mon Sep 17 00:00:00 2001 From: Benjamin Marwell Date: Sat, 10 Dec 2022 14:24:48 +0100 Subject: [PATCH] [#583] @JsonbCreator uses PropertyNamingStrategy. Signed-off-by: Benjamin Marwell --- .../internal/AnnotationIntrospector.java | 27 +++++---- .../internal/ClassMultiReleaseExtension.java | 4 +- .../yasson/internal/MappingContext.java | 3 +- .../internal/ClassMultiReleaseExtension.java | 6 +- .../internal/AnnotationIntrospectorTest.java | 18 +++--- ...ntrospectorWithoutOptionalModulesTest.java | 21 ++++--- .../yasson/internal/ClassParserTest.java | 4 +- .../CarWithCreateNamingStrategyTest.java | 57 +++++++++++++++++++ 8 files changed, 107 insertions(+), 33 deletions(-) create mode 100644 src/test/java16/org/eclipse/yasson/records/CarWithCreateNamingStrategyTest.java diff --git a/src/main/java/org/eclipse/yasson/internal/AnnotationIntrospector.java b/src/main/java/org/eclipse/yasson/internal/AnnotationIntrospector.java index ba085194f..a71c1f24d 100644 --- a/src/main/java/org/eclipse/yasson/internal/AnnotationIntrospector.java +++ b/src/main/java/org/eclipse/yasson/internal/AnnotationIntrospector.java @@ -59,6 +59,7 @@ import jakarta.json.bind.annotation.JsonbTypeInfo; import jakarta.json.bind.annotation.JsonbTypeSerializer; import jakarta.json.bind.annotation.JsonbVisibility; +import jakarta.json.bind.config.PropertyNamingStrategy; import jakarta.json.bind.config.PropertyVisibilityStrategy; import jakarta.json.bind.serializer.JsonbDeserializer; import jakarta.json.bind.serializer.JsonbSerializer; @@ -152,10 +153,12 @@ private String getJsonbPropertyCustomizedName(Property property, JsonbAnnotatedE /** * Searches for JsonbCreator annotation on constructors and static methods. * - * @param clazz class to search + * @param clazz class to search + * @param propertyNamingStrategy The naming strategy to use for the ${@code JsonbConstructor} annotation, + * if set and no {@code JsonbProperty} annotations are present. * @return JsonbCreator metadata object */ - public JsonbCreator getCreator(Class clazz) { + public JsonbCreator getCreator(Class clazz, PropertyNamingStrategy propertyNamingStrategy) { JsonbCreator jsonbCreator = null; Constructor[] declaredConstructors = AccessController.doPrivileged((PrivilegedAction[]>) clazz::getDeclaredConstructors); @@ -164,7 +167,7 @@ public JsonbCreator getCreator(Class clazz) { final jakarta.json.bind.annotation.JsonbCreator annot = findAnnotation(constructor.getDeclaredAnnotations(), jakarta.json.bind.annotation.JsonbCreator.class); if (annot != null) { - jsonbCreator = createJsonbCreator(constructor, jsonbCreator, clazz); + jsonbCreator = createJsonbCreator(constructor, jsonbCreator, clazz, propertyNamingStrategy); } } @@ -179,11 +182,11 @@ public JsonbCreator getCreator(Class clazz) { method, clazz)); } - jsonbCreator = createJsonbCreator(method, jsonbCreator, clazz); + jsonbCreator = createJsonbCreator(method, jsonbCreator, clazz, propertyNamingStrategy); } } if (jsonbCreator == null) { - jsonbCreator = ClassMultiReleaseExtension.findCreator(clazz, declaredConstructors, this); + jsonbCreator = ClassMultiReleaseExtension.findCreator(clazz, declaredConstructors, this, propertyNamingStrategy); if (jsonbCreator == null) { jsonbCreator = constructorPropertiesIntrospector.getCreator(declaredConstructors); } @@ -191,7 +194,7 @@ public JsonbCreator getCreator(Class clazz) { return jsonbCreator; } - JsonbCreator createJsonbCreator(Executable executable, JsonbCreator existing, Class clazz) { + JsonbCreator createJsonbCreator(Executable executable, JsonbCreator existing, Class clazz, PropertyNamingStrategy propertyNamingStrategy) { if (existing != null) { throw new JsonbException(Messages.getMessage(MessageKeys.MULTIPLE_JSONB_CREATORS, clazz)); } @@ -205,7 +208,8 @@ JsonbCreator createJsonbCreator(Executable executable, JsonbCreator existing, Cl if (jsonbPropertyAnnotation != null && !jsonbPropertyAnnotation.value().isEmpty()) { creatorModels[i] = new CreatorModel(jsonbPropertyAnnotation.value(), parameter, executable, jsonbContext); } else { - creatorModels[i] = new CreatorModel(parameter.getName(), parameter, executable, jsonbContext); + final String translatedParameterName = propertyNamingStrategy.translateName(parameter.getName()); + creatorModels[i] = new CreatorModel(translatedParameterName, parameter, executable, jsonbContext); } } @@ -779,16 +783,19 @@ public Set> collectInterfaces(Class cls) { /** * Processes customizations. * - * @param clsElement Element to process. + * @param clsElement Element to process. + * @param propertyNamingStrategy The naming strategy to use for the ${@code JsonbConstructor} annotation, + * if set and no {@code JsonbProperty} annotations are present. * @return Populated {@link ClassCustomization} instance. */ public ClassCustomization introspectCustomization(JsonbAnnotatedElement> clsElement, - ClassCustomization parentCustomization) { + ClassCustomization parentCustomization, + PropertyNamingStrategy propertyNamingStrategy) { return ClassCustomization.builder() .nillable(isClassNillable(clsElement)) .dateTimeFormatter(getJsonbDateFormat(clsElement)) .numberFormatter(getJsonbNumberFormat(clsElement)) - .creator(getCreator(clsElement.getElement())) + .creator(getCreator(clsElement.getElement(), propertyNamingStrategy)) .propertyOrder(getPropertyOrder(clsElement)) .adapterBinding(getAdapterBinding(clsElement)) .serializerBinding(getSerializerBinding(clsElement)) diff --git a/src/main/java/org/eclipse/yasson/internal/ClassMultiReleaseExtension.java b/src/main/java/org/eclipse/yasson/internal/ClassMultiReleaseExtension.java index ee719e91b..d5df2f870 100644 --- a/src/main/java/org/eclipse/yasson/internal/ClassMultiReleaseExtension.java +++ b/src/main/java/org/eclipse/yasson/internal/ClassMultiReleaseExtension.java @@ -18,6 +18,7 @@ import java.util.Optional; import jakarta.json.bind.JsonbException; +import jakarta.json.bind.config.PropertyNamingStrategy; import org.eclipse.yasson.internal.model.JsonbCreator; import org.eclipse.yasson.internal.model.Property; @@ -42,7 +43,8 @@ static boolean isSpecialAccessorMethod(Method method, Map clas static JsonbCreator findCreator(Class clazz, Constructor[] declaredConstructors, - AnnotationIntrospector introspector) { + AnnotationIntrospector introspector, + PropertyNamingStrategy propertyNamingStrategy) { return null; } diff --git a/src/main/java/org/eclipse/yasson/internal/MappingContext.java b/src/main/java/org/eclipse/yasson/internal/MappingContext.java index a480aca77..1a7b7233b 100644 --- a/src/main/java/org/eclipse/yasson/internal/MappingContext.java +++ b/src/main/java/org/eclipse/yasson/internal/MappingContext.java @@ -88,7 +88,8 @@ private static Function, ClassModel> createParseClassModelFunction(Clas .introspectCustomization(clsElement, parentClassModel == null ? ClassCustomization.empty() - : parentClassModel.getClassCustomization()); + : parentClassModel.getClassCustomization(), + jsonbContext.getConfigProperties().getPropertyNamingStrategy()); // PolymorphismSupport configPolymorphism = jsonbContext.getConfigProperties().getPolymorphismSupport(); // if (configPolymorphism != null) { // customization = mergeConfigAndAnnotationPolymorphism(configPolymorphism, diff --git a/src/main/java16/org/eclipse/yasson/internal/ClassMultiReleaseExtension.java b/src/main/java16/org/eclipse/yasson/internal/ClassMultiReleaseExtension.java index 35c7a89b0..d876a1e71 100644 --- a/src/main/java16/org/eclipse/yasson/internal/ClassMultiReleaseExtension.java +++ b/src/main/java16/org/eclipse/yasson/internal/ClassMultiReleaseExtension.java @@ -18,6 +18,7 @@ import java.util.Optional; import jakarta.json.bind.JsonbException; +import jakarta.json.bind.config.PropertyNamingStrategy; import org.eclipse.yasson.internal.model.JsonbCreator; import org.eclipse.yasson.internal.model.Property; @@ -47,10 +48,11 @@ static boolean isSpecialAccessorMethod(Method method, Map clas static JsonbCreator findCreator(Class clazz, Constructor[] declaredConstructors, - AnnotationIntrospector introspector) { + AnnotationIntrospector introspector, + PropertyNamingStrategy propertyNamingStrategy) { if (clazz.isRecord()) { if (declaredConstructors.length == 1) { - return introspector.createJsonbCreator(declaredConstructors[0], null, clazz); + return introspector.createJsonbCreator(declaredConstructors[0], null, clazz, propertyNamingStrategy); } } return null; diff --git a/src/test/java/org/eclipse/yasson/internal/AnnotationIntrospectorTest.java b/src/test/java/org/eclipse/yasson/internal/AnnotationIntrospectorTest.java index 14201cf60..dd0cf0656 100644 --- a/src/test/java/org/eclipse/yasson/internal/AnnotationIntrospectorTest.java +++ b/src/test/java/org/eclipse/yasson/internal/AnnotationIntrospectorTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -12,6 +12,7 @@ package org.eclipse.yasson.internal; +import jakarta.json.bind.config.PropertyNamingStrategy; import org.junit.jupiter.api.*; import static org.junit.jupiter.api.Assertions.*; @@ -40,6 +41,7 @@ */ public class AnnotationIntrospectorTest { private final JsonbContext jsonbContext = new JsonbContext(new JsonbConfig(), JsonProvider.provider()); + private final PropertyNamingStrategy propertyNamingStrategy = jsonbContext.getConfigProperties().getPropertyNamingStrategy(); /** * class under test. @@ -48,21 +50,21 @@ public class AnnotationIntrospectorTest { @Test public void testObjectShouldBeCreateableFromJsonbAnnotatedConstructor() { - JsonbCreator creator = instrospector.getCreator(ObjectWithJsonbCreatorAnnotatedConstructor.class); + JsonbCreator creator = instrospector.getCreator(ObjectWithJsonbCreatorAnnotatedConstructor.class, propertyNamingStrategy); assertParameters(ObjectWithJsonbCreatorAnnotatedConstructor.parameters(), creator); assertCreatedInstanceContainsAllParameters(ObjectWithJsonbCreatorAnnotatedConstructor.example(), creator); } @Test public void testObjectShouldBeCreateableFromJsonbAnnotatedStaticFactoryMethod() { - JsonbCreator creator = instrospector.getCreator(ObjectWithJsonbCreatorAnnotatedFactoryMethod.class); + JsonbCreator creator = instrospector.getCreator(ObjectWithJsonbCreatorAnnotatedFactoryMethod.class, propertyNamingStrategy); assertParameters(ObjectWithJsonbCreatorAnnotatedFactoryMethod.parameters(), creator); assertCreatedInstanceContainsAllParameters(ObjectWithJsonbCreatorAnnotatedFactoryMethod.example(), creator); } @Test public void testObjectShouldBeCreateableFromJsonbAnnotatedStaticFactoryMethodIgnoringConstructorPorperties() { - JsonbCreator creator = instrospector.getCreator(ObjectWithJsonbCreatorAndConstructorPropertiesAnnotation.class); + JsonbCreator creator = instrospector.getCreator(ObjectWithJsonbCreatorAndConstructorPropertiesAnnotation.class, propertyNamingStrategy); assertParameters(ObjectWithJsonbCreatorAndConstructorPropertiesAnnotation.parameters(), creator); assertCreatedInstanceContainsAllParameters(ObjectWithJsonbCreatorAndConstructorPropertiesAnnotation.example(), creator); } @@ -70,7 +72,7 @@ public void testObjectShouldBeCreateableFromJsonbAnnotatedStaticFactoryMethodIgn @Test public void testJsonbAnnotatedProtectedConstructorLeadsToAnException() { assertThrows(JsonbException.class, () -> { - JsonbCreator creator = instrospector.getCreator(ObjectWithJsonbCreatorAnnotatedProtectedConstructor.class); + JsonbCreator creator = instrospector.getCreator(ObjectWithJsonbCreatorAnnotatedProtectedConstructor.class, propertyNamingStrategy); assertCreatedInstanceContainsAllParameters(ObjectWithJsonbCreatorAnnotatedProtectedConstructor.example(), creator); }); } @@ -79,7 +81,7 @@ public void testJsonbAnnotatedProtectedConstructorLeadsToAnException() { @Disabled @Test public void testNoArgConstructorShouldBePreferredOverUnusableJsonbAnnotatedProtectedConstructor() { - JsonbCreator creator = instrospector.getCreator(ObjectWithNoArgAndJsonbCreatorAnnotatedProtectedConstructor.class); + JsonbCreator creator = instrospector.getCreator(ObjectWithNoArgAndJsonbCreatorAnnotatedProtectedConstructor.class, propertyNamingStrategy); assertParameters(ObjectWithNoArgAndJsonbCreatorAnnotatedProtectedConstructor.parameters(), creator); assertCreatedInstanceContainsAllParameters(ObjectWithNoArgAndJsonbCreatorAnnotatedProtectedConstructor.example(), creator); } @@ -87,13 +89,13 @@ public void testNoArgConstructorShouldBePreferredOverUnusableJsonbAnnotatedProte @Test public void testMoreThanOneAnnotatedCreatorMethodShouldLeadToAnException() { assertThrows(JsonbException.class, - () -> instrospector.getCreator(ObjectWithTwoJsonbCreatorAnnotatedSpots.class), + () -> instrospector.getCreator(ObjectWithTwoJsonbCreatorAnnotatedSpots.class, propertyNamingStrategy), () -> "More than one @" + JsonbCreator.class.getSimpleName()); } @Test public void testCreatorShouldBeNullOnMissingConstructorAnnotation() { - assertNull(instrospector.getCreator(ObjectWithoutAnnotatedConstructor.class)); + assertNull(instrospector.getCreator(ObjectWithoutAnnotatedConstructor.class, propertyNamingStrategy)); } } diff --git a/src/test/java/org/eclipse/yasson/internal/AnnotationIntrospectorWithoutOptionalModulesTest.java b/src/test/java/org/eclipse/yasson/internal/AnnotationIntrospectorWithoutOptionalModulesTest.java index 8ec8fb335..95568109d 100644 --- a/src/test/java/org/eclipse/yasson/internal/AnnotationIntrospectorWithoutOptionalModulesTest.java +++ b/src/test/java/org/eclipse/yasson/internal/AnnotationIntrospectorWithoutOptionalModulesTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -12,19 +12,21 @@ package org.eclipse.yasson.internal; -import org.junit.jupiter.api.*; -import static org.junit.jupiter.api.Assertions.*; - import static org.eclipse.yasson.internal.AnnotationIntrospectorTestAsserts.assertCreatedInstanceContainsAllParameters; import static org.eclipse.yasson.internal.AnnotationIntrospectorTestAsserts.assertParameters; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.fail; + +import jakarta.json.bind.JsonbConfig; +import jakarta.json.bind.config.PropertyNamingStrategy; +import jakarta.json.spi.JsonProvider; import org.eclipse.yasson.internal.AnnotationIntrospectorTestFixtures.ObjectWithJsonbCreatorAnnotatedConstructor; import org.eclipse.yasson.internal.AnnotationIntrospectorTestFixtures.ObjectWithJsonbCreatorAnnotatedFactoryMethod; import org.eclipse.yasson.internal.AnnotationIntrospectorTestFixtures.ObjectWithoutAnnotatedConstructor; import org.eclipse.yasson.internal.model.JsonbCreator; -import jakarta.json.bind.JsonbConfig; -import jakarta.json.spi.JsonProvider; +import org.junit.jupiter.api.Test; /** * Tests the {@link AnnotationIntrospector} with missing optional module "java.deskop",
@@ -41,6 +43,7 @@ public class AnnotationIntrospectorWithoutOptionalModulesTest { * class under test. */ private static final AnnotationIntrospector instrospector = new AnnotationIntrospector(new JsonbContext(new JsonbConfig(), JsonProvider.provider())); + private final PropertyNamingStrategy propertyNamingStrategy = propertyName -> propertyName; @Test public void testNoConstructorPropertiesAnnotationWithoutOptionalModules() { @@ -55,19 +58,19 @@ public void testNoConstructorPropertiesAnnotationWithoutOptionalModules() { @Test public void testCreatorShouldBeNullOnMissingConstructorAnnotation() { - assertNull(instrospector.getCreator(ObjectWithoutAnnotatedConstructor.class)); + assertNull(instrospector.getCreator(ObjectWithoutAnnotatedConstructor.class, propertyNamingStrategy)); } @Test public void testObjectShouldBeCreateableFromJsonbAnnotatedConstructorWithoutOptionalModules() { - JsonbCreator creator = instrospector.getCreator(ObjectWithJsonbCreatorAnnotatedConstructor.class); + JsonbCreator creator = instrospector.getCreator(ObjectWithJsonbCreatorAnnotatedConstructor.class, propertyNamingStrategy); assertParameters(ObjectWithJsonbCreatorAnnotatedConstructor.parameters(), creator); assertCreatedInstanceContainsAllParameters(ObjectWithJsonbCreatorAnnotatedConstructor.example(), creator); } @Test public void testObjectShouldBeCreateableFromJsonbAnnotatedStaticFactoryMethodWithoutOptionalModules() { - JsonbCreator creator = instrospector.getCreator(ObjectWithJsonbCreatorAnnotatedFactoryMethod.class); + JsonbCreator creator = instrospector.getCreator(ObjectWithJsonbCreatorAnnotatedFactoryMethod.class, propertyNamingStrategy); assertParameters(ObjectWithJsonbCreatorAnnotatedFactoryMethod.parameters(), creator); assertCreatedInstanceContainsAllParameters(ObjectWithJsonbCreatorAnnotatedFactoryMethod.example(), creator); } diff --git a/src/test/java/org/eclipse/yasson/internal/ClassParserTest.java b/src/test/java/org/eclipse/yasson/internal/ClassParserTest.java index 5dd8f42e0..0e866252d 100644 --- a/src/test/java/org/eclipse/yasson/internal/ClassParserTest.java +++ b/src/test/java/org/eclipse/yasson/internal/ClassParserTest.java @@ -40,7 +40,7 @@ public class ClassParserTest { public void testDefaultMappingFieldModifiers() { final JsonbAnnotatedElement> clsElement = introspector.collectAnnotations(FieldModifiersClass.class); ClassModel model = new ClassModel(FieldModifiersClass.class, introspector.introspectCustomization(clsElement, - ClassCustomization.empty()), null, null); + ClassCustomization.empty(), jsonbContext.getConfigProperties().getPropertyNamingStrategy()), null, null); classParser.parseProperties(model, clsElement); assertTrue(model.getPropertyModel("finalString").isReadable()); assertFalse(model.getPropertyModel("finalString").isWritable()); @@ -54,7 +54,7 @@ public void testDefaultMappingFieldModifiers() { public void testDefaultMappingMethodModifiers() { final JsonbAnnotatedElement> clsElement = introspector.collectAnnotations(MethodModifiersClass.class); ClassModel model = new ClassModel(FieldModifiersClass.class, introspector.introspectCustomization(clsElement, - ClassCustomization.empty()), null, null); + ClassCustomization.empty(), jsonbContext.getConfigProperties().getPropertyNamingStrategy()), null, null); classParser.parseProperties(model, clsElement); assertFalse(model.getPropertyModel("publicFieldWithPrivateMethods").isReadable()); assertFalse(model.getPropertyModel("publicFieldWithPrivateMethods").isWritable()); diff --git a/src/test/java16/org/eclipse/yasson/records/CarWithCreateNamingStrategyTest.java b/src/test/java16/org/eclipse/yasson/records/CarWithCreateNamingStrategyTest.java new file mode 100644 index 000000000..1fbd17dd1 --- /dev/null +++ b/src/test/java16/org/eclipse/yasson/records/CarWithCreateNamingStrategyTest.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, + * or the Eclipse Distribution License v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +package org.eclipse.yasson.records; + +import jakarta.json.bind.Jsonb; +import jakarta.json.bind.JsonbBuilder; +import jakarta.json.bind.JsonbConfig; +import jakarta.json.bind.annotation.JsonbCreator; +import jakarta.json.bind.config.PropertyNamingStrategy; +import org.junit.jupiter.api.Test; + +import static java.util.Objects.requireNonNull; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class CarWithCreateNamingStrategyTest { + + // camel case is intentional for this test case + public record Car(String brandName, String colorName) { + + @JsonbCreator + public Car { + requireNonNull(brandName, "brandName"); + requireNonNull(colorName, "colorName"); + } + } + + @Test + public void testRecordJsonbCreatorWithNamingStrategy() { + // given + final JsonbConfig config = new JsonbConfig() + .withPropertyNamingStrategy(PropertyNamingStrategy.LOWER_CASE_WITH_UNDERSCORES); + final Jsonb jsonb = JsonbBuilder.create(config); + + var json = """ + { + "brand_name": "Volkswagen", + "color_name": "Piano black" + } + """; + + // when + final Car car = jsonb.fromJson(json, Car.class); + + // then + assertEquals("Volkswagen", car.brandName()); + assertEquals("Piano black", car.colorName()); + } +}