Skip to content

Commit

Permalink
@JsonbCreator uses PropertyNamingStrategy. (#584)
Browse files Browse the repository at this point in the history
Signed-off-by: Benjamin Marwell <bmarwell@apache.org>
  • Loading branch information
bmarwell committed Feb 15, 2023
1 parent 3a5b85a commit 66bef09
Show file tree
Hide file tree
Showing 8 changed files with 107 additions and 33 deletions.
Expand Up @@ -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;
Expand Down Expand Up @@ -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<Constructor<?>[]>) clazz::getDeclaredConstructors);
Expand All @@ -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);
}
}

Expand All @@ -179,19 +182,19 @@ 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);
}
}
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));
}
Expand All @@ -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);
}
}

Expand Down Expand Up @@ -779,16 +783,19 @@ public Set<Class<?>> 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<Class<?>> 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))
Expand Down
Expand Up @@ -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;
Expand All @@ -42,7 +43,8 @@ static boolean isSpecialAccessorMethod(Method method, Map<String, Property> clas

static JsonbCreator findCreator(Class<?> clazz,
Constructor<?>[] declaredConstructors,
AnnotationIntrospector introspector) {
AnnotationIntrospector introspector,
PropertyNamingStrategy propertyNamingStrategy) {
return null;
}

Expand Down
Expand Up @@ -88,7 +88,8 @@ private static Function<Class<?>, 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,
Expand Down
Expand Up @@ -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;
Expand Down Expand Up @@ -47,10 +48,11 @@ static boolean isSpecialAccessorMethod(Method method, Map<String, Property> 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;
Expand Down
@@ -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
Expand All @@ -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.*;

Expand Down Expand Up @@ -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.
Expand All @@ -48,29 +50,29 @@ 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);
}

@Test
public void testJsonbAnnotatedProtectedConstructorLeadsToAnException() {
assertThrows(JsonbException.class, () -> {
JsonbCreator creator = instrospector.getCreator(ObjectWithJsonbCreatorAnnotatedProtectedConstructor.class);
JsonbCreator creator = instrospector.getCreator(ObjectWithJsonbCreatorAnnotatedProtectedConstructor.class, propertyNamingStrategy);
assertCreatedInstanceContainsAllParameters(ObjectWithJsonbCreatorAnnotatedProtectedConstructor.example(), creator);
});
}
Expand All @@ -79,21 +81,21 @@ 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);
}

@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));
}

}
@@ -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
Expand All @@ -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", <br>
Expand All @@ -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() {
Expand All @@ -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);
}
Expand Down
Expand Up @@ -40,7 +40,7 @@ public class ClassParserTest {
public void testDefaultMappingFieldModifiers() {
final JsonbAnnotatedElement<Class<?>> 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());
Expand All @@ -54,7 +54,7 @@ public void testDefaultMappingFieldModifiers() {
public void testDefaultMappingMethodModifiers() {
final JsonbAnnotatedElement<Class<?>> 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());
Expand Down
@@ -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());
}
}

0 comments on commit 66bef09

Please sign in to comment.