diff --git a/options.md b/options.md
index 9bb7689..22ed346 100644
--- a/options.md
+++ b/options.md
@@ -116,3 +116,58 @@ Special handling for collections. See the project test classes for usage.
| `@RecordBuilder.Options(useUnmodifiableCollections = true/false)` | Adds special handling for collection record components. The default is `false`. |
| `@RecordBuilder.Options(allowNullableCollections = true/false)` | Adds special null handling for record collectioncomponents. The default is `false`. |
| `@RecordBuilder.Options(addSingleItemCollectionBuilders = true/false)` | Adds special handling for record collectioncomponents. The default is `false`. |
+
+## Jackson Support
+
+RecordBuilder can automatically add Jackson annotations to generated builders, supporting both Jackson 2.x and 3.x. Configuration is done via the nested `@JacksonConfig` annotation.
+
+### Basic Example
+
+```java
+@RecordBuilder
+@RecordBuilder.Options(
+ jackson = @RecordBuilder.JacksonConfig(jsonPOJOBuilder = true)
+)
+@JsonDeserialize(builder = UserRecordBuilder.class)
+record UserRecord(String name, int age) {}
+```
+
+### Configuration Options
+
+| option | details |
+|--------|---------|
+| `jackson = @JacksonConfig(...)` | Configures Jackson annotation support for the generated builder. By default, no Jackson annotations are added. |
+
+### JacksonConfig Properties
+
+| property | details |
+|----------|---------|
+| `jsonPOJOBuilder` | **boolean** (default: `false`) - When `true`, adds `@JsonPOJOBuilder` annotation to the generated builder. This annotation works with `@JsonDeserialize(builder = ...)` on the record. |
+| `version` | **JacksonVersion** (default: `AUTO`) - Specifies which Jackson version to use:
• `AUTO` - Automatically detect Jackson version(s) on classpath and add all found annotations. If both Jackson 2.x and 3.x are present, annotations for both versions will be added.
• `JACKSON_2` - Only add Jackson 2.x annotations (`com.fasterxml.jackson.*`). Fails if Jackson 2.x is not found.
• `JACKSON_3` - Only add Jackson 3.x annotations (`tools.jackson.*`). Fails if Jackson 3.x is not found. |
+
+### Examples
+
+#### Auto-detect Jackson version (default)
+```java
+@RecordBuilder.Options(jackson = @RecordBuilder.JacksonConfig(jsonPOJOBuilder = true))
+```
+
+#### Explicit Jackson 2.x
+```java
+@RecordBuilder.Options(
+ jackson = @RecordBuilder.JacksonConfig(
+ jsonPOJOBuilder = true,
+ version = JacksonVersion.JACKSON_2
+ )
+)
+```
+
+#### With custom setter prefix
+```java
+@RecordBuilder.Options(
+ jackson = @RecordBuilder.JacksonConfig(jsonPOJOBuilder = true),
+ setterPrefix = "set"
+)
+```
+
+See [TestJacksonAnnotations](./record-builder-test/src/test/java/io/soabase/recordbuilder/test/TestJacksonAnnotations.java) for complete examples.
diff --git a/pom.xml b/pom.xml
index d58d237..0cd92b1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -64,6 +64,8 @@
6.2.0.Final
3.1.0
3.0.1-b09
+ 2.21.0
+ 3.0.4
0.7.0
1.0.0
1.18.42
@@ -167,6 +169,17 @@
${hibernate-validator-version}
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ ${jackson2-version}
+
+
+ tools.jackson.core
+ jackson-databind
+ ${jackson3-version}
+
+
org.glassfish
javax.el
diff --git a/record-builder-core/src/main/java/io/soabase/recordbuilder/core/RecordBuilder.java b/record-builder-core/src/main/java/io/soabase/recordbuilder/core/RecordBuilder.java
index 158ac36..a092aac 100644
--- a/record-builder-core/src/main/java/io/soabase/recordbuilder/core/RecordBuilder.java
+++ b/record-builder-core/src/main/java/io/soabase/recordbuilder/core/RecordBuilder.java
@@ -359,6 +359,31 @@
* @see #nullablePattern
*/
boolean defaultNotNull() default false;
+
+ /**
+ * Configuration for Jackson annotation support on generated builders.
+ *
+ * @see JacksonConfig
+ */
+ JacksonConfig jackson() default @JacksonConfig;
+ }
+
+ /**
+ * Configuration for Jackson annotation support on generated builders.
+ */
+ @Retention(RetentionPolicy.CLASS)
+ @Target(ElementType.ANNOTATION_TYPE)
+ @interface JacksonConfig {
+ /**
+ * Add {@code @JsonPOJOBuilder} annotation to generated builder. This annotation works with
+ * {@code @JsonDeserialize(builder = ...)} on the record.
+ */
+ boolean jsonPOJOBuilder() default false;
+
+ /**
+ * Which Jackson version to use for annotations.
+ */
+ JacksonVersion version() default JacksonVersion.AUTO;
}
@Retention(RetentionPolicy.CLASS)
@@ -378,6 +403,29 @@ enum ConcreteSettersForOptionalMode {
DISABLED, ENABLED, ENABLED_WITH_NULLABLE_ANNOTATION,
}
+ /**
+ * Specifies which Jackson version(s) to use when generating builder annotations.
+ */
+ enum JacksonVersion {
+ /**
+ * Automatically detect Jackson version(s) on classpath and add all found annotations. If both Jackson 2.x and
+ * 3.x are present, both annotations will be added.
+ */
+ AUTO,
+
+ /**
+ * Only add Jackson 2.x annotations (com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder). Fails if
+ * Jackson 2.x is not found on classpath.
+ */
+ JACKSON_2,
+
+ /**
+ * Only add Jackson 3.x annotations (tools.jackson.databind.annotation.JsonPOJOBuilder). Fails if Jackson 3.x is
+ * not found on classpath.
+ */
+ JACKSON_3,
+ }
+
/**
* Apply to record components to specify a field initializer for the generated builder
*/
diff --git a/record-builder-processor/src/main/java/io/soabase/recordbuilder/processor/InternalRecordBuilderProcessor.java b/record-builder-processor/src/main/java/io/soabase/recordbuilder/processor/InternalRecordBuilderProcessor.java
index 98ffdb6..e4a26a8 100644
--- a/record-builder-processor/src/main/java/io/soabase/recordbuilder/processor/InternalRecordBuilderProcessor.java
+++ b/record-builder-processor/src/main/java/io/soabase/recordbuilder/processor/InternalRecordBuilderProcessor.java
@@ -89,6 +89,8 @@ class InternalRecordBuilderProcessor {
builder.addAnnotation(recordBuilderGeneratedAnnotation);
}
+ new JacksonSupport(processingEnv).addJacksonAnnotations(metaData, builder);
+
if (!validateMethodNameConflicts(processingEnv, recordFacade.element())) {
builderType = Optional.empty();
return;
diff --git a/record-builder-processor/src/main/java/io/soabase/recordbuilder/processor/JacksonSupport.java b/record-builder-processor/src/main/java/io/soabase/recordbuilder/processor/JacksonSupport.java
new file mode 100644
index 0000000..868c04e
--- /dev/null
+++ b/record-builder-processor/src/main/java/io/soabase/recordbuilder/processor/JacksonSupport.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2019 The original author or authors
+ *
+ * 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 io.soabase.recordbuilder.processor;
+
+import com.palantir.javapoet.AnnotationSpec;
+import com.palantir.javapoet.ClassName;
+import com.palantir.javapoet.TypeSpec;
+import io.soabase.recordbuilder.core.RecordBuilder;
+
+import javax.annotation.processing.ProcessingEnvironment;
+
+import static javax.tools.Diagnostic.Kind.ERROR;
+
+class JacksonSupport {
+ private static final String JACKSON_2_ANNOTATION_PACKAGE = "com.fasterxml.jackson.databind.annotation";
+ private static final String JACKSON_3_ANNOTATION_PACKAGE = "tools.jackson.databind.annotation";
+
+ private static final String JSON_POJO_BUILDER = "JsonPOJOBuilder";
+
+ private final ProcessingEnvironment processingEnv;
+ private final boolean jackson2Present;
+ private final boolean jackson3Present;
+
+ JacksonSupport(ProcessingEnvironment processingEnv) {
+ this.processingEnv = processingEnv;
+ jackson2Present = isAnnotationClassPresent(JACKSON_2_ANNOTATION_PACKAGE, JSON_POJO_BUILDER);
+ jackson3Present = isAnnotationClassPresent(JACKSON_3_ANNOTATION_PACKAGE, JSON_POJO_BUILDER);
+ }
+
+ private boolean isAnnotationClassPresent(String packageName, String className) {
+ return processingEnv.getElementUtils().getTypeElement(packageName + "." + className) != null;
+ }
+
+ public void addJacksonAnnotations(RecordBuilder.Options metaData, TypeSpec.Builder builder) {
+ // return without further processing if no annotation is enabled
+ if (!anyJacksonAnnotationEnabled(metaData)) {
+ return;
+ }
+
+ switch (metaData.jackson().version()) {
+ case AUTO -> {
+ if (!jackson2Present && !jackson3Present) {
+ processingEnv.getMessager().printMessage(ERROR,
+ "jackson.jsonPOJOBuilder is enabled but Jackson is not found on classpath. "
+ + "Add jackson-databind dependency or disable jsonPOJOBuilder.");
+ return;
+ }
+
+ if (jackson2Present) {
+ addJacksonAnnotations(metaData, builder, JACKSON_2_ANNOTATION_PACKAGE);
+ }
+
+ if (jackson3Present) {
+ addJacksonAnnotations(metaData, builder, JACKSON_3_ANNOTATION_PACKAGE);
+ }
+ }
+
+ case JACKSON_2 -> {
+ if (!jackson2Present) {
+ processingEnv.getMessager().printMessage(ERROR,
+ "jackson.version is set to JACKSON_2 but Jackson 2.x is not found on classpath. "
+ + "Add jackson-databind 2.x dependency or change version to AUTO.");
+ return;
+ }
+
+ addJacksonAnnotations(metaData, builder, JACKSON_2_ANNOTATION_PACKAGE);
+ }
+
+ case JACKSON_3 -> {
+ if (!jackson3Present) {
+ processingEnv.getMessager().printMessage(ERROR,
+ "jackson.version is set to JACKSON_3 but Jackson 3.x is not found on classpath. "
+ + "Add jackson-databind 3.x dependency or change version to AUTO.");
+ return;
+ }
+
+ addJacksonAnnotations(metaData, builder, JACKSON_3_ANNOTATION_PACKAGE);
+ }
+ }
+ }
+
+ private boolean anyJacksonAnnotationEnabled(RecordBuilder.Options metaData) {
+ return metaData.jackson().jsonPOJOBuilder();
+ }
+
+ private void addJacksonAnnotations(RecordBuilder.Options metaData, TypeSpec.Builder builder, String packageName) {
+ if (metaData.jackson().jsonPOJOBuilder()) {
+ addJsonPOJOBuilderAnnotation(metaData, builder, packageName);
+ }
+ }
+
+ private void addJsonPOJOBuilderAnnotation(RecordBuilder.Options metaData, TypeSpec.Builder builder,
+ String packageName) {
+ final var annotationSpec = AnnotationSpec.builder(ClassName.get(packageName, JSON_POJO_BUILDER))
+ .addMember("withPrefix", "$S", metaData.setterPrefix()).build();
+
+ builder.addAnnotation(annotationSpec);
+ }
+}
diff --git a/record-builder-test/pom.xml b/record-builder-test/pom.xml
index e942868..d4a0730 100644
--- a/record-builder-test/pom.xml
+++ b/record-builder-test/pom.xml
@@ -72,6 +72,15 @@
provided
+
+ com.fasterxml.jackson.core
+ jackson-databind
+
+
+ tools.jackson.core
+ jackson-databind
+
+
org.glassfish
javax.el
diff --git a/record-builder-test/src/main/java/io/soabase/recordbuilder/test/JacksonAnnotated.java b/record-builder-test/src/main/java/io/soabase/recordbuilder/test/JacksonAnnotated.java
new file mode 100644
index 0000000..a9788f8
--- /dev/null
+++ b/record-builder-test/src/main/java/io/soabase/recordbuilder/test/JacksonAnnotated.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2019 The original author or authors
+ *
+ * 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 io.soabase.recordbuilder.test;
+
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import io.soabase.recordbuilder.core.RecordBuilder;
+
+import java.util.Map;
+
+public interface JacksonAnnotated {
+ String name();
+
+ String type();
+
+ Map properties();
+
+ @RecordBuilder
+ @RecordBuilder.Options(jackson = @RecordBuilder.JacksonConfig(jsonPOJOBuilder = true), useImmutableCollections = true, prefixEnclosingClassNames = false)
+ @JsonDeserialize(builder = JacksonAnnotatedRecordBuilder.class)
+ record JacksonAnnotatedRecord(String name, @RecordBuilder.Initializer("DEFAULT_TYPE") String type,
+ Map properties) implements JacksonAnnotated {
+ public static final String DEFAULT_TYPE = "dummy";
+ }
+
+ @RecordBuilder
+ @RecordBuilder.Options(jackson = @RecordBuilder.JacksonConfig(jsonPOJOBuilder = true), useImmutableCollections = true, prefixEnclosingClassNames = false, setterPrefix = "set")
+ @JsonDeserialize(builder = JacksonAnnotatedRecordCustomSetterPrefixBuilder.class)
+ record JacksonAnnotatedRecordCustomSetterPrefix(String name, @RecordBuilder.Initializer("DEFAULT_TYPE") String type,
+ Map properties) implements JacksonAnnotated {
+ public static final String DEFAULT_TYPE = "dummy";
+ }
+
+ @RecordBuilder
+ @RecordBuilder.Options(jackson = @RecordBuilder.JacksonConfig(jsonPOJOBuilder = true, version = RecordBuilder.JacksonVersion.JACKSON_2), useImmutableCollections = true, prefixEnclosingClassNames = false)
+ @JsonDeserialize(builder = JacksonAnnotatedRecordJackson2Builder.class)
+ record JacksonAnnotatedRecordJackson2(String name, @RecordBuilder.Initializer("DEFAULT_TYPE") String type,
+ Map properties) implements JacksonAnnotated {
+ public static final String DEFAULT_TYPE = "dummy";
+ }
+
+ @RecordBuilder
+ @RecordBuilder.Options(jackson = @RecordBuilder.JacksonConfig(jsonPOJOBuilder = true, version = RecordBuilder.JacksonVersion.JACKSON_3), useImmutableCollections = true, prefixEnclosingClassNames = false)
+ @tools.jackson.databind.annotation.JsonDeserialize(builder = JacksonAnnotatedRecordJackson3Builder.class)
+ record JacksonAnnotatedRecordJackson3(String name, @RecordBuilder.Initializer("DEFAULT_TYPE") String type,
+ Map properties) implements JacksonAnnotated {
+ public static final String DEFAULT_TYPE = "dummy";
+ }
+
+ @RecordBuilder
+ @RecordBuilder.Options(prefixEnclosingClassNames = false)
+ record JacksonAnnotatedRecordNoJackson(String name, String type, Map properties)
+ implements JacksonAnnotated {
+ }
+}
diff --git a/record-builder-test/src/test/java/io/soabase/recordbuilder/test/TestJacksonAnnotations.java b/record-builder-test/src/test/java/io/soabase/recordbuilder/test/TestJacksonAnnotations.java
new file mode 100644
index 0000000..f085584
--- /dev/null
+++ b/record-builder-test/src/test/java/io/soabase/recordbuilder/test/TestJacksonAnnotations.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2019 The original author or authors
+ *
+ * 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 io.soabase.recordbuilder.test;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
+import io.soabase.recordbuilder.test.JacksonAnnotated.JacksonAnnotatedRecord;
+import io.soabase.recordbuilder.test.JacksonAnnotated.JacksonAnnotatedRecordCustomSetterPrefix;
+import io.soabase.recordbuilder.test.JacksonAnnotated.JacksonAnnotatedRecordJackson2;
+import io.soabase.recordbuilder.test.JacksonAnnotated.JacksonAnnotatedRecordJackson3;
+import org.assertj.core.api.InstanceOfAssertFactories;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import java.util.Arrays;
+import java.util.stream.Stream;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.params.provider.Arguments.arguments;
+
+class TestJacksonAnnotations {
+ private static final Class> J2_POJO_BUILDER = JsonPOJOBuilder.class;
+ private static final Class> J3_POJO_BUILDER = tools.jackson.databind.annotation.JsonPOJOBuilder.class;
+
+ private final ObjectMapper jackson2ObjectMapper = new ObjectMapper();
+ private final tools.jackson.databind.ObjectMapper jackson3ObjectMapper = new tools.jackson.databind.ObjectMapper();
+
+ // -------------------------------------------------------------------------
+ // Jackson 2 annotation presence
+ // -------------------------------------------------------------------------
+
+ @ParameterizedTest
+ @MethodSource("jackson2RecordBuilders")
+ void addsJackson2JsonPOJOBuilderAnnotation(Class> type, String expectedPrefix) {
+ final var annotations = Arrays.stream(type.getAnnotations()).toList();
+ assertThat(annotations).filteredOn(annotation -> annotation.annotationType().equals(J2_POJO_BUILDER)).hasSize(1)
+ .first().asInstanceOf(InstanceOfAssertFactories.type(JsonPOJOBuilder.class))
+ .satisfies(annotation -> assertThat(annotation.withPrefix()).isEqualTo(expectedPrefix));
+ }
+
+ static Stream jackson2RecordBuilders() {
+ return Stream.of(arguments(JacksonAnnotatedRecordBuilder.class, ""),
+ arguments(JacksonAnnotatedRecordCustomSetterPrefixBuilder.class, "set"),
+ arguments(JacksonAnnotatedRecordJackson2Builder.class, ""));
+ }
+
+ // -------------------------------------------------------------------------
+ // Jackson 3 annotation presence
+ // -------------------------------------------------------------------------
+
+ @ParameterizedTest
+ @MethodSource("jackson3RecordBuilders")
+ void addsJackson3JsonPOJOBuilderAnnotation(Class> type, String expectedPrefix) {
+ final var annotations = Arrays.stream(type.getAnnotations()).toList();
+ assertThat(annotations).filteredOn(annotation -> annotation.annotationType().equals(J3_POJO_BUILDER)).hasSize(1)
+ .first()
+ .asInstanceOf(InstanceOfAssertFactories.type(tools.jackson.databind.annotation.JsonPOJOBuilder.class))
+ .satisfies(annotation -> assertThat(annotation.withPrefix()).isEqualTo(expectedPrefix));
+ }
+
+ static Stream jackson3RecordBuilders() {
+ return Stream.of(arguments(JacksonAnnotatedRecordJackson3Builder.class, ""));
+ }
+
+ // -------------------------------------------------------------------------
+ // AUTO mode: both Jackson 2 and 3 annotations present
+ // -------------------------------------------------------------------------
+
+ @ParameterizedTest
+ @MethodSource("autoRecordBuilders")
+ void addsAnnotationsForBothVersionsInAutoMode(Class> type) {
+ final var annotations = Arrays.stream(type.getAnnotations()).toList();
+ assertThat(annotations).filteredOn(annotation -> annotation.annotationType().equals(J2_POJO_BUILDER))
+ .hasSize(1);
+ assertThat(annotations).filteredOn(annotation -> annotation.annotationType().equals(J3_POJO_BUILDER))
+ .hasSize(1);
+ }
+
+ static Stream autoRecordBuilders() {
+ return Stream.of(arguments(JacksonAnnotatedRecordBuilder.class),
+ arguments(JacksonAnnotatedRecordCustomSetterPrefixBuilder.class));
+ }
+
+ // -------------------------------------------------------------------------
+ // Version isolation: each explicit version only adds its own annotation
+ // -------------------------------------------------------------------------
+
+ @Test
+ void jackson2RecordDoesNotHaveJackson3Annotation() {
+ final var annotations = Arrays.stream(JacksonAnnotatedRecordJackson2Builder.class.getAnnotations()).toList();
+ assertThat(annotations).noneMatch(annotation -> annotation.annotationType().equals(J3_POJO_BUILDER));
+ }
+
+ @Test
+ void jackson3RecordDoesNotHaveJackson2Annotation() {
+ final var annotations = Arrays.stream(JacksonAnnotatedRecordJackson3Builder.class.getAnnotations()).toList();
+ assertThat(annotations).noneMatch(annotation -> annotation.annotationType().equals(J2_POJO_BUILDER));
+ }
+
+ // -------------------------------------------------------------------------
+ // jsonPOJOBuilder = false → no annotation added
+ // -------------------------------------------------------------------------
+
+ @Test
+ void doesNotAddJsonPOJOBuilderAnnotationWhenDisabled() {
+ final var annotations = Arrays.stream(JacksonAnnotatedRecordNoJacksonBuilder.class.getAnnotations()).toList();
+ assertThat(annotations).noneMatch(annotation -> annotation.annotationType().equals(J2_POJO_BUILDER));
+ assertThat(annotations).noneMatch(annotation -> annotation.annotationType().equals(J3_POJO_BUILDER));
+ }
+
+ // -------------------------------------------------------------------------
+ // Deserialization round-trips
+ // -------------------------------------------------------------------------
+
+ @ParameterizedTest
+ @ValueSource(classes = { JacksonAnnotatedRecord.class, JacksonAnnotatedRecordCustomSetterPrefix.class,
+ JacksonAnnotatedRecordJackson2.class })
+ void deserializingWithJackson2InvokesBuilder(Class extends JacksonAnnotated> type)
+ throws JsonProcessingException {
+ final var json = """
+ {
+ "name" : "test"
+ }
+ """;
+
+ final var model = jackson2ObjectMapper.readValue(json, type);
+ assertThat(model.name()).isEqualTo("test");
+ assertThat(model.type()).isEqualTo("dummy"); // default value
+ assertThat(model.properties()).isNotNull().isEmpty(); // non-null initialized immutable collection
+ }
+
+ @Test
+ void deserializingWithJackson3InvokesBuilder() throws Exception {
+ final var json = """
+ {
+ "name" : "test"
+ }
+ """;
+
+ final var model = jackson3ObjectMapper.readValue(json, JacksonAnnotatedRecordJackson3.class);
+ assertThat(model.name()).isEqualTo("test");
+ assertThat(model.type()).isEqualTo("dummy"); // default value
+ assertThat(model.properties()).isNotNull().isEmpty(); // non-null initialized immutable collection
+ }
+
+}