Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,14 @@
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeParseException;
import java.util.List;
import java.util.function.Function;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* @author Ivica Cardic
* @author Igor Beslic
*/
public class ConvertUtils {

Expand Down Expand Up @@ -73,46 +76,58 @@ public static <T> T convertValue(Object fromValue, TypeReference<T> toValueTypeR
return objectMapper.convertValue(fromValue, toValueTypeRef);
}

/**
* Converts the string value given with parameter to typed value. Conversion rellies on parse method of Integer,
* Long, Double, LocalDateTime and LocalDate methods. Boolean values are derived from case unsensitive true and
* false variants of value.
*
* @param str String representation of value
* @return value as Integer, Long, Double, LocalDateTime, LocalDate, Boolean, String or null if received method
* argument is null
*/
public static Object convertString(String str) {
try {
return Integer.parseInt(str);
} catch (NumberFormatException e) {
if (logger.isTraceEnabled()) {
logger.trace(e.getMessage(), e);
}
if (str == null) {
return null;
}

try {
return Double.parseDouble(str);
} catch (NumberFormatException e) {
if (logger.isTraceEnabled()) {
logger.trace(e.getMessage(), e);
String trimmedString = str.trim();

Object value = null;

for (Function<String, Object> transformerFunction : parseFunctions) {
try {
value = transformerFunction.apply(trimmedString);
} catch (NumberFormatException | DateTimeParseException exception) {
if (logger.isTraceEnabled()) {
logger.trace(exception.getMessage(), exception);
}

continue;
}
}

if (str.equalsIgnoreCase("true") || str.equalsIgnoreCase("false")) {
return Boolean.parseBoolean(str);
if (value != null) {
return value;
}
}

try {
return LocalDateTime.parse(str);
} catch (DateTimeParseException e) {
if (logger.isTraceEnabled()) {
logger.trace(e.getMessage(), e);
}
if (trimmedString.equalsIgnoreCase("true")) {
return Boolean.TRUE;
}

try {
return LocalDate.parse(str);
} catch (DateTimeParseException e) {
if (logger.isTraceEnabled()) {
logger.trace(e.getMessage(), e);
}
if (trimmedString.equalsIgnoreCase("false")) {
return Boolean.FALSE;
}

return str;
}

private static final List<Function<String, Object>> parseFunctions;

static {
parseFunctions = List.of(
Integer::parseInt, Long::parseLong, Double::parseDouble, LocalDateTime::parse, LocalDate::parse);
}

@SuppressFBWarnings("EI")
public static void setObjectMapper(ObjectMapper objectMapper) {
ConvertUtils.objectMapper = objectMapper;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* Copyright 2025 ByteChef
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.bytechef.commons.util;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;

/**
* @author Igor Beslic
*/
public class ConvertUtilsTest {

@Test
public void testConvertString() {

for (String integerString : List.of("435", "+435", "435 ", " 435 ", " 435")) {
Assertions.assertThat(
ConvertUtils.convertString(integerString))
.isInstanceOf(
Integer.class)
.isEqualTo(
435);
}

for (String integerString : List.of("-125", " -125", "-125 ", " -125 ", " -125")) {
Assertions.assertThat(
ConvertUtils.convertString(integerString))
.isInstanceOf(
Integer.class)
.isEqualTo(
-125);
}

Assertions.assertThat(
ConvertUtils.convertString("452.45"))
.isInstanceOf(
Double.class)
.isEqualTo(
452.45);

for (String booleanString : List.of("TRUE", "true", "TruE", "truE", "trUE")) {
Assertions.assertThat(
ConvertUtils.convertString(booleanString))
.isInstanceOf(
Boolean.class)
.isEqualTo(
Boolean.TRUE);
}

for (String booleanString : List.of("FALSE", "false", "FAlse", "FALse", "falsE")) {
Assertions.assertThat(
ConvertUtils.convertString(booleanString))
.isInstanceOf(
Boolean.class)
.isEqualTo(
Boolean.FALSE);
}

Assertions.assertThat(
ConvertUtils.convertString("2011-12-03T10:15:30"))
.isInstanceOf(
LocalDateTime.class)
.isEqualTo(
LocalDateTime.of(2011, 12, 3, 10, 15, 30, 0));

Assertions.assertThat(
ConvertUtils.convertString("2011-12-03"))
.isInstanceOf(
LocalDate.class)
.isEqualTo(
LocalDate.of(2011, 12, 3));

Assertions.assertThat(
ConvertUtils.convertString("non convertable value"))
.isInstanceOf(
String.class)
.isEqualTo(
"non convertable value");

Assertions.assertThat(
ConvertUtils.convertString(null))
.isNull();
}

}
1 change: 1 addition & 0 deletions server/libs/modules/components/csv-file/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ version="1.0"
dependencies {
implementation("org.apache.commons:commons-lang3")
implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-csv")
implementation(project(":server:libs:core:commons:commons-util"))

testImplementation(project(":server:libs:atlas:atlas-execution:atlas-execution-api"))
testImplementation(project(":server:libs:atlas:atlas-file-storage:atlas-file-storage-api"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import static com.bytechef.component.csv.file.constant.CsvFileConstants.PAGE_SIZE;
import static com.bytechef.component.csv.file.constant.CsvFileConstants.READ_AS_STRING;

import com.bytechef.commons.util.ConvertUtils;
import com.bytechef.component.definition.Parameters;
import com.fasterxml.jackson.databind.MappingIterator;
import com.fasterxml.jackson.dataformat.csv.CsvParser;
Expand All @@ -34,11 +35,11 @@
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;

/**
* @author Ivica Cardic
* @author Igor Beslic
*/
public class CsvFileReadUtils {

Expand Down Expand Up @@ -75,12 +76,22 @@ public static Map<String, Object> getHeaderRow(

Map<String, Object> map = new LinkedHashMap<>();

int currColumn = 1;

for (Map.Entry<?, ?> entry : row.entrySet()) {
String strippedString = strip((String) entry.getKey(), enclosingCharacter);

if (strippedString.isEmpty()) {
strippedString = "column_" + currColumn;
}

map.put(
strip((String) entry.getKey(), enclosingCharacter),
strippedString,
processValue(
(String) entry.getValue(), enclosingCharacter, configuration.includeEmptyCells(),
configuration.readAsString()));

currColumn++;
}

return map;
Expand Down Expand Up @@ -154,7 +165,7 @@ public static Object processValue(
if (readAsString) {
value = valueString;
} else {
value = valueOf(valueString);
value = ConvertUtils.convertString(valueString);
}
}

Expand All @@ -169,40 +180,4 @@ public static String strip(String valueString, char enclosingCharacter) {
return StringUtils.removeEnd(valueString, String.valueOf(enclosingCharacter));
}

@SuppressWarnings("PMD.EmptyCatchBlock")
public static Object valueOf(String string) {
Object value = null;

try {
value = Integer.parseInt(string);
} catch (NumberFormatException nfe) {
// ignore
}

if (value == null) {
try {
value = Long.parseLong(string);
} catch (NumberFormatException nfe) {
// ignore
}
}

if (value == null) {
try {
value = Double.parseDouble(string);
} catch (NumberFormatException nfe) {
// ignore
}
}

if (value == null) {
value = BooleanUtils.toBooleanObject(string);
}

if (value == null) {
value = string;
}

return value;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import java.util.Base64;
import java.util.List;
import java.util.Map;
import org.assertj.core.api.Assertions;
import org.assertj.core.util.Files;
import org.json.JSONArray;
import org.json.JSONException;
Expand All @@ -43,6 +44,7 @@

/**
* @author Ivica Cardic
* @author Igor Beslic
*/
@ComponentIntTest
class CsvFileComponentHandlerIntTest {
Expand Down Expand Up @@ -156,6 +158,27 @@ void testReadHeaderAndDelimiter() throws JSONException {
true);
}

@Test
void testReadHeaderAndDelimiterAdvanced() throws JSONException {
File sampleFile = getFile("sample_header_semicolon_delimiter.csv");

Job job = componentJobTestExecutor.execute(
ENCODER.encodeToString("csv-file_v1_read".getBytes(StandardCharsets.UTF_8)),
Map.of(
FILE_ENTRY,
tempFileStorage.storeFileContent(
sampleFile.getAbsolutePath(), Files.contentOf(sampleFile, StandardCharsets.UTF_8)),
INCLUDE_EMPTY_CELLS, true, DELIMITER, ";",
HEADER_ROW, true));

assertThat(job.getStatus()).isEqualTo(Job.Status.COMPLETED);

Map<String, ?> outputs = taskFileStorage.readJobOutputs(job.getOutputs());

Assertions.assertThat(((Map<?, ?>) ((List<?>) outputs.get("readCsvFile")).getFirst()).size())
.isEqualTo(11);
}

// @Test
public void testWrite() throws JSONException {
Job job = componentJobTestExecutor.execute(
Expand Down
Loading
Loading