diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml
index 5847721..ae4909f 100644
--- a/.github/workflows/deploy.yaml
+++ b/.github/workflows/deploy.yaml
@@ -25,7 +25,7 @@ jobs:
runs-on: ubuntu-20.04
strategy:
matrix:
- java-version: [ 11 ]
+ java-version: [ 11, 17, 21, 23 ]
steps:
- name: Check out Git repository
uses: actions/checkout@v3
diff --git a/domino-jackson-processor/pom.xml b/domino-jackson-processor/pom.xml
index 1f3b779..45b55b2 100644
--- a/domino-jackson-processor/pom.xml
+++ b/domino-jackson-processor/pom.xml
@@ -6,7 +6,7 @@
org.dominokit
domino-jackson-parent
- 1.0.4
+ 1.0.5
domino-jackson-processor
@@ -108,7 +108,7 @@
org.apache.maven.plugins
maven-shade-plugin
- 3.2.2
+ 3.6.0
package
diff --git a/domino-jackson-processor/src/it/java/org/dominokit/jackson/records/Circle.java b/domino-jackson-processor/src/it/java/org/dominokit/jackson/records/Circle.java
new file mode 100644
index 0000000..729e477
--- /dev/null
+++ b/domino-jackson-processor/src/it/java/org/dominokit/jackson/records/Circle.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright © 2019 Dominokit
+ *
+ * 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 org.dominokit.jackson.records;
+
+import org.dominokit.jackson.annotation.JSONMapper;
+
+@JSONMapper
+public record Circle(double radius) {
+ public static final double PI = 3.14159;
+
+ public Circle {
+ if (radius < 0) {
+ throw new IllegalArgumentException("Radius cannot be negative");
+ }
+ }
+
+ public double area() {
+ return PI * radius * radius;
+ }
+}
diff --git a/domino-jackson-processor/src/it/java/org/dominokit/jackson/records/ComplexNumber.java b/domino-jackson-processor/src/it/java/org/dominokit/jackson/records/ComplexNumber.java
new file mode 100644
index 0000000..3f8587f
--- /dev/null
+++ b/domino-jackson-processor/src/it/java/org/dominokit/jackson/records/ComplexNumber.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright © 2019 Dominokit
+ *
+ * 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 org.dominokit.jackson.records;
+
+import org.dominokit.jackson.annotation.JSONMapper;
+
+@JSONMapper
+public record ComplexNumber(double real, double imaginary) {
+ public ComplexNumber(double real) {
+ this(real, 0); // Calls the canonical constructor with 0 for the imaginary part
+ }
+
+ public double magnitude() {
+ return Math.sqrt(real * real + imaginary * imaginary);
+ }
+}
diff --git a/domino-jackson-processor/src/it/java/org/dominokit/jackson/records/Point.java b/domino-jackson-processor/src/it/java/org/dominokit/jackson/records/Point.java
new file mode 100644
index 0000000..475f371
--- /dev/null
+++ b/domino-jackson-processor/src/it/java/org/dominokit/jackson/records/Point.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright © 2019 Dominokit
+ *
+ * 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 org.dominokit.jackson.records;
+
+import org.dominokit.jackson.annotation.JSONMapper;
+
+@JSONMapper
+public record Point(int x, int y) {
+}
diff --git a/domino-jackson-processor/src/it/java/org/dominokit/jackson/records/RecordIT.java b/domino-jackson-processor/src/it/java/org/dominokit/jackson/records/RecordIT.java
new file mode 100644
index 0000000..cca62a2
--- /dev/null
+++ b/domino-jackson-processor/src/it/java/org/dominokit/jackson/records/RecordIT.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright © 2019 Dominokit
+ *
+ * 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 org.dominokit.jackson.records;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class RecordIT {
+
+ private static final Point_MapperImpl POINT_MAPPER = new Point_MapperImpl();
+ private static final Circle_MapperImpl CIRCLE_MAPPER = new Circle_MapperImpl();
+ private static final ComplexNumber_MapperImpl COMPLEX_NUMBER_MAPPER = new ComplexNumber_MapperImpl();
+ private static final Rectangle_MapperImpl RECTANGLE_MAPPER = new Rectangle_MapperImpl();
+
+ @Test
+ public void simpleRecordTest(){
+ String pointJson = POINT_MAPPER.write(new Point(10, 20));
+ Point point = POINT_MAPPER.read(pointJson);
+
+ Assert.assertEquals("{\"x\":10,\"y\":20}", pointJson);
+ Assert.assertEquals(10, point.x());
+ Assert.assertEquals(20, point.y());
+ Assert.assertEquals(new Point(10, 20), point);
+
+ String circleJson = CIRCLE_MAPPER.write(new Circle(10.0));
+ Circle circle = CIRCLE_MAPPER.read(circleJson);
+
+ Assert.assertEquals("{\"radius\":10.0}", circleJson);
+ Assert.assertEquals(10.0, circle.radius(), 0.001);
+ Assert.assertEquals(new Circle(10), circle);
+
+ String complexNumberJson = COMPLEX_NUMBER_MAPPER.write(new ComplexNumber(10.0));
+ ComplexNumber complexNumber = COMPLEX_NUMBER_MAPPER.read(complexNumberJson);
+
+ Assert.assertEquals("{\"real\":10.0,\"imaginary\":0.0}", complexNumberJson);
+ Assert.assertEquals(10.0, complexNumber.real(), 0.001);
+ Assert.assertEquals(0, complexNumber.imaginary(), 0.001);
+ Assert.assertEquals(new ComplexNumber(10.0), complexNumber);
+
+ String rectangleJson = RECTANGLE_MAPPER.write(new Rectangle(new Point(10,20), new Point(20, 30)));
+ Rectangle rectangle = RECTANGLE_MAPPER.read(rectangleJson);
+
+ Assert.assertEquals("{\"bottomLeft\":{\"x\":10,\"y\":20},\"topRight\":{\"x\":20,\"y\":30}}", rectangleJson);
+ Assert.assertEquals(new Point(10, 20), rectangle.a());
+ Assert.assertEquals(new Point(20, 30), rectangle.b());
+ Assert.assertEquals(new Rectangle(new Point(10,20), new Point(20, 30)), rectangle);
+
+ }
+
+}
diff --git a/domino-jackson-processor/src/it/java/org/dominokit/jackson/records/Rectangle.java b/domino-jackson-processor/src/it/java/org/dominokit/jackson/records/Rectangle.java
new file mode 100644
index 0000000..c2f6780
--- /dev/null
+++ b/domino-jackson-processor/src/it/java/org/dominokit/jackson/records/Rectangle.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright © 2019 Dominokit
+ *
+ * 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 org.dominokit.jackson.records;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.dominokit.jackson.annotation.JSONMapper;
+
+@JSONMapper
+public record Rectangle(@JsonProperty("bottomLeft") Point a, @JsonProperty("topRight") Point b) {
+
+}
diff --git a/domino-jackson-processor/src/main/java/org/dominokit/jackson/processor/Type.java b/domino-jackson-processor/src/main/java/org/dominokit/jackson/processor/Type.java
index 508873d..f311350 100644
--- a/domino-jackson-processor/src/main/java/org/dominokit/jackson/processor/Type.java
+++ b/domino-jackson-processor/src/main/java/org/dominokit/jackson/processor/Type.java
@@ -970,4 +970,14 @@ private static TypeElement toTypeElement(TypeMirror type) {
public static String getTypeQualifiedName(TypeMirror typeMirror) {
return ((TypeElement) typeUtils.asElement(typeMirror)).getQualifiedName().toString();
}
+
+ public static boolean isRecord(TypeMirror type) {
+ boolean result = false;
+ TypeElement recordElement = elementUtils.getTypeElement("java.lang.Record");
+ if (recordElement != null) { // If running on Java 16+
+ TypeMirror recordType = recordElement.asType();
+ result = typeUtils.isSubtype(type, recordType);
+ }
+ return result;
+ }
}
diff --git a/domino-jackson-processor/src/main/java/org/dominokit/jackson/processor/deserialization/AptDeserializerBuilder.java b/domino-jackson-processor/src/main/java/org/dominokit/jackson/processor/deserialization/AptDeserializerBuilder.java
index 4acc913..30147b4 100644
--- a/domino-jackson-processor/src/main/java/org/dominokit/jackson/processor/deserialization/AptDeserializerBuilder.java
+++ b/domino-jackson-processor/src/main/java/org/dominokit/jackson/processor/deserialization/AptDeserializerBuilder.java
@@ -27,22 +27,51 @@
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import com.google.auto.common.MoreElements;
import com.google.auto.common.MoreTypes;
-import com.squareup.javapoet.*;
+import com.squareup.javapoet.ClassName;
+import com.squareup.javapoet.CodeBlock;
+import com.squareup.javapoet.MethodSpec;
+import com.squareup.javapoet.ParameterizedTypeName;
+import com.squareup.javapoet.TypeName;
+import com.squareup.javapoet.TypeSpec;
+import com.squareup.javapoet.WildcardTypeName;
import java.io.IOException;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.processing.Filer;
-import javax.lang.model.element.*;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.MirroredTypeException;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.ElementFilter;
import org.dominokit.jackson.JacksonContextProvider;
import org.dominokit.jackson.JsonDeserializationContext;
import org.dominokit.jackson.JsonDeserializer;
import org.dominokit.jackson.JsonDeserializerParameters;
-import org.dominokit.jackson.deser.bean.*;
+import org.dominokit.jackson.deser.bean.AbstractBeanJsonDeserializer;
+import org.dominokit.jackson.deser.bean.AbstractIdentityDeserializationInfo;
+import org.dominokit.jackson.deser.bean.BeanPropertyDeserializer;
+import org.dominokit.jackson.deser.bean.HasDeserializerAndParameters;
+import org.dominokit.jackson.deser.bean.IdentityDeserializationInfo;
+import org.dominokit.jackson.deser.bean.Instance;
+import org.dominokit.jackson.deser.bean.InstanceBuilder;
+import org.dominokit.jackson.deser.bean.MapLike;
+import org.dominokit.jackson.deser.bean.PropertyIdentityDeserializationInfo;
+import org.dominokit.jackson.deser.bean.SubtypeDeserializer;
import org.dominokit.jackson.deser.bean.SubtypeDeserializer.BeanSubtypeDeserializer;
+import org.dominokit.jackson.deser.bean.TypeDeserializationInfo;
import org.dominokit.jackson.exception.JsonDeserializationException;
import org.dominokit.jackson.processor.AbstractJsonMapperGenerator;
import org.dominokit.jackson.processor.AbstractMapperProcessor;
@@ -167,7 +196,7 @@ protected Set moreMethods() {
// Object instance can be created by InstanceBuilder
// only for non-abstract classes
if (beanType.getKind() == TypeKind.DECLARED
- && ((DeclaredType) beanType).asElement().getKind() == ElementKind.CLASS
+ && (((DeclaredType) beanType).asElement().getKind() == ElementKind.CLASS || isRecord())
&& !((DeclaredType) beanType).asElement().getModifiers().contains(Modifier.ABSTRACT)) {
methods.add(buildInitInstanceBuilderMethod());
}
@@ -261,6 +290,10 @@ private boolean isUseJsonCreator() {
.anyMatch(o -> o.getAnnotation(JsonCreator.class) != null);
}
+ private boolean isRecord() {
+ return Type.isRecord(beanType);
+ }
+
private ExecutableElement getCreator() {
return ((DeclaredType) beanType)
.asElement().getEnclosedElements().stream()
@@ -270,6 +303,40 @@ private ExecutableElement getCreator() {
.orElse(null);
}
+ public Optional getRecordCanonicalConstructor() {
+ TypeElement typeElement = (TypeElement) typeUtils.asElement(beanType);
+ // Get all fields declared in the class itself (assuming these are the record components)
+ List recordFields =
+ ElementFilter.fieldsIn(typeElement.getEnclosedElements()).stream()
+ .filter(
+ field ->
+ field.getModifiers().contains(Modifier.FINAL)
+ && !field.getModifiers().contains(Modifier.STATIC))
+ .collect(Collectors.toList());
+
+ // Find a constructor whose parameters match the record fields
+ for (ExecutableElement constructor :
+ ElementFilter.constructorsIn(typeElement.getEnclosedElements())) {
+ List extends VariableElement> parameters = constructor.getParameters();
+
+ if (parameters.size() == recordFields.size()) {
+ boolean matches = true;
+
+ // Check if parameter types match field types in order
+ for (int i = 0; i < parameters.size(); i++) {
+ if (!typeUtils.isSameType(parameters.get(i).asType(), recordFields.get(i).asType())) {
+ matches = false;
+ break;
+ }
+ }
+ if (matches) {
+ return Optional.of(constructor); // Canonical constructor found
+ }
+ }
+ }
+ return Optional.empty(); // Canonical constructor not found
+ }
+
private boolean isUseBuilder() {
String builderName = getBuilderName();
return builderName != null && !builderName.isEmpty();
@@ -359,8 +426,16 @@ private MethodSpec buildInitInstanceBuilderMethod() {
ParameterizedTypeName.get(
ClassName.get(InstanceBuilder.class), ClassName.get(beanType)));
- if (isUseJsonCreator()) {
- ExecutableElement creator = getCreator();
+ boolean jsonCreator = isUseJsonCreator();
+ boolean record = isRecord();
+
+ if (jsonCreator || record) {
+ ExecutableElement creator;
+ if (jsonCreator) {
+ creator = getCreator();
+ } else {
+ creator = getRecordCanonicalConstructor().get();
+ }
List extends VariableElement> parameterTypes = creator.getParameters();
parameterBuilders =
parameterTypes.stream()
@@ -424,7 +499,7 @@ private TypeSpec instanceBuilderReturnType(boolean useBuilder) {
.addModifiers(Modifier.PRIVATE)
.returns(ClassName.get(beanType));
- if (isUseJsonCreator()) {
+ if (isUseJsonCreator() || isRecord()) {
for (ParameterDeserializerBuilder parameterBuilder : parameterBuilders) {
createMethodBuilder.addParameter(
Type.wrapperType(parameterBuilder.getParameterType()),
@@ -493,7 +568,7 @@ private MethodSpec newInstanceMethod(
+ buildMethodName
+ "(), bufferedProperties)",
ParameterizedTypeName.get(ClassName.get(Instance.class), ClassName.get(beanType)));
- } else if (isUseJsonCreator()) {
+ } else if (isUseJsonCreator() || isRecord()) {
buildAssignProperties(beanType, createMethod, builder);
} else {
builder.addStatement(
@@ -549,7 +624,7 @@ private MethodSpec getDeserializerMethod() {
}
private Optional buildInitDeserializersMethod(TypeMirror beanType) {
- if (isUseBuilder() || isUseJsonCreator() || isAbstract(beanType)) {
+ if (isUseBuilder() || isUseJsonCreator() || isAbstract(beanType) || isRecord()) {
return Optional.empty();
}
TypeName resultType =
diff --git a/domino-jackson-processor/src/main/java/org/dominokit/jackson/processor/deserialization/DeserializerBuilder.java b/domino-jackson-processor/src/main/java/org/dominokit/jackson/processor/deserialization/DeserializerBuilder.java
index 3f2ace6..eb35752 100644
--- a/domino-jackson-processor/src/main/java/org/dominokit/jackson/processor/deserialization/DeserializerBuilder.java
+++ b/domino-jackson-processor/src/main/java/org/dominokit/jackson/processor/deserialization/DeserializerBuilder.java
@@ -120,14 +120,23 @@ private MethodSpec buildParametersMethod() {
}
private AccessorInfo setterInfo(Element field) {
- Optional accessor =
- getAccessors(beanType).stream()
- .filter(accessorInfo -> accessorInfo.getName().startsWith("set"))
- .filter(
- accessorInfo ->
- Introspector.decapitalize(accessorInfo.getName().substring(3))
- .equals(field.getSimpleName().toString()))
- .findFirst();
+ Optional accessor;
+ if (Type.isRecord(beanType)) {
+ accessor =
+ getAccessors(beanType).stream()
+ .filter(
+ accessorInfo -> accessorInfo.getName().equals(field.getSimpleName().toString()))
+ .findFirst();
+ } else {
+ accessor =
+ getAccessors(beanType).stream()
+ .filter(accessorInfo -> accessorInfo.getName().startsWith("set"))
+ .filter(
+ accessorInfo ->
+ Introspector.decapitalize(accessorInfo.getName().substring(3))
+ .equals(field.getSimpleName().toString()))
+ .findFirst();
+ }
return accessor.orElseGet(() -> new AccessorInfo(field.getSimpleName().toString()));
}
diff --git a/domino-jackson-processor/src/main/java/org/dominokit/jackson/processor/serialization/SerializerBuilder.java b/domino-jackson-processor/src/main/java/org/dominokit/jackson/processor/serialization/SerializerBuilder.java
index d824161..c7e3645 100644
--- a/domino-jackson-processor/src/main/java/org/dominokit/jackson/processor/serialization/SerializerBuilder.java
+++ b/domino-jackson-processor/src/main/java/org/dominokit/jackson/processor/serialization/SerializerBuilder.java
@@ -161,18 +161,28 @@ private boolean hasTypeJsonInclude() {
}
AbstractJsonMapperGenerator.AccessorInfo getterInfo() {
- String prefix = field.asType().getKind() == TypeKind.BOOLEAN ? "is" : "get";
- Optional accessor =
- getAccessors(beanType).stream()
- .filter(
- accessorInfo ->
- accessorInfo.getName().startsWith("is")
- || accessorInfo.getName().startsWith("get"))
- .filter(
- accessorInfo ->
- Introspector.decapitalize(accessorInfo.getName().substring(prefix.length()))
- .equals(field.getSimpleName().toString()))
- .findFirst();
+ Optional accessor;
+ if (Type.isRecord(beanType)) {
+ accessor =
+ getAccessors(beanType).stream()
+ .filter(
+ accessorInfo -> accessorInfo.getName().equals(field.getSimpleName().toString()))
+ .findFirst();
+ } else {
+ String prefix = field.asType().getKind() == TypeKind.BOOLEAN ? "is" : "get";
+ accessor =
+ getAccessors(beanType).stream()
+ .filter(
+ accessorInfo ->
+ accessorInfo.getName().startsWith("is")
+ || accessorInfo.getName().startsWith("get"))
+ .filter(
+ accessorInfo ->
+ Introspector.decapitalize(accessorInfo.getName().substring(prefix.length()))
+ .equals(field.getSimpleName().toString()))
+ .findFirst();
+ }
+
return accessor.orElseGet(
() -> new AbstractJsonMapperGenerator.AccessorInfo(field.getSimpleName().toString()));
}
diff --git a/domino-jackson/pom.xml b/domino-jackson/pom.xml
index ac5d8ab..d65868c 100644
--- a/domino-jackson/pom.xml
+++ b/domino-jackson/pom.xml
@@ -6,7 +6,7 @@
org.dominokit
domino-jackson-parent
- 1.0.4
+ 1.0.5
domino-jackson
diff --git a/pom.xml b/pom.xml
index 307c64a..9533ef0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
org.dominokit
domino-jackson-parent
- 1.0.4
+ 1.0.5
pom
domino-jackson-parent
@@ -66,15 +66,15 @@
HEAD-SNAPSHOT
- 1.0.4
+ 1.0.5
11
11
UTF-8
UTF-8
- 3.11.0
+ 3.13.0
3.0.1
- 2.10.4
+ 3.11.1
1.6
1.6.8
3.0.0-M1
@@ -85,7 +85,7 @@
1.1.0
2.16.0
- 1.2.1
+ 1.2.3
1.0.2
@@ -94,7 +94,7 @@
org.gwtproject
gwt
- 2.10.0
+ 2.12.0
pom
import
@@ -383,5 +383,82 @@
+
+ java17-tests
+
+ [17,)
+
+
+
+
+ org.codehaus.mojo
+ build-helper-maven-plugin
+ 1.9.1
+
+
+ add-integration-test-source-as-test-sources
+ generate-test-sources
+
+ add-test-source
+
+
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+ 3.5.2
+
+
+ integration-test
+
+ integration-test
+ verify
+
+
+
+
+ **/*IT.java
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ 17
+
+
+
+
+
+
+
+ java23-processors
+
+ [23,)
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ 23
+
+ -proc:full
+
+
+
+
+
+