diff --git a/jsonschema-module-jackson/pom.xml b/jsonschema-module-jackson/pom.xml index 59216688..63539cf9 100644 --- a/jsonschema-module-jackson/pom.xml +++ b/jsonschema-module-jackson/pom.xml @@ -16,6 +16,7 @@ com.github.victools.jsonschema.module.jackson + 1.9.0-Beta @@ -34,13 +35,21 @@ jackson-annotations provided + + org.jetbrains.kotlin + kotlin-stdlib + ${kotlin.version} + + + org.jetbrains.kotlin + kotlin-test + ${kotlin.version} + test + - - maven-compiler-plugin - maven-checkstyle-plugin @@ -54,7 +63,20 @@ org.moditect moditect-maven-plugin + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + true + + 1.8 + + + + org.apache.maven.plugins + maven-compiler-plugin + - \ No newline at end of file + diff --git a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JacksonModule.java b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JacksonModule.java index a698d6dc..c518db83 100644 --- a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JacksonModule.java +++ b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JacksonModule.java @@ -196,13 +196,24 @@ protected String getPropertyNameOverrideBasedOnJsonPropertyAnnotation(MemberScop if (annotation != null) { String nameOverride = annotation.value(); // check for invalid overrides - if (nameOverride != null && !nameOverride.isEmpty() && !nameOverride.equals(member.getDeclaredName())) { + if (isValidNameOverride(member, nameOverride)) { return nameOverride; } } return null; } + /** + * Checks whether the potential name override is valid for the specified member. + * + * @param member field/method to override + * @param nameOverride the name that will override the original name of the member + * @return true if the specified override is valid for the member, false otherwise + */ + protected static boolean isValidNameOverride(MemberScope member, String nameOverride) { + return nameOverride != null && !nameOverride.isEmpty() && !nameOverride.equals(member.getDeclaredName()); + } + /** * Alter the declaring name of the given field as per the declaring type's {@link JsonNaming} annotation. * diff --git a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/KotlinJacksonModule.java b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/KotlinJacksonModule.java new file mode 100644 index 00000000..f16bf1bd --- /dev/null +++ b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/KotlinJacksonModule.java @@ -0,0 +1,62 @@ +package com.github.victools.jsonschema.module.jackson; + +import com.fasterxml.classmate.members.RawField; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.victools.jsonschema.generator.MemberScope; +import java.lang.reflect.Parameter; +import java.util.List; +import java.util.OptionalInt; +import kotlin.Metadata; + +public class KotlinJacksonModule extends JacksonModule { + /** + * Look up an alternative name for a member in the constructor parameter list. + * When the Kotlin compiler compiles a Kotlin data class, it creates a constructor with a parameter + * for each field in the data class. The parameters have generic name such as "arg0", "arg1", etc. + * In order to find the appropriate constructor parameter, the method first determines the index of + * specified member within its type member list and uses the parameter with the same index. + * In order to avoid erroneous overrides, this method verifies that the specified member is indeed of a + * Kotlin class. + * + * @param member field/method to look-up alternative property name for + * @return alternative property name or the base class implementation return value + */ + @Override + protected String getPropertyNameOverrideBasedOnJsonPropertyAnnotation(MemberScope member) { + if (isKotlinType(member)) { + OptionalInt memberIndex = getMemberIndex(member); + if (memberIndex.isPresent()) { + Parameter[] parameters = getConstructorParameters(member); + Parameter parameter = parameters[memberIndex.getAsInt()]; + JsonProperty jsonPropertyAnnotation = parameter.getAnnotation(JsonProperty.class); + if (jsonPropertyAnnotation != null) { + String nameOverride = jsonPropertyAnnotation.value(); + if (isValidNameOverride(member, nameOverride)) { + return nameOverride; + } + } + } + } + return super.getPropertyNameOverrideBasedOnJsonPropertyAnnotation(member); + + } + + private OptionalInt getMemberIndex(MemberScope member) { + List memberFields = member.getDeclaringType().getMemberFields(); + for (int i = 0; i < memberFields.size(); i++) { + if (memberFields.get(i).getName().equals(member.getName())) { + return OptionalInt.of(i); + } + } + return OptionalInt.empty(); + } + + private static Parameter[] getConstructorParameters(MemberScope member) { + return member.getDeclaringType().getConstructors().get(0).getRawMember().getParameters(); + } + + private static boolean isKotlinType(MemberScope member) { + return member.getDeclaringType().getErasedType().isAnnotationPresent(Metadata.class); + } + +} diff --git a/jsonschema-module-jackson/src/test/java/com/github/victools/jsonschema/module/jackson/KotlinJacksonModuleTest.kt b/jsonschema-module-jackson/src/test/java/com/github/victools/jsonschema/module/jackson/KotlinJacksonModuleTest.kt new file mode 100644 index 00000000..db412eaf --- /dev/null +++ b/jsonschema-module-jackson/src/test/java/com/github/victools/jsonschema/module/jackson/KotlinJacksonModuleTest.kt @@ -0,0 +1,29 @@ +package com.github.victools.jsonschema.module.jackson + +import com.fasterxml.jackson.annotation.JsonProperty +import com.github.victools.jsonschema.generator.* +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test +import java.util.* + +class KotlinJacksonModuleTest { + data class TestJsonProperty( + @JsonProperty("my_text") val text: String, + @JsonProperty("my_number") val number: Int) + + @Test + fun `naming override in kotlin data class with JsonProperty`() { + val config = SchemaGeneratorConfigBuilder(SchemaVersion.DRAFT_2020_12, OptionPreset.PLAIN_JSON) + .with(KotlinJacksonModule()) + .build() + + val generator = SchemaGenerator(config) + val result = generator.generateSchema(TestJsonProperty::class.java) + val propertiesNode = result[config.getKeyword(SchemaKeyword.TAG_PROPERTIES)] + val propertyNames: MutableSet = TreeSet() + propertiesNode.fieldNames().forEachRemaining { e: String -> propertyNames.add(e) } + Assertions.assertTrue(propertyNames.contains("my_text")) + Assertions.assertTrue(propertyNames.contains("my_number")) + } + +}