Skip to content

Commit 8be6b98

Browse files
committed
Add support to configure adding jackson @JsonPOJOBuilder annotations to generated builders
1 parent 543453a commit 8be6b98

7 files changed

Lines changed: 147 additions & 0 deletions

File tree

options.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ The names used for generated methods, classes, etc. can be changed via the follo
5454
| `@RecordBuilder.Options(fileIndent = " ")` | Return the file indent to use. |
5555
| `@RecordBuilder.Options(prefixEnclosingClassNames = true/false)` | If the record is declared inside another class, the outer class's name will be prefixed to the builder name if this returns true. The default is `true`. |
5656

57+
## Jackson Support
58+
59+
| option | details |
60+
|--------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
61+
| `@RecordBuilder.Options(addJacksonAnnotations = true/false)` | If true, builders will be annotated with `@JsonPOJOBuilder` Jackson annotations which can be used in combination with `@JsonDeserialize(builder = ...)`. See [TestJacksonAnnotations](./record-builder-test/src/test/java/io/soabase/recordbuilder/test/TestJacksonAnnotations.java) for an example. The default is `false`. |
62+
5763
## Miscellaneous
5864

5965
| option | details |

pom.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
<hibernate-validator-version>6.2.0.Final</hibernate-validator-version>
6565
<jakarta-validation-api-version>3.1.0</jakarta-validation-api-version>
6666
<javax-el-version>3.0.1-b09</javax-el-version>
67+
<jackson-version>2.19.0</jackson-version>
6768
<central-publishing-maven-plugin-version>0.7.0</central-publishing-maven-plugin-version>
6869
<jspecify-version>1.0.0</jspecify-version>
6970
<lombok-version>1.18.42</lombok-version>
@@ -167,6 +168,12 @@
167168
<version>${hibernate-validator-version}</version>
168169
</dependency>
169170

171+
<dependency>
172+
<groupId>com.fasterxml.jackson.core</groupId>
173+
<artifactId>jackson-databind</artifactId>
174+
<version>${jackson-version}</version>
175+
</dependency>
176+
170177
<dependency>
171178
<groupId>org.glassfish</groupId>
172179
<artifactId>javax.el</artifactId>

record-builder-core/src/main/java/io/soabase/recordbuilder/core/RecordBuilder.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,8 @@
359359
* @see #nullablePattern
360360
*/
361361
boolean defaultNotNull() default false;
362+
363+
boolean addJacksonAnnotations() default false;
362364
}
363365

364366
@Retention(RetentionPolicy.CLASS)

record-builder-processor/src/main/java/io/soabase/recordbuilder/processor/InternalRecordBuilderProcessor.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ class InternalRecordBuilderProcessor {
8989
builder.addAnnotation(recordBuilderGeneratedAnnotation);
9090
}
9191

92+
addJacksonAnnotations();
93+
9294
if (!validateMethodNameConflicts(processingEnv, recordFacade.element())) {
9395
builderType = Optional.empty();
9496
return;
@@ -199,6 +201,18 @@ private void addVisibility(boolean builderIsInRecordPackage, Set<Modifier> modif
199201
}
200202
}
201203

