Skip to content

Commit f799233

Browse files
committed
Add @SpringConversion converter
1 parent 2f4ed44 commit f799233

File tree

13 files changed

+400
-4
lines changed

13 files changed

+400
-4
lines changed

.github/workflows/main.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,26 @@ jobs:
6969
- name: Test
7070
run: ./mvnw $MAVEN_ARGS -Psnapshots -Djunit-framework.version=${{ matrix.junit-framework }} verify
7171

72+
spring-framework:
73+
74+
name: Spring Framework ${{ matrix.spring-framework }}
75+
strategy:
76+
fail-fast: false
77+
matrix:
78+
spring-framework: [6.2.12, 7.0.0-RC3]
79+
runs-on: ubuntu-latest
80+
81+
steps:
82+
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
83+
- name: Set up Java
84+
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
85+
with:
86+
java-version: '25'
87+
distribution: 'zulu'
88+
cache: maven
89+
- name: Test
90+
run: ./mvnw $MAVEN_ARGS -Dspring-framework.version=${{ matrix.spring-framework }} verify
91+
7292
javadoc:
7393

7494
name: Javadoc

docs/converters/base64.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
---
22
description: An argument converter to decode Base64 instances into byte arrays
3-
hide:
4-
- toc
53
---
64

75
# `@Base64`

docs/converters/hex.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
---
22
description: An argument converter to decode hexadecimal strings into byte arrays
3-
hide:
4-
- toc
53
---
64

