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.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.node.ArrayNode;
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 java.time.*;
import java.util.*;
@Component
public class FlowableExcelMapper {
protected static ObjectMapper objectMapper = new ObjectMapper();
protected static JavaTimeModule javaTimeModule = new JavaTimeModule();
protected static FlowableExcelParser flowableExcelParser = new FlowableExcelParser();
public FlowableExcelMapper() {
objectMapper.registerModule(javaTimeModule);
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
}
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();
for (LinkedHashMap<String, Object> row : rowList) {
JsonNode jsonNode = null;
Set<Map.Entry<String, Object>> entries = row.entrySet();
for (Map.Entry<String, Object> entry : entries) {
jsonNode = addNode(jsonNode, entry.getKey(), entry.getValue());
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;
ArrayList<Object> row = rawList.get(rowNum);
for (int cellNum = 0; cellNum < row.size(); cellNum++) {
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;
}
public JsonNode addNode(JsonNode parent, String path, Object value) {
objectMapper = new ObjectMapper();
protected JsonNode addNode(JsonNode parent, String path, Object value) {
if (parent == null) {
parent = objectMapper.createObjectNode();
}
@@ -87,7 +121,7 @@ public class FlowableExcelMapper {
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 (char c : delimiters.toCharArray()) {
if (str.charAt(i) == c) {
@@ -8,56 +8,63 @@ import org.springframework.stereotype.Component;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
@Component
public class FlowableExcelParser {
public ArrayList<LinkedHashMap<String, Object>> parseExcel(String resourcePath) {
ArrayList<LinkedHashMap<String, Object>> rowList = new ArrayList<>();
// resourcePath defines the location of the Excel file
// 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)) {
Workbook workbook = new XSSFWorkbook(fileInputStream);
Sheet sheet = workbook.getSheetAt(0);
ArrayList<String> colHeaders = new ArrayList<>();
for (Row row : sheet) {
if (row.getRowNum() == 0) {
// Header row
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;
} else {
colHeaders.add(cell.getStringCellValue());
}
}
continue;
int maxCells = 0;
for (int rowNum = 0; rowNum < sheet.getLastRowNum(); rowNum++) {
Row row = sheet.getRow(rowNum);
if (row == null) {
break;
}
if (maxCells == 0) {
maxCells = row.getLastCellNum();
}
boolean allCellsNull = true;
LinkedHashMap<String, Object> rowMap = new LinkedHashMap<>();
for (int colNum = 0; colNum < colHeaders.size(); colNum++) {
Object content = null;
Cell cell = row.getCell(colNum);
CellValue cellValue = workbook.getCreationHelper().createFormulaEvaluator().evaluate(cell);
if (cellValue != null && cell.getCellType() != CellType.BLANK && cell.getCellType() != CellType._NONE) {
switch (cellValue.getCellType()) {
case STRING : content = cellValue.getStringValue(); break;
case BOOLEAN : content = cellValue.getBooleanValue(); break;
case NUMERIC : content = cellValue.getNumberValue(); break;
ArrayList<Object> cells = new ArrayList<>();
for (int cellNum = 0; cellNum < maxCells; cellNum++) {
Cell cell = row.getCell(cellNum);
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;
}
allCellsNull = false;
}
rowMap.put(colHeaders.get(colNum), content);
CellValue cellValue = workbook.getCreationHelper().createFormulaEvaluator().evaluate(cell);
Object content = null;
if (cellValue != null) {
CellType cellType = cell.getCellType();
if (cellType != CellType.BLANK && cellType != CellType._NONE) {
switch (cellValue.getCellType()) {
case STRING : content = cellValue.getStringValue(); break;
case BOOLEAN : content = cellValue.getBooleanValue(); break;
case NUMERIC : content = cellValue.getNumberValue(); break;
}
allCellsNull = false;
}
}
cells.add(content);
}
if (allCellsNull) {
// Take all rows until the first empty row
return rowList;
return rows;
}
rowList.add(rowMap);
rows.add(cells);
}
} catch (IOException 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 FlowableJsonParser flowableJsonParser = new FlowableJsonParser();
private static final FlowableExcelParser flowableExcelParser = new FlowableExcelParser();
private static final FlowableExcelMapper flowableExcelMapper = new FlowableExcelMapper();
public static void main(String[] args) {
SpringApplication.run(ParserApp.class, args);
@@ -28,9 +29,12 @@ public class ParserApp {
throw new RuntimeException(e);
}
ClassLoader classLoader = ParserApp.class.getClassLoader();
URL resourceUrl = classLoader.getResource("flowableExcelParserExampleData.xlsx");
URL resourceUrl = ParserApp.class.getClassLoader().getResource("flowableExcelParserExampleData.xlsx");
assert resourceUrl != null;
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
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) {
ClassLoader classLoader = FlowableExcelMapperTest.class.getClassLoader();
URL resourceUrl = classLoader.getResource(path);
@@ -44,7 +30,7 @@ class FlowableExcelMapperTest {
@Test
public void excelToJsonNodeTest() {
JsonNode node = flowableExcelMapper.excelToJsonNode(getResourcePath("flowableExcelMapperTestData1.xlsx"));
JsonNode node = flowableExcelMapper.excelToJsonNode(getResourcePath("flowableExcelMapperTestData.xlsx"));
logger.info("node2={}{}", System.lineSeparator(), node.toPrettyString());
}
}
@@ -20,26 +20,18 @@ class FlowableExcelParserTest {
@Autowired
private FlowableExcelParser flowableExcelParser;
private Stream<Arguments> GetArgumentsFromExcel(String path) {
ArrayList<Arguments> argumentList = new ArrayList<>();
protected String getResourcePath(String path) {
ClassLoader classLoader = FlowableExcelParserTest.class.getClassLoader();
URL resourceUrl = classLoader.getResource(path);
Assertions.assertThat(resourceUrl).isNotNull();
ArrayList<LinkedHashMap<String, Object>> rowList = flowableExcelParser.parseExcel(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());
}
}
argumentList.add(Arguments.of(inputMap, outputMap));
return resourceUrl.getPath();
}
protected Stream<Arguments> GetArgumentsFromExcel(String path) {
ArrayList<Arguments> argumentList = new ArrayList<>();
ArrayList<ArrayList<Object>> rowList = flowableExcelParser.parseExcel(getResourcePath(path));
for (ArrayList<Object> row : rowList) {
argumentList.add(Arguments.of(row));
}
return argumentList.stream();
}
@@ -51,18 +43,12 @@ class FlowableExcelParserTest {
@ParameterizedTest
@MethodSource("booleanExcelParserTestDataProvider")
void booleanExcelParserTest(LinkedHashMap<String, Object> inputMap, LinkedHashMap<String, Object> outputMap) {
for (Map.Entry<String, Object> entry : outputMap.entrySet()) {
String key = entry.getKey();
if (key.equals("output1")) {
Boolean val = (Boolean)inputMap.get("input1") || (Boolean)inputMap.get("input2") || (Boolean)inputMap.get("input3");
Assertions.assertThat(entry.getValue()).isEqualTo(val);
}
if (key.equals("output2")) {
Boolean val = (Boolean)inputMap.get("input1") && (Boolean)inputMap.get("input2") && (Boolean)inputMap.get("input3");
Assertions.assertThat(entry.getValue()).isEqualTo(val);
}
}
void booleanExcelParserTest(ArrayList<Object> row) {
Boolean valOr = (Boolean) row.get(0) || (Boolean) row.get(1) || (Boolean) row.get(2);
Assertions.assertThat(row.get(3)).isEqualTo(valOr);
Boolean valAnd = (Boolean) row.get(0) && (Boolean) row.get(1) && (Boolean) row.get(2);
Assertions.assertThat(row.get(4)).isEqualTo(valAnd);
}
@NotNull
@@ -72,22 +58,15 @@ class FlowableExcelParserTest {
@ParameterizedTest
@MethodSource("stringExcelParserTestDataProvider")
void stringExcelParserTest(LinkedHashMap<String, Object> inputMap, LinkedHashMap<String, Object> outputMap) {
for (Map.Entry<String, Object> entry : outputMap.entrySet()) {
String key = entry.getKey();
if (key.equals("output1")) {
String val = inputMap.get("input1").toString() + inputMap.get("input2").toString();
Assertions.assertThat(entry.getValue()).isEqualTo(val);
}
if (key.equals("output2")) {
String val = inputMap.get("input2").toString() + inputMap.get("input3").toString();
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);
}
}
void stringExcelParserTest(ArrayList<Object> row) {
String val12 = row.get(0).toString() + row.get(1).toString();
Assertions.assertThat(row.get(3)).isEqualTo(val12);
String val23 = row.get(1).toString() + row.get(2).toString();
Assertions.assertThat(row.get(4)).isEqualTo(val23);
String val123 = row.get(0).toString() + row.get(1).toString() + row.get(2).toString();
Assertions.assertThat(row.get(5)).isEqualTo(val123);
}
@NotNull
@@ -97,21 +76,14 @@ class FlowableExcelParserTest {
@ParameterizedTest
@MethodSource("numberExcelParserTestDataProvider")
void numberExcelParserTest(LinkedHashMap<String, Object> inputMap, LinkedHashMap<String, Object> outputMap) {
for (Map.Entry<String, Object> entry : outputMap.entrySet()) {
String key = entry.getKey();
if (key.equals("output1")) {
Double val = (Double)inputMap.get("input1") + (Double)inputMap.get("input2");
Assertions.assertThat(entry.getValue()).isEqualTo(val);
}
if (key.equals("output2")) {
Double val = (Double)inputMap.get("input2") + (Double)inputMap.get("input3");
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);
}
}
void numberExcelParserTest(ArrayList<Object> row) {
Double val12 = (Double) row.get(0) + (Double) row.get(1);
Assertions.assertThat(row.get(3)).isEqualTo(val12);
Double val23 = (Double) row.get(1) + (Double) row.get(2);
Assertions.assertThat(row.get(4)).isEqualTo(val23);
Double val123 = (Double) row.get(0) + (Double) row.get(1) + (Double) row.get(2);
Assertions.assertThat(row.get(5)).isEqualTo(val123);
}
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.