204+
private void addJacksonAnnotations() {
205+
if (!metaData.addJacksonAnnotations()) {
206+
return;
207+
}
208+
209+
final var annotationSpec = AnnotationSpec
210+
.builder(ClassName.get("com.fasterxml.jackson.databind.annotation", "JsonPOJOBuilder"))
211+
.addMember("withPrefix", "$S", metaData.setterPrefix()).build();
212+
213+
builder.addAnnotation(annotationSpec);
214+
}
215+
202216
private void addOnceOnlySupport() {
203217
if (recordComponents.isEmpty()) {
204218
return;

record-builder-test/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,11 @@
7272
<scope>provided</scope>
7373
</dependency>
7474

75+
<dependency>
76+
<groupId>com.fasterxml.jackson.core</groupId>
77+
<artifactId>jackson-databind</artifactId>
78+
</dependency>
79+
7580
<dependency>
7681
<groupId>org.glassfish</groupId>
7782
<artifactId>javax.el</artifactId>
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright 2019 The original author or authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.soabase.recordbuilder.test;
17+
18+
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
19+
import io.soabase.recordbuilder.core.RecordBuilder;
20+
21+
import java.util.Map;
22+
23+
public interface JacksonAnnotated {
24+
String name();
25+
26+
String type();
27+
28+
Map<String, Object> properties();
29+
30+
@RecordBuilder
31+
@RecordBuilder.Options(addJacksonAnnotations = true, useImmutableCollections = true, prefixEnclosingClassNames = false)
32+
@JsonDeserialize(builder = JacksonAnnotatedRecordBuilder.class)
33+
record JacksonAnnotatedRecord(String name, @RecordBuilder.Initializer("DEFAULT_TYPE") String type,
34+
Map<String, Object> properties) implements JacksonAnnotated {
35+
public static final String DEFAULT_TYPE = "dummy";
36+
}
37+
38+
@RecordBuilder
39+
@RecordBuilder.Options(addJacksonAnnotations = true, useImmutableCollections = true, prefixEnclosingClassNames = false, setterPrefix = "set")
40+
@JsonDeserialize(builder = JacksonAnnotatedRecordCustomSetterPrefixBuilder.class)
41+
record JacksonAnnotatedRecordCustomSetterPrefix(String name, @RecordBuilder.Initializer("DEFAULT_TYPE") String type,
42+
Map<String, Object> properties) implements JacksonAnnotated {
43+
public static final String DEFAULT_TYPE = "dummy";
44+
}
45+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright 2019 The original author or authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.soabase.recordbuilder.test;
17+
18+
import com.fasterxml.jackson.core.JsonProcessingException;
19+
import com.fasterxml.jackson.databind.ObjectMapper;
20+
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
21+
import io.soabase.recordbuilder.test.JacksonAnnotated.JacksonAnnotatedRecord;
22+
import io.soabase.recordbuilder.test.JacksonAnnotated.JacksonAnnotatedRecordCustomSetterPrefix;
23+
import org.assertj.core.api.InstanceOfAssertFactories;
24+
import org.junit.jupiter.params.ParameterizedTest;
25+
import org.junit.jupiter.params.provider.Arguments;
26+
import org.junit.jupiter.params.provider.MethodSource;
27+
import org.junit.jupiter.params.provider.ValueSource;
28+
29+
import java.util.Arrays;
30+
import java.util.stream.Stream;
31+
32+
import static org.assertj.core.api.Assertions.assertThat;
33+
import static org.junit.jupiter.params.provider.Arguments.arguments;
34+
35+
class TestJacksonAnnotations {
36+
private final ObjectMapper objectMapper = new ObjectMapper();
37+
38+
@ParameterizedTest
39+
@MethodSource("recordBuilders")
40+
void addsJsonPOJOBuilderAnnotation(Class<? extends JacksonAnnotated> type, String expectedPrefix) {
41+
final var annotations = Arrays.stream(type.getAnnotations()).toList();
42+
assertThat(annotations).filteredOn(annotation -> annotation.annotationType().equals(JsonPOJOBuilder.class))
43+
.hasSize(1).first().asInstanceOf(InstanceOfAssertFactories.type(JsonPOJOBuilder.class))
44+
.satisfies(annotation -> {
45+
assertThat(annotation.withPrefix()).isEqualTo(expectedPrefix);
46+
});
47+
}
48+
49+
static Stream<Arguments> recordBuilders() {
50+
return Stream.of(arguments(JacksonAnnotatedRecordBuilder.class, ""),
51+
arguments(JacksonAnnotatedRecordCustomSetterPrefixBuilder.class, "set"));
52+
}
53+
54+
@ParameterizedTest
55+
@ValueSource(classes = { JacksonAnnotatedRecord.class, JacksonAnnotatedRecordCustomSetterPrefix.class })
56+
void deserializingModelInvokesBuilder(Class<? extends JacksonAnnotated> type) throws JsonProcessingException {
57+
final var json = """
58+
{
59+
"name" : "test"
60+
}
61+
""";
62+
63+
final var model = objectMapper.readValue(json, type);
64+
assertThat(model.name()).isEqualTo("test");
65+
assertThat(model.type()).isEqualTo("dummy"); // default value
66+
assertThat(model.properties()).isNotNull().isEmpty(); // non-null initialized immutable collection
67+
}
68+
}

0 commit comments

Comments
 (0)