Added types to FlowableExcelMapper

This commit is contained in:
Andreas Isler
2025-11-12 19:01:10 +01:00
parent 8279a73c0d
commit b5c6b38a3d
20 changed files with 125 additions and 122 deletions
@@ -2,35 +2,69 @@ package com.example.parser;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.apache.poi.ss.usermodel.DateUtil;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.time.*;
import java.util.*; import java.util.*;
@Component @Component
public class FlowableExcelMapper { public class FlowableExcelMapper {
protected static ObjectMapper objectMapper = new ObjectMapper(); protected static ObjectMapper objectMapper = new ObjectMapper();
protected static JavaTimeModule javaTimeModule = new JavaTimeModule();
protected static FlowableExcelParser flowableExcelParser = new FlowableExcelParser(); protected static FlowableExcelParser flowableExcelParser = new FlowableExcelParser();
public FlowableExcelMapper() {
objectMapper.registerModule(javaTimeModule);
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
}
public ArrayNode excelToJsonNode(String excelPath) { public ArrayNode excelToJsonNode(String excelPath) {
ArrayList<LinkedHashMap<String, Object>> rowList = flowableExcelParser.parseExcel(excelPath); ArrayList<ArrayList<Object>> rawList = flowableExcelParser.parseExcel(excelPath);
ArrayList<Object> paths = new ArrayList<>();
ArrayList<Object> types = new ArrayList<>();
ArrayNode arrayNode = objectMapper.createArrayNode(); ArrayNode arrayNode = objectMapper.createArrayNode();
for (LinkedHashMap<String, Object> row : rowList) { for (int rowNum = 0; rowNum < rawList.size(); rowNum++) {
if (rowNum == 0) {
// The 1st row contains the paths
paths.addAll(rawList.get(rowNum));
} else if (rowNum == 1) {
// The 1st row contains the types of the variables
types.addAll(rawList.get(rowNum));
} else {
// All other rows contain the values of the variables
JsonNode jsonNode = null; JsonNode jsonNode = null;
Set<Map.Entry<String, Object>> entries = row.entrySet(); ArrayList<Object> row = rawList.get(rowNum);
for (Map.Entry<String, Object> entry : entries) { for (int cellNum = 0; cellNum < row.size(); cellNum++) {
jsonNode = addNode(jsonNode, entry.getKey(), entry.getValue()); Object content = row.get(cellNum);
String type = types.get(cellNum).toString();
switch (type) {
case "String": content = content.toString(); break;
case "Boolean": content = Boolean.parseBoolean(content.toString()); break;
case "Integer": content = (int) Double.parseDouble(content.toString()); break;
case "Double": content = Double.parseDouble(content.toString()); break;
case "Date":
// Excel considers dates as local dates, but we want them as UTC
Date dateTime = DateUtil.getJavaDate(Double.parseDouble(content.toString()));
content = dateTime.toInstant().plusSeconds(ZonedDateTime.now().getOffset().getTotalSeconds());
break;
default: break;
}
jsonNode = addNode(jsonNode, paths.get(cellNum).toString(), content);
} }
arrayNode.add(jsonNode); arrayNode.add(jsonNode);
} }
}
return arrayNode; return arrayNode;
} }
public JsonNode addNode(JsonNode parent, String path, Object value) { protected JsonNode addNode(JsonNode parent, String path, Object value) {
objectMapper = new ObjectMapper();
if (parent == null) { if (parent == null) {
parent = objectMapper.createObjectNode(); parent = objectMapper.createObjectNode();
} }
@@ -87,7 +121,7 @@ public class FlowableExcelMapper {
return parent; return parent;
} }
public static int indexOfFirstDelimiter(String str, String delimiters) { protected int indexOfFirstDelimiter(String str, String delimiters) {
for (int i = 0; i < str.length(); i++) { for (int i = 0; i < str.length(); i++) {
for (char c : delimiters.toCharArray()) { for (char c : delimiters.toCharArray()) {
if (str.charAt(i) == c) { if (str.charAt(i) == c) {
@@ -8,38 +8,44 @@ import org.springframework.stereotype.Component;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedHashMap;
@Component @Component
public class FlowableExcelParser { public class FlowableExcelParser {
public ArrayList<LinkedHashMap<String, Object>> parseExcel(String resourcePath) { // resourcePath defines the location of the Excel file
ArrayList<LinkedHashMap<String, Object>> rowList = new ArrayList<>(); // Return all cells in the 1st sheet which have values as objects (String, boolean or double)
// The 1st row defines the amount of cells per row
// Parsing the sheet stops after the 1st empty row
public ArrayList<ArrayList<Object>> parseExcel(String resourcePath) {
ArrayList<ArrayList<Object>> rows = new ArrayList<>();
try (FileInputStream fileInputStream = new FileInputStream(resourcePath)) { try (FileInputStream fileInputStream = new FileInputStream(resourcePath)) {
Workbook workbook = new XSSFWorkbook(fileInputStream); Workbook workbook = new XSSFWorkbook(fileInputStream);
Sheet sheet = workbook.getSheetAt(0); Sheet sheet = workbook.getSheetAt(0);
ArrayList<String> colHeaders = new ArrayList<>(); int maxCells = 0;
for (Row row : sheet) { for (int rowNum = 0; rowNum < sheet.getLastRowNum(); rowNum++) {
if (row.getRowNum() == 0) { Row row = sheet.getRow(rowNum);
// Header row if (row == null) {
for (int colNum = 0; colNum < row.getLastCellNum(); colNum++) {
Cell cell = row.getCell(colNum);
if (cell == null || cell.getCellType() == CellType.BLANK || cell.getCellType() == CellType._NONE) {
// Take all columns until the first empty header cell
break; break;
} else {
colHeaders.add(cell.getStringCellValue());
} }
} if (maxCells == 0) {
continue; maxCells = row.getLastCellNum();
} }
boolean allCellsNull = true; boolean allCellsNull = true;
LinkedHashMap<String, Object> rowMap = new LinkedHashMap<>(); ArrayList<Object> cells = new ArrayList<>();
for (int colNum = 0; colNum < colHeaders.size(); colNum++) { for (int cellNum = 0; cellNum < maxCells; cellNum++) {
Object content = null; Cell cell = row.getCell(cellNum);
Cell cell = row.getCell(colNum); if(rowNum == 0) {
if (cell == null || cell.getCellType() == CellType.BLANK || cell.getCellType() == CellType._NONE) {
// Count cells of 1st row until the first empty cell
maxCells = cellNum;
break;
}
}
CellValue cellValue = workbook.getCreationHelper().createFormulaEvaluator().evaluate(cell); CellValue cellValue = workbook.getCreationHelper().createFormulaEvaluator().evaluate(cell);
if (cellValue != null && cell.getCellType() != CellType.BLANK && cell.getCellType() != CellType._NONE) { Object content = null;
if (cellValue != null) {
CellType cellType = cell.getCellType();
if (cellType != CellType.BLANK && cellType != CellType._NONE) {
switch (cellValue.getCellType()) { switch (cellValue.getCellType()) {
case STRING : content = cellValue.getStringValue(); break; case STRING : content = cellValue.getStringValue(); break;
case BOOLEAN : content = cellValue.getBooleanValue(); break; case BOOLEAN : content = cellValue.getBooleanValue(); break;
@@ -47,17 +53,18 @@ public class FlowableExcelParser {
} }
allCellsNull = false; allCellsNull = false;
} }
rowMap.put(colHeaders.get(colNum), content); }
cells.add(content);
} }
if (allCellsNull) { if (allCellsNull) {
// Take all rows until the first empty row // Take all rows until the first empty row
return rowList; return rows;
} }
rowList.add(rowMap); rows.add(cells);
} }
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
return rowList; return rows;
} }
} }
@@ -18,6 +18,7 @@ public class ParserApp {
private static final ObjectMapper objectMapper = new ObjectMapper(); private static final ObjectMapper objectMapper = new ObjectMapper();
private static final FlowableJsonParser flowableJsonParser = new FlowableJsonParser(); private static final FlowableJsonParser flowableJsonParser = new FlowableJsonParser();
private static final FlowableExcelParser flowableExcelParser = new FlowableExcelParser(); private static final FlowableExcelParser flowableExcelParser = new FlowableExcelParser();
private static final FlowableExcelMapper flowableExcelMapper = new FlowableExcelMapper();
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(ParserApp.class, args); SpringApplication.run(ParserApp.class, args);
@@ -28,9 +29,12 @@ public class ParserApp {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
ClassLoader classLoader = ParserApp.class.getClassLoader(); URL resourceUrl = ParserApp.class.getClassLoader().getResource("flowableExcelParserExampleData.xlsx");
URL resourceUrl = classLoader.getResource("flowableExcelParserExampleData.xlsx");
assert resourceUrl != null; assert resourceUrl != null;
logger.info("parsed excel={}", flowableExcelParser.parseExcel(resourceUrl.getPath())); logger.info("parsed excel={}", flowableExcelParser.parseExcel(resourceUrl.getPath()));
resourceUrl = ParserApp.class.getClassLoader().getResource("flowableExcelMapperExampleData.xlsx");
assert resourceUrl != null;
logger.info("mapped excel={}", flowableExcelMapper.excelToJsonNode(resourceUrl.getPath()));
} }
} }
@@ -18,23 +18,9 @@ class FlowableExcelMapperTest {
@Autowired @Autowired
private FlowableExcelMapper flowableExcelMapper; private FlowableExcelMapper flowableExcelMapper;
private static final Logger logger = LoggerFactory.getLogger(ParserApp.class); private static final Logger logger = LoggerFactory.getLogger(FlowableExcelMapperTest.class);
@Test
public void addNodeTest() {
JsonNode node = null;
node = flowableExcelMapper.addNode(node, "3*0*2", "String1");
node = flowableExcelMapper.addNode(node, "3*0*3", "String1");
node = flowableExcelMapper.addNode(node, "3*1.1", 234);
node = flowableExcelMapper.addNode(node, "3*1.2", 234);
node = flowableExcelMapper.addNode(node, "3*2", false);
node = flowableExcelMapper.addNode(node, "4.5", "String1");
node = flowableExcelMapper.addNode(node, "4.6", 234);
node = flowableExcelMapper.addNode(node, "4.7", false);
logger.info("node2={}{}", System.lineSeparator(), node.toPrettyString());
}
protected String getResourcePath(String path) { protected String getResourcePath(String path) {
ClassLoader classLoader = FlowableExcelMapperTest.class.getClassLoader(); ClassLoader classLoader = FlowableExcelMapperTest.class.getClassLoader();
URL resourceUrl = classLoader.getResource(path); URL resourceUrl = classLoader.getResource(path);
@@ -44,7 +30,7 @@ class FlowableExcelMapperTest {
@Test @Test
public void excelToJsonNodeTest() { public void excelToJsonNodeTest() {
JsonNode node = flowableExcelMapper.excelToJsonNode(getResourcePath("flowableExcelMapperTestData1.xlsx")); JsonNode node = flowableExcelMapper.excelToJsonNode(getResourcePath("flowableExcelMapperTestData.xlsx"));
logger.info("node2={}{}", System.lineSeparator(), node.toPrettyString()); logger.info("node2={}{}", System.lineSeparator(), node.toPrettyString());
} }
} }
@@ -20,26 +20,18 @@ class FlowableExcelParserTest {
@Autowired @Autowired
private FlowableExcelParser flowableExcelParser; private FlowableExcelParser flowableExcelParser;
private Stream<Arguments> GetArgumentsFromExcel(String path) { protected String getResourcePath(String path) {
ArrayList<Arguments> argumentList = new ArrayList<>();
ClassLoader classLoader = FlowableExcelParserTest.class.getClassLoader(); ClassLoader classLoader = FlowableExcelParserTest.class.getClassLoader();
URL resourceUrl = classLoader.getResource(path); URL resourceUrl = classLoader.getResource(path);
Assertions.assertThat(resourceUrl).isNotNull(); Assertions.assertThat(resourceUrl).isNotNull();
ArrayList<LinkedHashMap<String, Object>> rowList = flowableExcelParser.parseExcel(resourceUrl.getPath()); return resourceUrl.getPath();
Assertions.assertThat(rowList).isNotNull();
for (LinkedHashMap<String, Object> row : rowList) {
LinkedHashMap<String, Object> inputMap = new LinkedHashMap<>();
LinkedHashMap<String, Object> outputMap = new LinkedHashMap<>();
for (Map.Entry<String, Object> entry : row.entrySet()) {
String key = entry.getKey();
if (List.of("input1", "input2", "input3").contains(key)) {
inputMap.put(key, entry.getValue());
} }
if (List.of("output1", "output2").contains(key)) {
outputMap.put(key, entry.getValue()); protected Stream<Arguments> GetArgumentsFromExcel(String path) {
} ArrayList<Arguments> argumentList = new ArrayList<>();
} ArrayList<ArrayList<Object>> rowList = flowableExcelParser.parseExcel(getResourcePath(path));
argumentList.add(Arguments.of(inputMap, outputMap)); for (ArrayList<Object> row : rowList) {
argumentList.add(Arguments.of(row));
} }
return argumentList.stream(); return argumentList.stream();
} }
@@ -51,18 +43,12 @@ class FlowableExcelParserTest {
@ParameterizedTest @ParameterizedTest
@MethodSource("booleanExcelParserTestDataProvider") @MethodSource("booleanExcelParserTestDataProvider")
void booleanExcelParserTest(LinkedHashMap<String, Object> inputMap, LinkedHashMap<String, Object> outputMap) { void booleanExcelParserTest(ArrayList<Object> row) {
for (Map.Entry<String, Object> entry : outputMap.entrySet()) { Boolean valOr = (Boolean) row.get(0) || (Boolean) row.get(1) || (Boolean) row.get(2);
String key = entry.getKey(); Assertions.assertThat(row.get(3)).isEqualTo(valOr);
if (key.equals("output1")) {
Boolean val = (Boolean)inputMap.get("input1") || (Boolean)inputMap.get("input2") || (Boolean)inputMap.get("input3"); Boolean valAnd = (Boolean) row.get(0) && (Boolean) row.get(1) && (Boolean) row.get(2);
Assertions.assertThat(entry.getValue()).isEqualTo(val); Assertions.assertThat(row.get(4)).isEqualTo(valAnd);
}
if (key.equals("output2")) {
Boolean val = (Boolean)inputMap.get("input1") && (Boolean)inputMap.get("input2") && (Boolean)inputMap.get("input3");
Assertions.assertThat(entry.getValue()).isEqualTo(val);
}
}
} }
@NotNull @NotNull
@@ -72,22 +58,15 @@ class FlowableExcelParserTest {
@ParameterizedTest @ParameterizedTest
@MethodSource("stringExcelParserTestDataProvider") @MethodSource("stringExcelParserTestDataProvider")
void stringExcelParserTest(LinkedHashMap<String, Object> inputMap, LinkedHashMap<String, Object> outputMap) { void stringExcelParserTest(ArrayList<Object> row) {
for (Map.Entry<String, Object> entry : outputMap.entrySet()) { String val12 = row.get(0).toString() + row.get(1).toString();
String key = entry.getKey(); Assertions.assertThat(row.get(3)).isEqualTo(val12);
if (key.equals("output1")) {
String val = inputMap.get("input1").toString() + inputMap.get("input2").toString(); String val23 = row.get(1).toString() + row.get(2).toString();
Assertions.assertThat(entry.getValue()).isEqualTo(val); Assertions.assertThat(row.get(4)).isEqualTo(val23);
}
if (key.equals("output2")) { String val123 = row.get(0).toString() + row.get(1).toString() + row.get(2).toString();
String val = inputMap.get("input2").toString() + inputMap.get("input3").toString(); Assertions.assertThat(row.get(5)).isEqualTo(val123);
Assertions.assertThat(entry.getValue()).isEqualTo(val);
}
if (key.equals("output3")) {
String val = inputMap.get("input1").toString() + inputMap.get("input2").toString() + inputMap.get("input3").toString();
Assertions.assertThat(entry.getValue()).isEqualTo(val);
}
}
} }
@NotNull @NotNull
@@ -97,21 +76,14 @@ class FlowableExcelParserTest {
@ParameterizedTest @ParameterizedTest
@MethodSource("numberExcelParserTestDataProvider") @MethodSource("numberExcelParserTestDataProvider")
void numberExcelParserTest(LinkedHashMap<String, Object> inputMap, LinkedHashMap<String, Object> outputMap) { void numberExcelParserTest(ArrayList<Object> row) {
for (Map.Entry<String, Object> entry : outputMap.entrySet()) { Double val12 = (Double) row.get(0) + (Double) row.get(1);
String key = entry.getKey(); Assertions.assertThat(row.get(3)).isEqualTo(val12);
if (key.equals("output1")) {
Double val = (Double)inputMap.get("input1") + (Double)inputMap.get("input2"); Double val23 = (Double) row.get(1) + (Double) row.get(2);
Assertions.assertThat(entry.getValue()).isEqualTo(val); Assertions.assertThat(row.get(4)).isEqualTo(val23);
}
if (key.equals("output2")) { Double val123 = (Double) row.get(0) + (Double) row.get(1) + (Double) row.get(2);
Double val = (Double)inputMap.get("input2") + (Double)inputMap.get("input3"); Assertions.assertThat(row.get(5)).isEqualTo(val123);
Assertions.assertThat(entry.getValue()).isEqualTo(val);
}
if (key.equals("output3")) {
Double val = (Double)inputMap.get("input1") + (Double)inputMap.get("input2") + (Double)inputMap.get("input3");
Assertions.assertThat(entry.getValue()).isEqualTo(val);
}
}
} }
} }
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.