From ddd7c98f03a9665d4791782fa060cd73a6645566 Mon Sep 17 00:00:00 2001 From: Carsten Wickner <11309681+CarstenWickner@users.noreply.github.com> Date: Mon, 11 Nov 2024 23:35:46 +0100 Subject: [PATCH] feat: Support @JacksonAnnotationsInside on meta/combo annotations (#495) --- CHANGELOG.md | 5 +- jsonschema-generator-parent/pom.xml | 7 + .../generator/AnnotationHelper.java | 125 ++++++++++++++++ .../jsonschema/generator/TypeContext.java | 15 +- .../generator/AnnotationHelperTest.java | 139 ++++++++++++++++++ jsonschema-module-jackson/README.md | 1 + .../jackson/CustomEnumDefinitionProvider.java | 17 ++- .../module/jackson/JacksonModule.java | 41 +++--- ...onIdentityReferenceDefinitionProvider.java | 20 ++- .../module/jackson/JsonPropertySorter.java | 7 +- .../module/jackson/JsonSubTypesResolver.java | 36 +++-- .../JsonUnwrappedDefinitionProvider.java | 34 ++--- ...entityReferenceDefinitionProviderTest.java | 12 +- ...SubTypesResolverCustomDefinitionsTest.java | 10 +- slate-docs/source/includes/_jackson-module.md | 1 + 15 files changed, 388 insertions(+), 82 deletions(-) create mode 100644 jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/AnnotationHelper.java create mode 100644 jsonschema-generator/src/test/java/com/github/victools/jsonschema/generator/AnnotationHelperTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f4bc6f1..9043e4ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,12 +18,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - avoid exception when trying to find type with annotation when given type is `null` ### `jsonschema-module-jackson` +#### Added +- support `@JacksonAnnotationsInside` annotated combo annotations + #### Fixed - avoid exception in subtype resolution, when targeting void method - check for ignored properties excluded fields when a property name override makes it conflict with a non-conventional getter method ### `jsonschema-maven-plugin` -### Added +#### Added - support `` flag to exclude abstract types (not interfaces) - support `` flag to exclude interface types diff --git a/jsonschema-generator-parent/pom.xml b/jsonschema-generator-parent/pom.xml index a7bf92e9..83944f37 100644 --- a/jsonschema-generator-parent/pom.xml +++ b/jsonschema-generator-parent/pom.xml @@ -129,6 +129,13 @@ Provided PR #456 (introducing support for Jakarta @AssertTrue/@AssertFalse) + + Antoine Malliarakis + https://github.com/smaarn + + Provided PR #487 (support @JacksonAnnotationsInside annotations) + + diff --git a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/AnnotationHelper.java b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/AnnotationHelper.java new file mode 100644 index 00000000..b147b30a --- /dev/null +++ b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/AnnotationHelper.java @@ -0,0 +1,125 @@ +/* + * Copyright 2024 VicTools. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.github.victools.jsonschema.generator; + +import com.fasterxml.classmate.members.ResolvedMember; +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +/** + * Helper class providing with standard mechanism to resolve annotations on annotated entities. + * + * @since 4.37.0 + */ +public final class AnnotationHelper { + + private AnnotationHelper() { + super(); + } + + /** + * Resolves the specified annotation on the given resolved member and resolve nested annotations. + * + * @param the generic type of the annotation + * @param member where to look for the specified annotation + * @param annotationClass the class of the annotation to look for + * @param metaAnnotationCheck the predicate indicating nested annotations + * @return an empty entry if not found + */ + public static Optional resolveAnnotation(ResolvedMember member, Class annotationClass, + Predicate metaAnnotationCheck) { + final A annotation = member.getAnnotations().get(annotationClass); + if (annotation == null) { + return AnnotationHelper.resolveNestedAnnotations(StreamSupport.stream(member.getAnnotations().spliterator(), false), + annotationClass, metaAnnotationCheck); + } + return Optional.of(annotation); + } + + /** + * Select the instance of the specified annotation type from the given list. + * + *

Also considering meta annotations (i.e., annotations on annotations) if a meta annotation is + * deemed eligible according to the given {@code Predicate}.

+ * + * @param
the generic type of the annotation + * @param annotationList a list of annotations to look into + * @param annotationClass the class of the annotation to look for + * @param metaAnnotationCheck the predicate indicating nested annotations + * @return an empty entry if not found + */ + public static Optional resolveAnnotation(List annotationList, Class annotationClass, + Predicate metaAnnotationCheck) { + final Optional annotation = annotationList.stream() + .filter(annotationClass::isInstance) + .findFirst(); + if (annotation.isPresent()) { + return annotation.map(annotationClass::cast); + } + return AnnotationHelper.resolveNestedAnnotations(annotationList.stream(), annotationClass, metaAnnotationCheck); + } + + /** + * Select the instance of the specified annotation type from the given annotatedElement's annotations. + * + *

Also considering meta annotations (i.e., annotations on annotations) if a meta annotation is + * deemed eligible according to the given metaAnnotationPredicate.

+ * + * @param
the generic type of the annotation + * @param annotatedElement where to look for the specified annotation + * @param annotationClass the class of the annotation to look for + * @param metaAnnotationCheck the predicate indicating meta annotations + * @return an empty entry if not found + */ + public static Optional resolveAnnotation(AnnotatedElement annotatedElement, Class annotationClass, + Predicate metaAnnotationCheck) { + final A annotation = annotatedElement.getAnnotation(annotationClass); + if (annotation == null) { + return AnnotationHelper.resolveNestedAnnotations(Arrays.stream(annotatedElement.getAnnotations()), + annotationClass, metaAnnotationCheck); + } + return Optional.of(annotation); + } + + private static Optional resolveNestedAnnotations(Stream initialAnnotations, Class annotationClass, + Predicate metaAnnotationCheck) { + List annotations = AnnotationHelper.extractAnnotationsFromMetaAnnotations(initialAnnotations, metaAnnotationCheck); + while (!annotations.isEmpty()) { + final Optional directAnnotation = annotations.stream() + .filter(annotationClass::isInstance) + .findFirst(); + if (directAnnotation.isPresent()) { + return directAnnotation.map(annotationClass::cast); + } + annotations = AnnotationHelper.extractAnnotationsFromMetaAnnotations(annotations.stream(), metaAnnotationCheck); + } + return Optional.empty(); + } + + private static List extractAnnotationsFromMetaAnnotations(Stream annotations, Predicate metaAnnotationCheck) { + return annotations.filter(metaAnnotationCheck) + .flatMap(a -> Arrays.stream(a.annotationType().getAnnotations())) + .collect(Collectors.toList()); + } +} diff --git a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/TypeContext.java b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/TypeContext.java index e0cdc899..e0661432 100644 --- a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/TypeContext.java +++ b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/TypeContext.java @@ -276,20 +276,7 @@ public ResolvedType getContainerItemType(ResolvedType containerType) { */ public A getAnnotationFromList(Class annotationClass, List annotationList, Predicate considerOtherAnnotation) { - List annotations = annotationList; - while (!annotations.isEmpty()) { - Optional nestedAnnotation = annotations.stream() - .filter(annotationClass::isInstance) - .findFirst(); - if (nestedAnnotation.isPresent()) { - return nestedAnnotation.map(annotationClass::cast).get(); - } - annotations = annotations.stream() - .filter(considerOtherAnnotation) - .flatMap(otherAnnotation -> Stream.of(otherAnnotation.annotationType().getAnnotations())) - .collect(Collectors.toList()); - } - return null; + return AnnotationHelper.resolveAnnotation(annotationList, annotationClass, considerOtherAnnotation).orElse(null); } /** diff --git a/jsonschema-generator/src/test/java/com/github/victools/jsonschema/generator/AnnotationHelperTest.java b/jsonschema-generator/src/test/java/com/github/victools/jsonschema/generator/AnnotationHelperTest.java new file mode 100644 index 00000000..d85347a1 --- /dev/null +++ b/jsonschema-generator/src/test/java/com/github/victools/jsonschema/generator/AnnotationHelperTest.java @@ -0,0 +1,139 @@ +/* + * Copyright 2024 VicTools. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.github.victools.jsonschema.generator; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.lang.annotation.Annotation; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.Arrays; +import java.util.Optional; +import java.util.function.Predicate; +import java.util.stream.Stream; + +/** + * Unit test class dedicated to the validation of {@link AnnotationHelper}. + */ +public class AnnotationHelperTest { + + static Stream annotationLookupScenarios() { + return Stream.of( + Arguments.of(NonAnnotatedClass.class, Optional.empty()), + Arguments.of(AnnotatedClassWithUselessAnnotations.class, Optional.empty()), + Arguments.of(DirectlyAnnotatedClass.class, Optional.of("")), + Arguments.of(BothDirectAndIndirectlyAnnotatedClass.class, Optional.of("direct value")), + Arguments.of(IndirectlyAnnotatedClass.class, Optional.of("first combo annotation value")), + Arguments.of(BreadthFirstAnnotatedClass.class, Optional.of("first combo annotation value")) + ); + } + + @ParameterizedTest + @MethodSource("annotationLookupScenarios") + void resolveAnnotation_AnnotatedElement_respects_annotationLookupScenarios(Class annotatedClass, Optional expectedAnnotationValue) { + Optional value = AnnotationHelper.resolveAnnotation(annotatedClass, TargetAnnotation.class, metaAnnotationPredicate()).map(TargetAnnotation::value); + Assertions.assertEquals(expectedAnnotationValue, value); + } + + @ParameterizedTest + @MethodSource("annotationLookupScenarios") + void resolveAnnotation_List_respects_annotationLookupScenarios(Class annotatedClass, Optional expectedAnnotationValue) { + Optional value = AnnotationHelper.resolveAnnotation(Arrays.asList(annotatedClass.getAnnotations()), TargetAnnotation.class, metaAnnotationPredicate()).map(TargetAnnotation::value); + Assertions.assertEquals(expectedAnnotationValue, value); + } + + private static Predicate metaAnnotationPredicate() { + return (annotation) -> annotation.annotationType().isAnnotationPresent(MetaAnnotation.class); + } + + @Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE}) + @Retention(RetentionPolicy.RUNTIME) + public @interface TargetAnnotation { + String value() default ""; + } + + @Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE}) + @Retention(RetentionPolicy.RUNTIME) + public @interface MetaAnnotation {} + + @TargetAnnotation + private static class DirectlyAnnotatedClass { + } + + private static class NonAnnotatedClass { + } + + @UselessFirstComboAnnotation + @UselessSecondComboAnnotation + private static class AnnotatedClassWithUselessAnnotations { + + } + + @Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE}) + @Retention(RetentionPolicy.RUNTIME) + @MetaAnnotation + private @interface UselessFirstComboAnnotation { + } + + @Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE}) + @Retention(RetentionPolicy.RUNTIME) + @MetaAnnotation + private @interface UselessSecondComboAnnotation { + } + + @Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE}) + @Retention(RetentionPolicy.RUNTIME) + @MetaAnnotation + @TargetAnnotation("first combo annotation value") + private @interface FirstComboAnnotation { + } + + @Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE}) + @Retention(RetentionPolicy.RUNTIME) + @MetaAnnotation + @TargetAnnotation("second combo annotation value") + private @interface SecondComboAnnotation { + } + + @Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE}) + @Retention(RetentionPolicy.RUNTIME) + @MetaAnnotation + @SecondComboAnnotation + private @interface ThirdComboAnnotation { + } + + @FirstComboAnnotation + @SecondComboAnnotation + private static class IndirectlyAnnotatedClass { + } + + @TargetAnnotation("direct value") + @FirstComboAnnotation + @SecondComboAnnotation + private static class BothDirectAndIndirectlyAnnotatedClass { + } + + @ThirdComboAnnotation + @FirstComboAnnotation + private static class BreadthFirstAnnotatedClass {} + +} \ No newline at end of file diff --git a/jsonschema-module-jackson/README.md b/jsonschema-module-jackson/README.md index cc11c7ec..09821c3b 100644 --- a/jsonschema-module-jackson/README.md +++ b/jsonschema-module-jackson/README.md @@ -21,6 +21,7 @@ Module for the [jsonschema-generator](../jsonschema-generator) – deriving JSON 13. Optionally: ignore all methods but those with a `@JsonProperty` annotation, if the `JacksonOption.INCLUDE_ONLY_JSONPROPERTY_ANNOTATED_METHODS` was provided (i.e. this is an "opt-in"). 14. Optionally: respect `@JsonIdentityReference(alwaysAsId=true)` annotation if there is a corresponding `@JsonIdentityInfo` annotation on the type and the `JacksonOption.JSONIDENTITY_REFERENCE_ALWAYS_AS_ID` as provided (i.e., this is an "opt-in") 15. Elevate nested properties to the parent type where members are annotated with `@JsonUnwrapped`. +16. Support `@JacksonAnnotationsInside` annotated combo annotations Schema attributes derived from validation annotations on getter methods are also applied to their associated fields. diff --git a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/CustomEnumDefinitionProvider.java b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/CustomEnumDefinitionProvider.java index 2ddbc0f7..02eeece9 100644 --- a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/CustomEnumDefinitionProvider.java +++ b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/CustomEnumDefinitionProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 VicTools. + * Copyright 2020-2024 VicTools. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.github.victools.jsonschema.generator.AnnotationHelper; import com.github.victools.jsonschema.generator.CustomDefinition; import com.github.victools.jsonschema.generator.CustomDefinitionProviderV2; import com.github.victools.jsonschema.generator.SchemaGenerationContext; @@ -121,7 +122,9 @@ protected ResolvedMethod getJsonValueAnnotatedMethod(ResolvedType javaType, Sche ResolvedMethod[] memberMethods = context.getTypeContext().resolveWithMembers(javaType).getMemberMethods(); Set jsonValueAnnotatedMethods = Stream.of(memberMethods) .filter(method -> method.getArgumentCount() == 0) - .filter(method -> Optional.ofNullable(method.getAnnotations().get(JsonValue.class)).map(JsonValue::value).orElse(false)) + .filter(method -> AnnotationHelper.resolveAnnotation(method, JsonValue.class, JacksonModule.NESTED_ANNOTATION_CHECK) + .map(JsonValue::value) + .orElse(false)) .collect(Collectors.toSet()); if (jsonValueAnnotatedMethods.size() == 1) { return jsonValueAnnotatedMethods.iterator().next(); @@ -141,14 +144,14 @@ protected List getSerializedValuesFromJsonProperty(ResolvedType javaType List serializedJsonValues = new ArrayList<>(enumConstants.length); for (Object enumConstant : enumConstants) { String enumValueName = ((Enum) enumConstant).name(); - JsonProperty annotation = javaType.getErasedType() - .getDeclaredField(enumValueName) - .getAnnotation(JsonProperty.class); - if (annotation == null) { + Optional annotation = AnnotationHelper.resolveAnnotation(javaType.getErasedType().getDeclaredField(enumValueName), + JsonProperty.class, JacksonModule.NESTED_ANNOTATION_CHECK); + if (!annotation.isPresent()) { // enum constant without @JsonProperty annotation return null; } - serializedJsonValues.add(JsonProperty.USE_DEFAULT_NAME.equals(annotation.value()) ? enumValueName : annotation.value()); + final String annotationValue = annotation.get().value(); + serializedJsonValues.add(JsonProperty.USE_DEFAULT_NAME.equals(annotationValue) ? enumValueName : annotationValue); } return serializedJsonValues; } catch (NoSuchFieldException | SecurityException ex) { 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 fef0fda2..9c952e63 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 @@ -1,5 +1,5 @@ /* - * Copyright 2019 VicTools. + * Copyright 2019-2024 VicTools. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,8 @@ import com.fasterxml.classmate.ResolvedType; import com.fasterxml.classmate.members.HierarchicType; +import com.fasterxml.classmate.members.ResolvedMember; +import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; import com.fasterxml.jackson.annotation.JsonBackReference; import com.fasterxml.jackson.annotation.JsonClassDescription; import com.fasterxml.jackson.annotation.JsonProperty; @@ -27,6 +29,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.PropertyNamingStrategy; import com.fasterxml.jackson.databind.annotation.JsonNaming; +import com.github.victools.jsonschema.generator.AnnotationHelper; import com.github.victools.jsonschema.generator.FieldScope; import com.github.victools.jsonschema.generator.MemberScope; import com.github.victools.jsonschema.generator.MethodScope; @@ -35,6 +38,8 @@ import com.github.victools.jsonschema.generator.SchemaGeneratorConfigPart; import com.github.victools.jsonschema.generator.SchemaGeneratorGeneralConfigPart; import com.github.victools.jsonschema.generator.TypeScope; +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; @@ -42,6 +47,7 @@ import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.function.Predicate; /** * Module for setting up schema generation aspects based on {@code jackson-annotations}. @@ -54,6 +60,9 @@ */ public class JacksonModule implements Module { + static final Predicate NESTED_ANNOTATION_CHECK = annotation -> + annotation.annotationType().isAnnotationPresent(JacksonAnnotationsInside.class); + private final Set options; private ObjectMapper objectMapper; private final Map, BeanDescription> beanDescriptions = Collections.synchronizedMap(new HashMap<>()); @@ -183,11 +192,9 @@ protected String resolveDescription(MemberScope member) { */ protected String resolveDescriptionForType(TypeScope scope) { Class rawType = scope.getType().getErasedType(); - JsonClassDescription classAnnotation = rawType.getAnnotation(JsonClassDescription.class); - if (classAnnotation != null) { - return classAnnotation.value(); - } - return null; + return AnnotationHelper.resolveAnnotation(rawType, JsonClassDescription.class, NESTED_ANNOTATION_CHECK) + .map(JsonClassDescription::value) + .orElse(null); } /** @@ -201,7 +208,7 @@ protected String resolveDescriptionForType(TypeScope scope) { * @return alternative property name (or {@code null}) */ protected String getPropertyNameOverrideBasedOnJsonPropertyAnnotation(MemberScope member) { - JsonProperty annotation = member.getAnnotationConsideringFieldAndGetter(JsonProperty.class); + JsonProperty annotation = member.getAnnotationConsideringFieldAndGetter(JsonProperty.class, NESTED_ANNOTATION_CHECK); if (annotation != null) { String nameOverride = annotation.value(); // check for invalid overrides @@ -237,7 +244,7 @@ protected String getPropertyNameOverrideBasedOnJsonNamingAnnotation(FieldScope f * @return annotated naming strategy instance (or {@code null}) */ private PropertyNamingStrategy getAnnotatedNamingStrategy(Class declaringType) { - return Optional.ofNullable(declaringType.getAnnotation(JsonNaming.class)) + return AnnotationHelper.resolveAnnotation(declaringType, JsonNaming.class, NESTED_ANNOTATION_CHECK) .map(JsonNaming::value) .map(strategyType -> { try { @@ -274,11 +281,11 @@ protected final BeanDescription getBeanDescriptionForClass(ResolvedType targetTy * @return whether field should be excluded */ protected boolean shouldIgnoreField(FieldScope field) { - if (field.getAnnotationConsideringFieldAndGetterIfSupported(JsonBackReference.class) != null) { + if (field.getAnnotationConsideringFieldAndGetterIfSupported(JsonBackReference.class, NESTED_ANNOTATION_CHECK) != null) { return true; } // @since 4.32.0 - JsonUnwrapped unwrappedAnnotation = field.getAnnotationConsideringFieldAndGetterIfSupported(JsonUnwrapped.class); + JsonUnwrapped unwrappedAnnotation = field.getAnnotationConsideringFieldAndGetterIfSupported(JsonUnwrapped.class, NESTED_ANNOTATION_CHECK); if (unwrappedAnnotation != null && unwrappedAnnotation.enabled()) { // unwrapped properties should be ignored here, as they are included in their unwrapped form return true; @@ -312,12 +319,12 @@ protected boolean shouldIgnoreField(FieldScope field) { protected boolean shouldIgnoreMethod(MethodScope method) { FieldScope getterField = method.findGetterField(); if (getterField == null) { - if (method.getAnnotationConsideringFieldAndGetterIfSupported(JsonBackReference.class) != null) { + if (method.getAnnotationConsideringFieldAndGetterIfSupported(JsonBackReference.class, NESTED_ANNOTATION_CHECK) != null) { return true; } // @since 4.32.0 - JsonUnwrapped unwrappedAnnotation = method.getAnnotationConsideringFieldAndGetterIfSupported(JsonUnwrapped.class); - if (unwrappedAnnotation != null && unwrappedAnnotation.enabled()) { + JsonUnwrapped unwrapped = method.getAnnotationConsideringFieldAndGetterIfSupported(JsonUnwrapped.class, NESTED_ANNOTATION_CHECK); + if (unwrapped != null && unwrapped.enabled()) { // unwrapped properties should be ignored here, as they are included in their unwrapped form return true; } @@ -325,7 +332,7 @@ protected boolean shouldIgnoreMethod(MethodScope method) { return true; } return this.options.contains(JacksonOption.INCLUDE_ONLY_JSONPROPERTY_ANNOTATED_METHODS) - && method.getAnnotationConsideringFieldAndGetter(JsonProperty.class) == null; + && method.getAnnotationConsideringFieldAndGetter(JsonProperty.class, NESTED_ANNOTATION_CHECK) == null; } /** @@ -335,7 +342,7 @@ protected boolean shouldIgnoreMethod(MethodScope method) { * @return whether the field should be in the "required" list or not */ protected boolean getRequiredCheckBasedOnJsonPropertyAnnotation(MemberScope member) { - JsonProperty jsonProperty = member.getAnnotationConsideringFieldAndGetterIfSupported(JsonProperty.class) ; + JsonProperty jsonProperty = member.getAnnotationConsideringFieldAndGetterIfSupported(JsonProperty.class, NESTED_ANNOTATION_CHECK); return jsonProperty != null && jsonProperty.required(); } @@ -346,7 +353,7 @@ protected boolean getRequiredCheckBasedOnJsonPropertyAnnotation(MemberScope member) { - JsonProperty jsonProperty = member.getAnnotationConsideringFieldAndGetter(JsonProperty.class); + JsonProperty jsonProperty = member.getAnnotationConsideringFieldAndGetter(JsonProperty.class, NESTED_ANNOTATION_CHECK); return jsonProperty != null && jsonProperty.access() == JsonProperty.Access.READ_ONLY; } @@ -357,7 +364,7 @@ protected boolean getReadOnlyCheck(MemberScope member) { * @return whether the field should be marked as write-only */ protected boolean getWriteOnlyCheck(MemberScope member) { - JsonProperty jsonProperty = member.getAnnotationConsideringFieldAndGetter(JsonProperty.class); + JsonProperty jsonProperty = member.getAnnotationConsideringFieldAndGetter(JsonProperty.class, NESTED_ANNOTATION_CHECK); return jsonProperty != null && jsonProperty.access() == JsonProperty.Access.WRITE_ONLY; } } diff --git a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonIdentityReferenceDefinitionProvider.java b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonIdentityReferenceDefinitionProvider.java index 2c50fe31..460d8777 100644 --- a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonIdentityReferenceDefinitionProvider.java +++ b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonIdentityReferenceDefinitionProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2022 VicTools. + * Copyright 2022-2024 VicTools. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,18 +22,20 @@ import com.fasterxml.jackson.annotation.JsonIdentityInfo; import com.fasterxml.jackson.annotation.JsonIdentityReference; import com.fasterxml.jackson.annotation.ObjectIdGenerator; +import com.github.victools.jsonschema.generator.AnnotationHelper; import com.github.victools.jsonschema.generator.CustomDefinition; import com.github.victools.jsonschema.generator.CustomDefinitionProviderV2; import com.github.victools.jsonschema.generator.CustomPropertyDefinition; import com.github.victools.jsonschema.generator.MemberScope; import com.github.victools.jsonschema.generator.SchemaGenerationContext; import com.github.victools.jsonschema.generator.TypeContext; +import java.util.Arrays; import java.util.Optional; import java.util.stream.Stream; /** * Implementation of the {@link CustomDefinitionProviderV2} interface for handling types with the {@code @JsonIdentityReference(alwaysAsid = true)} - identityReferenceAnnotation. + * identityReferenceAnnotation. */ public class JsonIdentityReferenceDefinitionProvider implements CustomDefinitionProviderV2 { @@ -73,7 +75,8 @@ public CustomPropertyDefinition provideCustomPropertySchemaDefinition(MemberScop * @return designated type of the applicable identity reference (may be empty) */ public Optional getIdentityReferenceType(ResolvedType javaType, TypeContext typeContext) { - JsonIdentityReference referenceAnnotation = javaType.getErasedType().getAnnotation(JsonIdentityReference.class); + JsonIdentityReference referenceAnnotation = AnnotationHelper.resolveAnnotation(javaType.getErasedType(), JsonIdentityReference.class, + JacksonModule.NESTED_ANNOTATION_CHECK).orElse(null); return this.getIdentityReferenceType(referenceAnnotation, javaType, typeContext); } @@ -86,9 +89,10 @@ public Optional getIdentityReferenceType(ResolvedType javaType, Ty * @return designated type of the applicable identity reference (may be empty) */ public Optional getIdentityReferenceType(MemberScope scope) { - JsonIdentityReference referenceAnnotation = scope.getContainerItemAnnotationConsideringFieldAndGetterIfSupported(JsonIdentityReference.class); + JsonIdentityReference referenceAnnotation = scope.getContainerItemAnnotationConsideringFieldAndGetterIfSupported(JsonIdentityReference.class, + JacksonModule.NESTED_ANNOTATION_CHECK); if (referenceAnnotation == null) { - referenceAnnotation = scope.getAnnotationConsideringFieldAndGetter(JsonIdentityReference.class); + referenceAnnotation = scope.getAnnotationConsideringFieldAndGetter(JsonIdentityReference.class, JacksonModule.NESTED_ANNOTATION_CHECK); } return this.getIdentityReferenceType(referenceAnnotation, scope.getType(), scope.getContext()); } @@ -110,12 +114,14 @@ private Optional getIdentityReferenceType(JsonIdentityReference re return Optional.empty(); } // additionally, the type itself must have a @JsonIdentityInfo annotation - ResolvedType typeWithIdentityInfoAnnotation = typeContext.getTypeWithAnnotation(javaType, JsonIdentityInfo.class); + ResolvedType typeWithIdentityInfoAnnotation = typeContext.getTypeWithAnnotation(javaType, JsonIdentityInfo.class, + JacksonModule.NESTED_ANNOTATION_CHECK); if (typeWithIdentityInfoAnnotation == null) { // otherwise, the @JsonIdentityReference annotation is simply ignored return Optional.empty(); } - JsonIdentityInfo identityInfoAnnotation = typeWithIdentityInfoAnnotation.getErasedType().getAnnotation(JsonIdentityInfo.class); + JsonIdentityInfo identityInfoAnnotation = typeContext.getAnnotationFromList(JsonIdentityInfo.class, + Arrays.asList(typeWithIdentityInfoAnnotation.getErasedType().getAnnotations()), JacksonModule.NESTED_ANNOTATION_CHECK); // @JsonIdentityInfo annotation declares generator with specific identity type ResolvedType identityTypeFromGenerator = typeContext.getTypeParameterFor(typeContext.resolve(identityInfoAnnotation.generator()), ObjectIdGenerator.class, 0); diff --git a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonPropertySorter.java b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonPropertySorter.java index 3d6c79e5..42d38b03 100644 --- a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonPropertySorter.java +++ b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonPropertySorter.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 VicTools. + * Copyright 2020-2024 VicTools. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ import com.fasterxml.classmate.members.HierarchicType; import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.github.victools.jsonschema.generator.AnnotationHelper; import com.github.victools.jsonschema.generator.MemberScope; import com.github.victools.jsonschema.generator.MethodScope; import com.github.victools.jsonschema.generator.impl.PropertySortUtils; @@ -98,7 +99,7 @@ protected int getPropertyIndex(MemberScope property) { * @return whether properties that are not specifically mentioned in a {@link JsonPropertyOrder} annotation should be sorted alphabetically */ protected boolean shouldSortPropertiesAlphabetically(Class declaringType) { - return Optional.ofNullable(declaringType.getAnnotation(JsonPropertyOrder.class)) + return AnnotationHelper.resolveAnnotation(declaringType, JsonPropertyOrder.class, JacksonModule.NESTED_ANNOTATION_CHECK) .map(JsonPropertyOrder::alphabetic) .orElse(this.sortAlphabeticallyIfNotAnnotated); } @@ -110,7 +111,7 @@ protected boolean shouldSortPropertiesAlphabetically(Class declaringType) { * @return {@link JsonPropertyOrder#value()} or empty list */ private List getAnnotatedPropertyOrder(Class declaringType) { - return Optional.ofNullable(declaringType.getAnnotation(JsonPropertyOrder.class)) + return AnnotationHelper.resolveAnnotation(declaringType, JsonPropertyOrder.class, JacksonModule.NESTED_ANNOTATION_CHECK) .map(JsonPropertyOrder::value) .filter(valueArray -> valueArray.length != 0) .map(Arrays::asList) diff --git a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonSubTypesResolver.java b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonSubTypesResolver.java index aba2eae0..a0ea27b8 100644 --- a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonSubTypesResolver.java +++ b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonSubTypesResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 VicTools. + * Copyright 2020-2024 VicTools. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +22,7 @@ import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.github.victools.jsonschema.generator.AnnotationHelper; import com.github.victools.jsonschema.generator.CustomDefinition; import com.github.victools.jsonschema.generator.CustomDefinitionProviderV2; import com.github.victools.jsonschema.generator.CustomPropertyDefinition; @@ -34,6 +35,8 @@ import com.github.victools.jsonschema.generator.TypeContext; import com.github.victools.jsonschema.generator.TypeScope; import com.github.victools.jsonschema.generator.impl.AttributeCollector; +import java.lang.annotation.Annotation; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -109,7 +112,8 @@ public List findSubtypes(ResolvedType declaredType, SchemaGenerati if (this.skipSubtypeResolution(declaredType, context.getTypeContext())) { return null; } - JsonSubTypes subtypesAnnotation = declaredType.getErasedType().getAnnotation(JsonSubTypes.class); + JsonSubTypes subtypesAnnotation = AnnotationHelper.resolveAnnotation(declaredType.getErasedType(), JsonSubTypes.class, + JacksonModule.NESTED_ANNOTATION_CHECK).orElse(null); return this.lookUpSubtypesFromAnnotation(declaredType, subtypesAnnotation, context.getTypeContext()); } @@ -123,7 +127,7 @@ public List findTargetTypeOverrides(MemberScope property) { if (this.skipSubtypeResolution(property)) { return null; } - JsonSubTypes subtypesAnnotation = property.getAnnotationConsideringFieldAndGetter(JsonSubTypes.class); + JsonSubTypes subtypesAnnotation = property.getAnnotationConsideringFieldAndGetter(JsonSubTypes.class, JacksonModule.NESTED_ANNOTATION_CHECK); return this.lookUpSubtypesFromAnnotation(property.getType(), subtypesAnnotation, property.getContext()); } @@ -170,16 +174,21 @@ public CustomDefinition provideCustomSchemaDefinition(ResolvedType javaType, Sch // since 4.37.0: not for void methods return null; } - ResolvedType typeWithTypeInfo = context.getTypeContext().getTypeWithAnnotation(javaType, JsonTypeInfo.class); - if (typeWithTypeInfo == null || javaType.getErasedType().getAnnotation(JsonSubTypes.class) != null - || this.skipSubtypeResolution(javaType, context.getTypeContext())) { + final TypeContext typeContext = context.getTypeContext(); + ResolvedType typeWithTypeInfo = typeContext.getTypeWithAnnotation(javaType, JsonTypeInfo.class, JacksonModule.NESTED_ANNOTATION_CHECK); + if (typeWithTypeInfo == null + || AnnotationHelper.resolveAnnotation(javaType.getErasedType(), JsonSubTypes.class, JacksonModule.NESTED_ANNOTATION_CHECK).isPresent() + || this.skipSubtypeResolution(javaType, typeContext)) { // no @JsonTypeInfo annotation found or the given javaType is the super type, that should be replaced return null; } Class erasedTypeWithTypeInfo = typeWithTypeInfo.getErasedType(); - JsonTypeInfo typeInfoAnnotation = erasedTypeWithTypeInfo.getAnnotation(JsonTypeInfo.class); - JsonSubTypes subTypesAnnotation = erasedTypeWithTypeInfo.getAnnotation(JsonSubTypes.class); - TypeScope scope = context.getTypeContext().createTypeScope(javaType); + final List annotationsList = Arrays.asList(erasedTypeWithTypeInfo.getAnnotations()); + JsonTypeInfo typeInfoAnnotation = typeContext.getAnnotationFromList(JsonTypeInfo.class, annotationsList, + JacksonModule.NESTED_ANNOTATION_CHECK); + JsonSubTypes subTypesAnnotation = typeContext.getAnnotationFromList(JsonSubTypes.class, annotationsList, + JacksonModule.NESTED_ANNOTATION_CHECK); + TypeScope scope = typeContext.createTypeScope(javaType); ObjectNode definition = this.createSubtypeDefinition(scope, typeInfoAnnotation, subTypesAnnotation, context); if (definition == null) { return null; @@ -195,10 +204,11 @@ public CustomDefinition provideCustomSchemaDefinition(ResolvedType javaType, Sch * @return applicable custom per-property override schema definition (may be {@code null}) */ public CustomPropertyDefinition provideCustomPropertySchemaDefinition(MemberScope scope, SchemaGenerationContext context) { - if (this.skipSubtypeResolution(scope) || scope.getType().getErasedType().getDeclaredAnnotation(JsonSubTypes.class) != null) { + if (this.skipSubtypeResolution(scope) || AnnotationHelper.resolveAnnotation(scope.getType().getErasedType(), JsonSubTypes.class, + JacksonModule.NESTED_ANNOTATION_CHECK).isPresent()) { return null; } - JsonTypeInfo typeInfoAnnotation = scope.getAnnotationConsideringFieldAndGetter(JsonTypeInfo.class); + JsonTypeInfo typeInfoAnnotation = scope.getAnnotationConsideringFieldAndGetter(JsonTypeInfo.class, JacksonModule.NESTED_ANNOTATION_CHECK); if (typeInfoAnnotation == null) { // the normal per-type behaviour is not being overridden, i.e., no need for an inline custom property schema return null; @@ -210,7 +220,7 @@ public CustomPropertyDefinition provideCustomPropertySchemaDefinition(MemberScop .add(context.createStandardDefinitionReference(scope.getType(), this)); return new CustomPropertyDefinition(definition, CustomDefinition.AttributeInclusion.YES); } - JsonSubTypes subTypesAnnotation = scope.getAnnotationConsideringFieldAndGetter(JsonSubTypes.class); + JsonSubTypes subTypesAnnotation = scope.getAnnotationConsideringFieldAndGetter(JsonSubTypes.class, JacksonModule.NESTED_ANNOTATION_CHECK); ObjectNode definition = this.createSubtypeDefinition(scope, typeInfoAnnotation, subTypesAnnotation, context); if (definition == null) { return null; @@ -269,7 +279,7 @@ private static Optional getNameFromSubTypeAnnotation(Class erasedTarg * @return simple class name, with declaring class's unqualified name as prefix for member classes */ private static Optional getNameFromTypeNameAnnotation(Class erasedTargetType) { - return Optional.ofNullable(erasedTargetType.getAnnotation(JsonTypeName.class)) + return AnnotationHelper.resolveAnnotation(erasedTargetType, JsonTypeName.class, JacksonModule.NESTED_ANNOTATION_CHECK) .map(JsonTypeName::value) .filter(name -> !name.isEmpty()); } diff --git a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonUnwrappedDefinitionProvider.java b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonUnwrappedDefinitionProvider.java index a825e811..48cc2451 100644 --- a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonUnwrappedDefinitionProvider.java +++ b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonUnwrappedDefinitionProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2023 VicTools. + * Copyright 2023-2024 VicTools. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,11 +23,11 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.github.victools.jsonschema.generator.AnnotationHelper; import com.github.victools.jsonschema.generator.CustomDefinition; import com.github.victools.jsonschema.generator.CustomDefinitionProviderV2; import com.github.victools.jsonschema.generator.SchemaGenerationContext; import com.github.victools.jsonschema.generator.SchemaKeyword; -import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -60,9 +60,9 @@ public CustomDefinition provideCustomSchemaDefinition(ResolvedType javaType, Sch // include each annotated member's type considering the optional prefix and/or suffix Stream.concat(Stream.of(typeWithMembers.getMemberFields()), Stream.of(typeWithMembers.getMemberMethods())) - .filter(member -> Optional.ofNullable(member.getAnnotations().get(JsonUnwrapped.class)) - .filter(JsonUnwrapped::enabled).isPresent()) .map(member -> this.createUnwrappedMemberSchema(member, context)) + .filter(Optional::isPresent) + .map(Optional::get) .forEachOrdered(allOf::add); return new CustomDefinition(definition); @@ -75,12 +75,9 @@ public CustomDefinition provideCustomSchemaDefinition(ResolvedType javaType, Sch * @return whether the given member has an {@code enabled} {@link JsonUnwrapped @JsonUnwrapped} annotation */ private boolean hasJsonUnwrappedAnnotation(ResolvedMember member) { - for (Annotation annotation : member.getAnnotations()) { - if (annotation instanceof JsonUnwrapped && ((JsonUnwrapped) annotation).enabled()) { - return true; - } - } - return false; + return AnnotationHelper.resolveAnnotation(member, JsonUnwrapped.class, JacksonModule.NESTED_ANNOTATION_CHECK) + .filter(JsonUnwrapped::enabled) + .isPresent(); } /** @@ -90,13 +87,16 @@ private boolean hasJsonUnwrappedAnnotation(ResolvedMember member) { * @param context generation context * @return created schema */ - private ObjectNode createUnwrappedMemberSchema(ResolvedMember member, SchemaGenerationContext context) { - ObjectNode definition = context.createStandardDefinition(member.getType(), null); - JsonUnwrapped annotation = member.getAnnotations().get(JsonUnwrapped.class); - if (!annotation.prefix().isEmpty() || !annotation.suffix().isEmpty()) { - this.applyPrefixAndSuffixToPropertyNames(definition, annotation.prefix(), annotation.suffix(), context); - } - return definition; + private Optional createUnwrappedMemberSchema(ResolvedMember member, SchemaGenerationContext context) { + return AnnotationHelper.resolveAnnotation(member, JsonUnwrapped.class, JacksonModule.NESTED_ANNOTATION_CHECK) + .filter(JsonUnwrapped::enabled) + .map(annotation -> { + ObjectNode definition = context.createStandardDefinition(member.getType(), null); + if (!annotation.prefix().isEmpty() || !annotation.suffix().isEmpty()) { + this.applyPrefixAndSuffixToPropertyNames(definition, annotation.prefix(), annotation.suffix(), context); + } + return definition; + }); } /** diff --git a/jsonschema-module-jackson/src/test/java/com/github/victools/jsonschema/module/jackson/JsonIdentityReferenceDefinitionProviderTest.java b/jsonschema-module-jackson/src/test/java/com/github/victools/jsonschema/module/jackson/JsonIdentityReferenceDefinitionProviderTest.java index 62d00614..6c5a1284 100644 --- a/jsonschema-module-jackson/src/test/java/com/github/victools/jsonschema/module/jackson/JsonIdentityReferenceDefinitionProviderTest.java +++ b/jsonschema-module-jackson/src/test/java/com/github/victools/jsonschema/module/jackson/JsonIdentityReferenceDefinitionProviderTest.java @@ -17,12 +17,15 @@ package com.github.victools.jsonschema.module.jackson; import com.fasterxml.classmate.ResolvedType; +import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; import com.fasterxml.jackson.annotation.JsonIdentityInfo; import com.fasterxml.jackson.annotation.JsonIdentityReference; import com.fasterxml.jackson.annotation.ObjectIdGenerators; import com.github.victools.jsonschema.generator.FieldScope; import com.github.victools.jsonschema.generator.SchemaVersion; import com.github.victools.jsonschema.generator.TypeContext; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Optional; import java.util.UUID; import java.util.stream.Stream; @@ -127,11 +130,16 @@ private static class TestTypeWithReferences { private static class TestTypeReferencedAsInteger { } - @JsonIdentityInfo(generator = ObjectIdGenerators.StringIdGenerator.class) - @JsonIdentityReference(alwaysAsId = true) + @StringIdentity private static class TestTypeReferencedAsString { } + @JsonIdentityInfo(generator = ObjectIdGenerators.StringIdGenerator.class) + @JsonIdentityReference(alwaysAsId = true) + @JacksonAnnotationsInside + @Retention(RetentionPolicy.RUNTIME) + @interface StringIdentity {} + @JsonIdentityInfo(generator = ObjectIdGenerators.UUIDGenerator.class) @JsonIdentityReference(alwaysAsId = true) private static class TestTypeReferencedAsUuid { diff --git a/jsonschema-module-jackson/src/test/java/com/github/victools/jsonschema/module/jackson/JsonSubTypesResolverCustomDefinitionsTest.java b/jsonschema-module-jackson/src/test/java/com/github/victools/jsonschema/module/jackson/JsonSubTypesResolverCustomDefinitionsTest.java index b0cef553..215c28c6 100644 --- a/jsonschema-module-jackson/src/test/java/com/github/victools/jsonschema/module/jackson/JsonSubTypesResolverCustomDefinitionsTest.java +++ b/jsonschema-module-jackson/src/test/java/com/github/victools/jsonschema/module/jackson/JsonSubTypesResolverCustomDefinitionsTest.java @@ -17,6 +17,7 @@ package com.github.victools.jsonschema.module.jackson; import com.fasterxml.classmate.ResolvedType; +import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeName; @@ -25,6 +26,8 @@ import com.github.victools.jsonschema.generator.MemberScope; import com.github.victools.jsonschema.generator.MethodScope; import com.github.victools.jsonschema.generator.SchemaVersion; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.stream.Stream; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; @@ -188,7 +191,7 @@ private static class TestClassWithSuperTypeReferences { public TestSuperClassWithNameProperty superTypeWithAnnotationOnGetter; @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.WRAPPER_ARRAY) public TestSuperClassWithNameProperty superTypeWithAnnotationOnFieldAndGetter; - @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, property = "fullClass", include = JsonTypeInfo.As.PROPERTY) + @FullClassProperty public TestSuperInterface superInterfaceWithAnnotationOnField; public TestSuperClassWithNameProperty getSuperTypeNoAnnotation() { @@ -210,6 +213,11 @@ public TestSuperClassWithNameProperty getSuperTypeWithAnnotationOnFieldAndGetter } } + @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, property = "fullClass", include = JsonTypeInfo.As.PROPERTY) + @JacksonAnnotationsInside + @Retention(RetentionPolicy.RUNTIME) + @interface FullClassProperty {} + @JsonTypeInfo(use = JsonTypeInfo.Id.NAME) @JsonSubTypes({ @JsonSubTypes.Type(TestSubClass1.class), diff --git a/slate-docs/source/includes/_jackson-module.md b/slate-docs/source/includes/_jackson-module.md index 9a01f37d..1234c07f 100644 --- a/slate-docs/source/includes/_jackson-module.md +++ b/slate-docs/source/includes/_jackson-module.md @@ -32,6 +32,7 @@ SchemaGeneratorConfigBuilder configBuilder = new SchemaGeneratorConfigBuilder(Sc 13. Optionally: ignore all methods but those with a `@JsonProperty` annotation, if the `JacksonOption.INCLUDE_ONLY_JSONPROPERTY_ANNOTATED_METHODS` was provided (i.e. this is an "opt-in"). 14. Optionally: respect `@JsonIdentityReference(alwaysAsId=true)` annotation if there is a corresponding `@JsonIdentityInfo` annotation on the type and the `JacksonOption.JSONIDENTITY_REFERENCE_ALWAYS_AS_ID` as provided (i.e., this is an "opt-in") 15. Elevate nested properties to the parent type where members are annotated with `@JsonUnwrapped`. +16. Support `@JacksonAnnotationsInside` annotated combo annotations Schema attributes derived from annotations on getter methods are also applied to their associated fields.