diff --git a/jsonb-generator/src/main/java/io/avaje/jsonb/generator/TypeReader.java b/jsonb-generator/src/main/java/io/avaje/jsonb/generator/TypeReader.java index 3179c1e0..ec4246bf 100644 --- a/jsonb-generator/src/main/java/io/avaje/jsonb/generator/TypeReader.java +++ b/jsonb-generator/src/main/java/io/avaje/jsonb/generator/TypeReader.java @@ -3,6 +3,7 @@ import javax.lang.model.element.*; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.ElementFilter; +import javax.lang.model.util.Elements; import static java.util.stream.Collectors.toSet; @@ -263,18 +264,49 @@ private void readMethod(Element element, List localFields) { } } // for getter/accessor methods only, not setters - PropertyPrism.getOptionalOn(methodElement).ifPresent(propertyPrism -> { - if (!methodElement.getParameters().isEmpty()) { - logError(errorContext + baseType + ", @Json.Property can only be placed on Getter Methods, but on %s", methodElement); - return; - } + PropertyPrism.getOptionalOn(methodElement) + .filter(p -> !hasRecordPropertyAnnotation(methodElement)) + .ifPresent(propertyPrism -> { + if (!methodElement.getParameters().isEmpty()) { + logError(errorContext + baseType + ", @Json.Property can only be placed on Getter Methods, but on %s", methodElement); + return; + } + + // getter property as simulated read-only field with getter method + final var frequency = frequency(propertyPrism.value()); + final var reader = new FieldReader(element, namingConvention, currentSubType, genericTypeParams, frequency); + reader.getterMethod(new MethodReader(methodElement)); + localFields.add(reader); + }); + } + + private boolean hasRecordPropertyAnnotation(ExecutableElement methodElement) { + try { + return APContext.jdkVersion() >= 16 + && Optional.ofNullable(recordComponentFor(methodElement)) + .map(Element.class::cast) + .flatMap(TypeReader::matchingField) + .filter(PropertyPrism::isPresent) + .isPresent(); + } catch (Exception e) { + return false; + } + } + + /** + * e is a RecordComponentElement that doesn't have the annotation + * look up the field by name to see if the annotation is present + */ + private static Optional matchingField(Element e) { + return ElementFilter.fieldsIn(e.getEnclosingElement().getEnclosedElements()).stream() + .filter(f -> f.getSimpleName().contentEquals(e.getSimpleName())) + .findAny(); + } - // getter property as simulated read-only field with getter method - final var frequency = frequency(propertyPrism.value()); - final var reader = new FieldReader(element, namingConvention, currentSubType, genericTypeParams, frequency); - reader.getterMethod(new MethodReader(methodElement)); - localFields.add(reader); - }); + private static Object recordComponentFor(ExecutableElement methodElement) throws Exception { + return Elements.class + .getMethod("recordComponentFor", ExecutableElement.class) + .invoke(APContext.elements(), methodElement); } private boolean checkMethod2(ExecutableElement methodElement) {