From ca7a01d3b10f02e0e254fd9175587c4088872574 Mon Sep 17 00:00:00 2001 From: Yanming Zhou Date: Mon, 16 Mar 2026 10:02:49 +0800 Subject: [PATCH] Allow to create JsonLineMapper with provided target type other than Map It's related to #5344. Signed-off-by: Yanming Zhou --- .../item/file/mapping/JsonLineMapper.java | 79 ++++++++++++++++--- .../file/mapping/JsonLineMapperTests.java | 28 ++++++- 2 files changed, 94 insertions(+), 13 deletions(-) diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/infrastructure/item/file/mapping/JsonLineMapper.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/infrastructure/item/file/mapping/JsonLineMapper.java index 8248707e28..1bf1b7798d 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/infrastructure/item/file/mapping/JsonLineMapper.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/infrastructure/item/file/mapping/JsonLineMapper.java @@ -15,15 +15,17 @@ */ package org.springframework.batch.infrastructure.item.file.mapping; +import java.lang.reflect.Type; import java.util.Map; +import tools.jackson.core.type.TypeReference; import tools.jackson.databind.json.JsonMapper; import org.springframework.batch.infrastructure.item.file.LineMapper; /** - * Interpret a line as a JSON object and parse it up to a Map. The line should be a - * standard JSON object, starting with "{" and ending with "}" and composed of + * Interpret a line as a JSON object and parse it up to provided target type. The line + * should be a standard JSON object, starting with "{" and ending with "}" and composed of * name:value pairs separated by commas. Whitespace is ignored, e.g. * *
@@ -38,37 +40,96 @@
  *
  * @author Dave Syer
  * @author Mahmoud Ben Hassine
+ * @author Yanming Zhou
  *
  */
-public class JsonLineMapper implements LineMapper> {
+public class JsonLineMapper implements LineMapper {
 
 	private final JsonMapper jsonMapper;
 
+	private final TypeReference type;
+
 	/**
-	 * Create a new {@link JsonLineMapper} with a default {@link JsonMapper}.
+	 * Create a new {@link JsonLineMapper} with a default {@link JsonMapper} and
+	 * {@code Map} target type.
+	 * @deprecated in favor of {@code new JsonLineMapper>(new
+	 * TypeReference<>() {})}
 	 */
+	@Deprecated(forRemoval = true)
 	public JsonLineMapper() {
 		this(new JsonMapper());
 	}
 
 	/**
-	 * Create a new {@link JsonLineMapper} with the provided {@link JsonMapper}.
+	 * Create a new {@link JsonLineMapper} with the provided {@link JsonMapper} and
+	 * {@code Map} target type.
 	 * @param jsonMapper the json mapper to use
 	 * @since 6.0
+	 * @deprecated in favor of {@code new JsonLineMapper>(jsonMapper,
+	 * new TypeReference<>() {})}
 	 */
+	@Deprecated(forRemoval = true)
+	@SuppressWarnings("unchecked")
 	public JsonLineMapper(JsonMapper jsonMapper) {
+		this(jsonMapper, (TypeReference) new TypeReference>() {
+		});
+	}
+
+	/**
+	 * Create a new {@link JsonLineMapper} with the provided {@link JsonMapper} and
+	 * provided target type.
+	 * @param type the target type
+	 * @since 6.1
+	 */
+	public JsonLineMapper(Class type) {
+		this(new JsonMapper(), type);
+	}
+
+	/**
+	 * Create a new {@link JsonLineMapper} with the provided {@link JsonMapper} and
+	 * provided target type.
+	 * @param type the target type
+	 * @since 6.1
+	 */
+	public JsonLineMapper(TypeReference type) {
+		this(new JsonMapper(), type);
+	}
+
+	/**
+	 * Create a new {@link JsonLineMapper} with the provided {@link JsonMapper} and
+	 * provided target type.
+	 * @param jsonMapper the json mapper to use
+	 * @param type the target type
+	 * @since 6.1
+	 */
+	public JsonLineMapper(JsonMapper jsonMapper, Class type) {
+		this(jsonMapper, new TypeReference<>() {
+			@Override
+			public Type getType() {
+				return type;
+			}
+		});
+	}
+
+	/**
+	 * Create a new {@link JsonLineMapper} with the provided {@link JsonMapper}.
+	 * @param jsonMapper the json mapper to use
+	 * @param type the target type
+	 * @since 6.1
+	 */
+	public JsonLineMapper(JsonMapper jsonMapper, TypeReference type) {
 		this.jsonMapper = jsonMapper;
+		this.type = type;
 	}
 
 	/**
-	 * Interpret the line as a Json object and create a Map from it.
+	 * Interpret the line as a Json object and convert it to target type.
 	 *
 	 * @see LineMapper#mapLine(String, int)
 	 */
 	@Override
-	@SuppressWarnings("unchecked")
-	public Map mapLine(String line, int lineNumber) throws Exception {
-		return this.jsonMapper.readValue(line, Map.class);
+	public T mapLine(String line, int lineNumber) throws Exception {
+		return this.jsonMapper.readValue(line, type);
 	}
 
 }
diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/infrastructure/item/file/mapping/JsonLineMapperTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/infrastructure/item/file/mapping/JsonLineMapperTests.java
index 062bc896a0..8a0e42b636 100644
--- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/infrastructure/item/file/mapping/JsonLineMapperTests.java
+++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/infrastructure/item/file/mapping/JsonLineMapperTests.java
@@ -19,31 +19,51 @@
 
 import tools.jackson.core.exc.UnexpectedEndOfInputException;
 import org.junit.jupiter.api.Test;
+import tools.jackson.core.type.TypeReference;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 
 class JsonLineMapperTests {
 
-	private final JsonLineMapper mapper = new JsonLineMapper();
+	private final JsonLineMapper> mapJsonLineMapper = new JsonLineMapper<>(new TypeReference<>() {
+	});
+
+	private final JsonLineMapper userJsonLineMapper = new JsonLineMapper<>(User.class);
 
 	@Test
 	void testMapLine() throws Exception {
-		Map map = mapper.mapLine("{\"foo\": 1}", 1);
+		Map map = mapJsonLineMapper.mapLine("{\"foo\": 1}", 1);
 		assertEquals(1, map.get("foo"));
 	}
 
 	@SuppressWarnings("unchecked")
 	@Test
 	void testMapNested() throws Exception {
-		Map map = mapper.mapLine("{\"foo\": 1, \"bar\" : {\"foo\": 2}}", 1);
+		Map map = mapJsonLineMapper.mapLine("{\"foo\": 1, \"bar\" : {\"foo\": 2}}", 1);
 		assertEquals(1, map.get("foo"));
 		assertEquals(2, ((Map) map.get("bar")).get("foo"));
 	}
 
 	@Test
 	void testMappingError() {
-		assertThrows(UnexpectedEndOfInputException.class, () -> mapper.mapLine("{\"foo\": 1", 1));
+		assertThrows(UnexpectedEndOfInputException.class, () -> mapJsonLineMapper.mapLine("{\"foo\": 1", 1));
+	}
+
+	@Test
+	void testMapLineToDomainType() throws Exception {
+		User user = userJsonLineMapper.mapLine("""
+				{"name":"foo","email":"bar@example.com","introduction":"I'm\\npowerful\\nman"}""", 1);
+		assertEquals("foo", user.name());
+		assertEquals("bar@example.com", user.email());
+		assertEquals("""
+				I'm
+				powerful
+				man""", user.introduction());
+	}
+
+	record User(String name, String email, String introduction) {
+
 	}
 
 }