Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
* <code>name:value</code> pairs separated by commas. Whitespace is ignored, e.g.
*
* <pre>
Expand All @@ -38,37 +40,96 @@
*
* @author Dave Syer
* @author Mahmoud Ben Hassine
* @author Yanming Zhou
*
*/
public class JsonLineMapper implements LineMapper<Map<String, Object>> {
public class JsonLineMapper<T> implements LineMapper<T> {

private final JsonMapper jsonMapper;

private final TypeReference<T> type;

/**
* Create a new {@link JsonLineMapper} with a default {@link JsonMapper}.
* Create a new {@link JsonLineMapper} with a default {@link JsonMapper} and
* {@code Map<String, Object>} target type.
* @deprecated in favor of {@code new JsonLineMapper<Map<String, Object>>(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<String, Object>} target type.
* @param jsonMapper the json mapper to use
* @since 6.0
* @deprecated in favor of {@code new JsonLineMapper<Map<String, Object>>(jsonMapper,
* new TypeReference<>() {})}
*/
@Deprecated(forRemoval = true)
@SuppressWarnings("unchecked")
public JsonLineMapper(JsonMapper jsonMapper) {
this(jsonMapper, (TypeReference<T>) new TypeReference<Map<String, Object>>() {
});
}

/**
* 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<T> 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<T> 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<T> 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<T> 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<String, Object> 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);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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<Map<String, Object>> mapJsonLineMapper = new JsonLineMapper<>(new TypeReference<>() {
});

private final JsonLineMapper<User> userJsonLineMapper = new JsonLineMapper<>(User.class);

@Test
void testMapLine() throws Exception {
Map<String, Object> map = mapper.mapLine("{\"foo\": 1}", 1);
Map<String, Object> map = mapJsonLineMapper.mapLine("{\"foo\": 1}", 1);
assertEquals(1, map.get("foo"));
}

@SuppressWarnings("unchecked")
@Test
void testMapNested() throws Exception {
Map<String, Object> map = mapper.mapLine("{\"foo\": 1, \"bar\" : {\"foo\": 2}}", 1);
Map<String, Object> map = mapJsonLineMapper.mapLine("{\"foo\": 1, \"bar\" : {\"foo\": 2}}", 1);
assertEquals(1, map.get("foo"));
assertEquals(2, ((Map<String, Object>) 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) {

}

}
Loading