75
# `@Hex`
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
---
2+
description: An argument converter using the Spring Framework type conversion
3+
---
4+
5+
# `@SpringConversion`
6+
7+
`@SpringConversion` is an annotation that converts instances using the
8+
[type conversion](https://docs.spring.io/spring-framework/reference/core/validation/convert.html)
9+
provided by the Spring Framework:
10+
11+
``` java
12+
--8<--
13+
SpringConversionDemo.java:import
14+
SpringConversionDemo.java:test
15+
--8<--
16+
```
17+
18+
The converter delegates the conversion to the
19+
[`DefaultConversionService`](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/convert/support/DefaultConversionService.html),
20+
which provides a wide range of built-in converters for common Java types, including:
21+
22+
* Primitives and their wrappers
23+
* Arrays
24+
* Collections (`List`, `Set`, `Map`, etc.)
25+
* Enums
26+
* Common value types (`UUID`, `Currency`, `Locale`, etc.)
27+
28+
## Requirements
29+
30+
The annotation requires `spring-core` available in the test classpath:
31+
32+
=== ":simple-apachemaven: Maven"
33+
34+
``` xml
35+
<dependency>
36+
<groupId>org.springframework</groupId>
37+
<artifactId>spring-core</artifactId>
38+
<version>${spring-framework.version}</version>
39+
<scope>test</scope>
40+
</dependency>
41+
```
42+
43+
=== ":simple-gradle: Gradle"
44+
45+
``` kotlin
46+
testImplementation("org.springframework:spring-core:${springFrameworkVersion}")
47+
```
48+
49+
However, declaring this dependency is generally not required in a Spring application.
50+
51+
## Examples
52+
53+
The following sections demonstrate some of the possible conversions. For a complete list of supported conversions,
54+
refer to the Spring Framework reference documentation.
55+
56+
### Array → Array
57+
58+
| Source Type | Target Declaration | Example |
59+
|-------------------|----------------------------------|--------------------------------------------------------------------------|
60+
| `#!java String[]` | `#!java @SpringConversion int[]` | `#!java new String[] { "123", "456" }``#!java new int[] { 123, 456 }` |
61+
62+
63+
### Array → Collection
64+
65+
| Source Type | Target Declaration | Example |
66+
|-----------------------|------------------------------------------|---------------------------------------------------------------------|
67+
| `#!java int[]` | `#!java @SpringConversion List<Integer>` | `#!java new int[] { 123, 456 }``#!java List.of(123, 456)` |
68+
| `#!java Integer[]` | `#!java @SpringConversion List<Integer>` | `#!java new Integer[] { 123, 456 }``#!java List.of(123, 456)` |
69+
| `#!java String[]` | `#!java @SpringConversion List<Integer>` | `#!java new String[] { "123", "456" }``#!java List.of(123, 456)` |
70+
71+
### Array → Object
72+
73+
| Source Type | Target Declaration | Example |
74+
|--------------------|--------------------------------|-------------------------------------------------------|
75+
| `#!java int[]` | `#!java @SpringConversion int` | `#!java new int[] { 123, 456 }``#!java 123` |
76+
| `#!java Integer[]` | `#!java @SpringConversion int` | `#!java new Integer[] { 123, 456 }``#!java 123` |
77+
| `#!java String[]` | `#!java @SpringConversion int` | `#!java new String[] { "123", "456" }``#!java 123` |
78+
79+
### Array → String
80+
81+
| Source Type | Target Declaration | Example |
82+
|--------------------|-----------------------------------|-------------------------------------------------------------|
83+
| `#!java int[]` | `#!java @SpringConversion String` | `#!java new int[] { 123, 456 }``#!java "123,456"` |
84+
| `#!java Integer[]` | `#!java @SpringConversion String` | `#!java new Integer[] { 123, 456 }``#!java "123,456"` |
85+
| `#!java String[]` | `#!java @SpringConversion String` | `#!java new String[] { "123", "456" }``#!java "123,456"` |
86+
87+
### Collection → Collection
88+
89+
| Source Type | Target Declaration | Example |
90+
|-----------------------|------------------------------------------|-------------------------------------------------------------|
91+
| `#!java List<String>` | `#!java @SpringConversion List<Integer>` | `#!java List.of("123", "456")``#!java List.of(123, 456)` |
92+
93+
### Map → Map
94+
95+
| Source Type | Target Declaration | Example |
96+
|------------------------------|-------------------------------------------------|-------------------------------------------------------------------------------|
97+
| `#!java Map<String, String>` | `#!java @SpringConversion Map<Integer, Double>` | `#!java Map.of("1", "123", "2", "456")``#!java Map.of(1, 123.0, 2, 456.0)` |
98+
99+
### String → Collection
100+
101+
| Source Type | Target Declaration | Example |
102+
|-----------------------|------------------------------------------|---------------------------------------------------------------------|
103+
| `#!java String` | `#!java @SpringConversion List<Integer>` | `#!java "123, 456"``#!java List.of(123, 456)` |

docs/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ The following converters are available:
4242
* [`@Base64`](converters/base64.md): decodes Base64 instances into byte arrays
4343
* [`@Bytes`](converters/bytes.md): converts strings or numbers into byte arrays
4444
* [`@Hex`](converters/hex.md): decodes hexadecimal strings into byte arrays
45+
* [`@SpringConversion`](converters/spring-conversion.md): converts instances using the Spring Framework type conversion
4546

4647
Do you have in mind another converter for your use case?
4748
[:fontawesome-brands-github: Raise an issue!](https://github.com/scordio/junit-converters/issues/new)

mkdocs.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ theme:
1818
features:
1919
- content.code.copy
2020
- navigation.sections
21+
- toc.integrate
2122
icon:
2223
repo: fontawesome/brands/github
2324
favicon: assets/images/logo.png
@@ -91,5 +92,6 @@ nav:
9192
- converters/base64.md
9293
- converters/bytes.md
9394
- converters/hex.md
95+
- converters/spring-conversion.md
9496
- javadoc.md
9597
- release-notes.md

pom.xml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
3838
<!-- Dependency versions -->
3939
<junit-framework.version>5.14.0</junit-framework.version>
40+
<spring-framework.version>5.3.39</spring-framework.version>
4041
</properties>
4142

4243
<dependencyManagement>
@@ -62,6 +63,13 @@
6263
<artifactId>jspecify</artifactId>
6364
<version>1.0.0</version>
6465
</dependency>
66+
<!-- Optional -->
67+
<dependency>
68+
<groupId>org.springframework</groupId>
69+
<artifactId>spring-core</artifactId>
70+
<version>${spring-framework.version}</version>
71+
<optional>true</optional>
72+
</dependency>
6573
<!-- Provided -->
6674
<dependency>
6775
<groupId>org.junit.jupiter</groupId>
@@ -231,6 +239,31 @@
231239
</execution>
232240
</executions>
233241
</plugin>
242+
<plugin>
243+
<groupId>org.apache.maven.plugins</groupId>
244+
<artifactId>maven-enforcer-plugin</artifactId>
245+
<version>3.6.2</version>
246+
<configuration>
247+
<rules>
248+
<bannedDependencies>
249+
<includes>
250+
<include>org.jspecify:jspecify</include>
251+
</includes>
252+
<excludes>
253+
<exclude>*:*:*:jar:compile</exclude>
254+
</excludes>
255+
</bannedDependencies>
256+
<dependencyConvergence/>
257+
</rules>
258+
</configuration>
259+
<executions>
260+
<execution>
261+
<goals>
262+
<goal>enforce</goal>
263+
</goals>
264+
</execution>
265+
</executions>
266+
</plugin>
234267
<plugin>
235268
<groupId>org.codehaus.mojo</groupId>
236269
<artifactId>build-helper-maven-plugin</artifactId>
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright © 2025 Stefano Cordio
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.github.scordio;
17+
18+
// --8<-- [start:import]
19+
import io.github.scordio.junit.converters.SpringConversion;
20+
import org.junit.jupiter.params.ParameterizedTest;
21+
import org.junit.jupiter.params.provider.ValueSource;
22+
// --8<-- [end:import]
23+
24+
import java.util.List;
25+
26+
import static org.junit.jupiter.api.Assertions.assertEquals;
27+
28+
class SpringConversionDemo {
29+
30+
// @formatter:off
31+
// --8<-- [start:test]
32+
33+
@ParameterizedTest
34+
@ValueSource(strings = "123, 456")
35+
void test(@SpringConversion List<Integer> ints) {
36+
assertEquals(List.of(123, 456), ints);
37+
}
38+
// --8<-- [end:test]
39+
// @formatter:on
40+
41+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright © 2025 Stefano Cordio
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.github.scordio.junit.converters;
17+
18+
import org.jspecify.annotations.Nullable;
19+
import org.junit.jupiter.api.extension.ParameterContext;
20+
import org.junit.jupiter.params.converter.ArgumentConversionException;
21+
import org.junit.jupiter.params.converter.ArgumentConverter;
22+
import org.junit.jupiter.params.support.FieldContext;
23+
import org.springframework.core.MethodParameter;
24+
import org.springframework.core.convert.ConversionService;
25+
import org.springframework.core.convert.TypeDescriptor;
26+
import org.springframework.core.convert.support.DefaultConversionService;
27+
28+
class SpringArgumentConverter implements ArgumentConverter {
29+
30+
private static final ConversionService CONVERSION_SERVICE = new DefaultConversionService();
31+
32+
@Override
33+
public @Nullable Object convert(@Nullable Object source, ParameterContext context)
34+
throws ArgumentConversionException {
35+
TypeDescriptor sourceType = TypeDescriptor.forObject(source);
36+
TypeDescriptor targetType = new TypeDescriptor(MethodParameter.forParameter(context.getParameter()));
37+
return CONVERSION_SERVICE.convert(source, sourceType, targetType);
38+
}
39+
40+
@Override
41+
public @Nullable Object convert(@Nullable Object source, FieldContext context) throws ArgumentConversionException {
42+
TypeDescriptor sourceType = TypeDescriptor.forObject(source);
43+
TypeDescriptor targetType = new TypeDescriptor(context.getField());
44+
return CONVERSION_SERVICE.convert(source, sourceType, targetType);
45+
}
46+
47+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright © 2025 Stefano Cordio
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.github.scordio.junit.converters;
17+
18+
import org.junit.jupiter.params.converter.ConvertWith;
19+
20+
import java.lang.annotation.Documented;
21+
import java.lang.annotation.ElementType;
22+
import java.lang.annotation.Retention;
23+
import java.lang.annotation.RetentionPolicy;
24+
import java.lang.annotation.Target;
25+
26+
/**
27+
* {@link ConvertWith} composed annotation that converts arguments using the Spring
28+
* {@link org.springframework.core.convert.ConversionService conversion service}.
29+
*
30+
* @see org.springframework.core.convert.ConversionService
31+
* @see org.springframework.core.convert.support.DefaultConversionService
32+
*/
33+
@Target({ ElementType.ANNOTATION_TYPE, ElementType.PARAMETER, ElementType.FIELD })
34+
@Retention(RetentionPolicy.RUNTIME)
35+
@Documented
36+
@ConvertWith(SpringArgumentConverter.class)
37+
@SuppressWarnings("exports")
38+
public @interface SpringConversion {
39+
40+
}

0 commit comments

Comments
 (0)