Skip to content

Commit 2ce85a6

Browse files
committed
add support for Enum's implementing CustomType<EnumName>
1 parent a1b8202 commit 2ce85a6

10 files changed

Lines changed: 200 additions & 27 deletions

File tree

src/main/java/it/aboutbits/springboot/toolbox/jackson/CustomTypeDeserializer.java

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,24 +23,29 @@
2323
@NullMarked
2424
public class CustomTypeDeserializer<T extends CustomType<?>> extends ValueDeserializer<T> {
2525
private final Class<T> customType;
26-
private final Constructor<T> constructor;
26+
private final @Nullable Constructor<T> constructor;
2727
private final Function<JsonParser, @Nullable Object> typeConverter;
2828

2929
public CustomTypeDeserializer(Class<T> customType) {
3030
this.customType = customType;
3131

32-
try {
33-
this.constructor = CustomTypeReflectionUtil.getCustomTypeConstructor(customType);
34-
} catch (NoSuchMethodException e) {
35-
throw new CustomTypeDeserializerException(
36-
"Unable to find constructor for type: " + customType.getName(),
37-
e
32+
if (customType.isEnum()) {
33+
this.constructor = null;
34+
this.typeConverter = getEnumConverter(customType);
35+
} else {
36+
try {
37+
this.constructor = CustomTypeReflectionUtil.getCustomTypeConstructor(customType);
38+
} catch (NoSuchMethodException e) {
39+
throw new CustomTypeDeserializerException(
40+
"Unable to find constructor for type: " + customType.getName(),
41+
e
42+
);
43+
}
44+
45+
this.typeConverter = getTypeConverter(
46+
constructor.getParameterTypes()[0]
3847
);
3948
}
40-
41-
this.typeConverter = getTypeConverter(
42-
constructor.getParameterTypes()[0]
43-
);
4449
}
4550

4651
@Override
@@ -49,9 +54,18 @@ public Class<T> handledType() {
4954
}
5055

5156
@Override
52-
public T deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) {
57+
@SuppressWarnings("unchecked")
58+
public @Nullable T deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) {
5359
var value = typeConverter.apply(jsonParser);
5460

61+
if (value == null) {
62+
return null;
63+
}
64+
65+
if (constructor == null) {
66+
return (T) value;
67+
}
68+
5569
try {
5670
return constructor.newInstance(value);
5771
} catch (

src/main/java/it/aboutbits/springboot/toolbox/web/CustomTypePropertyEditor.java

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,22 +17,27 @@
1717

1818
@NullMarked
1919
public final class CustomTypePropertyEditor<T extends CustomType<?>> extends PropertyEditorSupport {
20-
private final Constructor<T> constructor;
20+
private final @Nullable Constructor<T> constructor;
2121
private final Function<@Nullable String, @Nullable Object> typeConverter;
2222

2323
public CustomTypePropertyEditor(Class<T> customType) {
24-
try {
25-
this.constructor = CustomTypeReflectionUtil.getCustomTypeConstructor(customType);
26-
} catch (NoSuchMethodException e) {
27-
throw new CustomTypeDeserializer.CustomTypeDeserializerException(
28-
"Unable to find constructor for type: " + customType.getName(),
29-
e
24+
if (customType.isEnum()) {
25+
this.constructor = null;
26+
this.typeConverter = toEnumConverter(customType);
27+
} else {
28+
try {
29+
this.constructor = CustomTypeReflectionUtil.getCustomTypeConstructor(customType);
30+
} catch (NoSuchMethodException e) {
31+
throw new CustomTypeDeserializer.CustomTypeDeserializerException(
32+
"Unable to find constructor for type: " + customType.getName(),
33+
e
34+
);
35+
}
36+
37+
this.typeConverter = getTextToTypeConverter(
38+
constructor.getParameters()[0].getType()
3039
);
3140
}
32-
33-
this.typeConverter = getTextToTypeConverter(
34-
constructor.getParameters()[0].getType()
35-
);
3641
}
3742

3843
@SuppressWarnings("unchecked")
@@ -45,9 +50,14 @@ public String getAsText() {
4550
}
4651

4752
@Override
48-
public void setAsText(String text) throws IllegalArgumentException {
53+
public void setAsText(@Nullable String text) throws IllegalArgumentException {
4954
var value = typeConverter.apply(text);
5055

56+
if (constructor == null) {
57+
setValue(value);
58+
return;
59+
}
60+
5161
try {
5262
setValue(
5363
constructor.newInstance(value)

src/test/java/it/aboutbits/springboot/toolbox/autoconfiguration/mvc/EntityIdBindingsForControllerTest.java

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import it.aboutbits.springboot.toolbox.autoconfiguration.mvc.body.BodyWithEnumEntityId;
77
import it.aboutbits.springboot.toolbox.autoconfiguration.persistence.impl.jpa.CustomTypeEnumTestModel;
88
import it.aboutbits.springboot.toolbox.autoconfiguration.persistence.impl.jpa.CustomTypeTestModel;
9+
import it.aboutbits.springboot.toolbox.autoconfiguration.persistence.impl.jpa.DirectEnumEntityId;
910
import org.jspecify.annotations.NullMarked;
1011
import org.junit.jupiter.api.Nested;
1112
import org.junit.jupiter.api.Test;
@@ -117,6 +118,49 @@ void emailAddressAsBody() throws Exception {
117118
}
118119
}
119120

121+
@Nested
122+
class DirectEnumEntityIdTest {
123+
@Test
124+
void valueAsPathVariable() throws Exception {
125+
var value = DirectEnumEntityId.VAL1;
126+
127+
var resultAsString = performGetAndReturnResult(
128+
String.format("/test/entity-id/DirectEnumEntityId/as-path-variable/%s", value)
129+
);
130+
131+
var actual = jsonMapper.readValue(resultAsString, DirectEnumEntityId.class);
132+
133+
assertThat(actual).isEqualTo(value);
134+
}
135+
136+
@Test
137+
void valueAsRequestParameter() throws Exception {
138+
var value = DirectEnumEntityId.VAL2;
139+
140+
var resultAsString = performGetAndReturnResult(
141+
String.format("/test/entity-id/DirectEnumEntityId/as-request-parameter?value=%s", value)
142+
);
143+
144+
var actual = jsonMapper.readValue(resultAsString, DirectEnumEntityId.class);
145+
146+
assertThat(actual).isEqualTo(value);
147+
}
148+
149+
@Test
150+
void valueAsBody() throws Exception {
151+
var value = DirectEnumEntityId.VAL1;
152+
153+
var resultAsString = performPostAndReturnResult(
154+
"/test/entity-id/DirectEnumEntityId/as-body",
155+
value
156+
);
157+
158+
var actual = jsonMapper.readValue(resultAsString, DirectEnumEntityId.class);
159+
160+
assertThat(actual).isEqualTo(value);
161+
}
162+
}
163+
120164
private String performGetAndReturnResult(String url) throws Exception {
121165
var requestBuilder = MockMvcRequestBuilders.get(url)
122166
.contentType(MediaType.APPLICATION_JSON);

src/test/java/it/aboutbits/springboot/toolbox/autoconfiguration/mvc/controller/EntityIdTestController.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import it.aboutbits.springboot.toolbox.autoconfiguration.mvc.body.BodyWithEnumEntityId;
55
import it.aboutbits.springboot.toolbox.autoconfiguration.persistence.impl.jpa.CustomTypeEnumTestModel;
66
import it.aboutbits.springboot.toolbox.autoconfiguration.persistence.impl.jpa.CustomTypeTestModel;
7+
import it.aboutbits.springboot.toolbox.autoconfiguration.persistence.impl.jpa.DirectEnumEntityId;
78
import org.jspecify.annotations.NullMarked;
89
import org.springframework.web.bind.annotation.GetMapping;
910
import org.springframework.web.bind.annotation.PathVariable;
@@ -46,4 +47,19 @@ public CustomTypeEnumTestModel.ID customTypeEnumTestModelIdAsRequestParameter(@R
4647
public BodyWithEnumEntityId customTypeEnumTestModelIdAsBody(@RequestBody BodyWithEnumEntityId value) {
4748
return value;
4849
}
50+
51+
@GetMapping("/DirectEnumEntityId/as-path-variable/{value}")
52+
public DirectEnumEntityId directEnumEntityIdAsPathVariable(@PathVariable DirectEnumEntityId value) {
53+
return value;
54+
}
55+
56+
@GetMapping("/DirectEnumEntityId/as-request-parameter")
57+
public DirectEnumEntityId directEnumEntityIdAsRequestParameter(@RequestParam DirectEnumEntityId value) {
58+
return value;
59+
}
60+
61+
@PostMapping("/DirectEnumEntityId/as-body")
62+
public DirectEnumEntityId directEnumEntityIdAsBody(@RequestBody DirectEnumEntityId value) {
63+
return value;
64+
}
4965
}

src/test/java/it/aboutbits/springboot/toolbox/autoconfiguration/persistence/EntityIdJpaTest.java

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
import it.aboutbits.springboot.toolbox.autoconfiguration.persistence.impl.jpa.CustomTypeEnumTestModelRepository;
88
import it.aboutbits.springboot.toolbox.autoconfiguration.persistence.impl.jpa.CustomTypeTestModel;
99
import it.aboutbits.springboot.toolbox.autoconfiguration.persistence.impl.jpa.CustomTypeTestModelRepository;
10+
import it.aboutbits.springboot.toolbox.autoconfiguration.persistence.impl.jpa.DirectEnumEntityId;
11+
import it.aboutbits.springboot.toolbox.autoconfiguration.persistence.impl.jpa.DirectEnumIdTestModel;
12+
import it.aboutbits.springboot.toolbox.autoconfiguration.persistence.impl.jpa.DirectEnumIdTestModelRepository;
1013
import it.aboutbits.springboot.toolbox.autoconfiguration.persistence.impl.jpa.ReferencedTestModel;
1114
import org.jspecify.annotations.NullMarked;
1215
import org.junit.jupiter.api.Nested;
@@ -28,10 +31,13 @@ class EntityIdJpaTest {
2831
@Autowired
2932
CustomTypeEnumTestModelRepository repositoryEnum;
3033

34+
@Autowired
35+
DirectEnumIdTestModelRepository repositoryDirectEnum;
36+
3137
@Nested
3238
class OwnId {
3339
@Test
34-
void inAndOut_shouldSucceed() {
40+
void ownId_inAndOut_shouldSucceed() {
3541
var item = new CustomTypeTestModel();
3642

3743
var savedItem = repository.save(item);
@@ -48,7 +54,7 @@ void inAndOut_shouldSucceed() {
4854
@Nested
4955
class ReferencedId {
5056
@Test
51-
void inAndOut_shouldSucceed() {
57+
void referencedId_inAndOut_shouldSucceed() {
5258
var item = new CustomTypeTestModel();
5359
item.setReferencedId(new ReferencedTestModel.ID(1234L));
5460

@@ -66,7 +72,7 @@ void inAndOut_shouldSucceed() {
6672
@Nested
6773
class EnumId {
6874
@Test
69-
void inAndOut_shouldSucceed() {
75+
void enumId_inAndOut_shouldSucceed() {
7076
var values = CustomTypeEnumTestModel.CustomTypeEnum.values();
7177

7278
var item = new CustomTypeEnumTestModel();
@@ -84,4 +90,24 @@ values[new Random().nextInt(values.length)]
8490
.isEqualTo(savedItem);
8591
}
8692
}
93+
94+
@Nested
95+
class DirectEnumId {
96+
@Test
97+
void directEnumId_inAndOut_shouldSucceed() {
98+
var values = DirectEnumEntityId.values();
99+
100+
var item = new DirectEnumIdTestModel();
101+
item.setId(values[new Random().nextInt(values.length)]);
102+
103+
var savedItem = repositoryDirectEnum.save(item);
104+
105+
var retrievedItem = repositoryDirectEnum.findById(savedItem.getId());
106+
107+
assertThat(retrievedItem).isPresent()
108+
.get()
109+
.usingRecursiveComparison()
110+
.isEqualTo(savedItem);
111+
}
112+
}
87113
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package it.aboutbits.springboot.toolbox.autoconfiguration.persistence.impl.jpa;
2+
3+
import it.aboutbits.springboot.toolbox.type.identity.EntityId;
4+
import org.jspecify.annotations.NullMarked;
5+
6+
@NullMarked
7+
public enum DirectEnumEntityId implements EntityId<DirectEnumEntityId> {
8+
VAL1,
9+
VAL2;
10+
11+
@Override
12+
public DirectEnumEntityId value() {
13+
return this;
14+
}
15+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package it.aboutbits.springboot.toolbox.autoconfiguration.persistence.impl.jpa;
2+
3+
import it.aboutbits.springboot.toolbox.type.identity.Identified;
4+
import jakarta.persistence.Entity;
5+
import jakarta.persistence.EnumType;
6+
import jakarta.persistence.Enumerated;
7+
import jakarta.persistence.Id;
8+
import jakarta.persistence.Table;
9+
import lombok.Getter;
10+
import lombok.Setter;
11+
import org.jspecify.annotations.NonNull;
12+
import org.jspecify.annotations.NullUnmarked;
13+
14+
@Entity
15+
@Getter
16+
@Setter
17+
@Table(name = "direct_enum_id_test_model")
18+
@NullUnmarked
19+
public class DirectEnumIdTestModel implements Identified<@NonNull DirectEnumEntityId> {
20+
@Id
21+
@Enumerated(EnumType.STRING)
22+
private DirectEnumEntityId id;
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package it.aboutbits.springboot.toolbox.autoconfiguration.persistence.impl.jpa;
2+
3+
import org.jspecify.annotations.NullMarked;
4+
import org.springframework.data.jpa.repository.JpaRepository;
5+
6+
@NullMarked
7+
public interface DirectEnumIdTestModelRepository extends JpaRepository<DirectEnumIdTestModel, DirectEnumEntityId> {
8+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
databaseChangeLog:
2+
- changeSet:
3+
author: Junie
4+
id: 2026-05-06-create-direct-enum-id-test-model-table
5+
changes:
6+
- createTable:
7+
tableName: direct_enum_id_test_model
8+
columns:
9+
- column:
10+
name: id
11+
type: text
12+
constraints:
13+
nullable: false
14+
primaryKey: true

src/test/resources/db/changelog/master.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,6 @@ databaseChangeLog:
1111
- include:
1212
file: 2025-08-29-create-custom-type-enum-testing-table.yml
1313
relativeToChangelogFile: true
14+
- include:
15+
file: 2026-05-06-create-direct-enum-id-test-model-table.yml
16+
relativeToChangelogFile: true

0 commit comments

Comments
 (0)