OpenCSV로 쌍따옴표(Double quote)와 개행(Carriage return)이 포함된 CSV 파싱하기
포스팅 제목부터 범상치 않은데요,
CSV파일을 다루면서 파싱하는데 어려움이 있던 부분을 포스팅합니다.
Java OpenCSV 라이브러리를 사용했구요,
Gradle에서 아래와 같이 추가하면 됩니다.
implementation 'com.opencsv:opencsv:5.3'
문제의 CSV본문은 사람이 입력한 자연어 그대로 들어가기에 파싱되는 필드내에 콤마(,)와 쌍따옴표("), 개행(\n)까지 들어있을 수 있습니다.
예시는 아래와 같습니다.
"1","김철수","일반회원","교정진료를 ""희망""합니다, 잘부탁드립니다."
"2","홍길동","일반회원","스케일링을
희망합니다, 1달에 2번"
예시를 보면 쌍따옴표로 묶인 필드 내에 콤마와 개행, 심지어 쌍따옴표까지 혼합해서 나온 경우를 보실 수 있습니다.
상당히 까다로워 보이지만, 저 예시는 CSV포맷을 지키고 있습니다.
즉, 파싱가능한 문장이란거죠.
코드를 보겠습니다.
private boolean isArmsETLSuccess(String csvFilePath) {
boolean ret = true;
List<Arms> armsList = null;
try (BufferedReader br = Files.newBufferedReader(Path.of(csvFilePath), StandardCharsets.UTF_8)) {
ColumnPositionMappingStrategy<Arms> strategy = new ColumnPositionMappingStrategy<>();
String[] columns = Arms.getFieldNames(1, 30).toArray(String []:: new);
strategy.setColumnMapping(columns);
strategy.setType(Arms.class);
CsvToBean<Arms> csvToBean = new CsvToBeanBuilder<Arms>(br)
.withMappingStrategy(strategy)
.withSkipLines(1)
.withStrictQuotes(true)
.withEscapeChar('\n')
.withIgnoreLeadingWhiteSpace(true)
.build();
csvToBean.setThrowExceptions(false);
armsList = csvToBean.parse();
List<CsvException> exceptionList = csvToBean.getCapturedExceptions();
if(!exceptionList.isEmpty()){
ret = false;
exceptionList.forEach(e -> {
log.error(String.format("dirPath:%s, CSV parse error:", csvFilePath), e);
}
for(Arms arms: armsList){
}
armsRepository.saveAll(armsList);
} catch (IOException | java.lang.RuntimeException e) {
ret = false;
log.error(String.format("dirPath:%s, CSV parse error:", csvFilePath), e);
}
return ret;
}
예시의 코드는 CSV파일을 파싱해서 Arms라는 객체로 저장하는 예시입니다.
손으로 일일이 짜려면 코드가 복잡해지지만, OpenCSV에선 CsvToBean 클래스를 지원해줘서
용자가 정의한 DTO로 변환이 손쉽게 됩니다.
파싱하려는 예시가 쌍따옴표로 묶여있기 때문에
.withStrictQuotes(true)
로 필드가 묶여있다는걸 명시하고,
.withEscapeChar('\n')
로 파싱된 필드내에 개행이 있는 경우, 새로운 레코드가 아님을 예외처리 합니다.
.withIgnoreLeadingWhiteSpace(true)
필드에 공백이 있을 수 있으므로 공백도 예외처리 해둡니다.
만약, 파싱중에 에러를 핸들링하고 싶다면,
parse 메서드를 호출하기 전에
csvToBean.setThrowExceptions(false);
를 설정해서 exception이 발생해도 파싱을 멈추지않고 모아두어 아래처럼 나중에 처리할 수 있습니다.
List<CsvException> exceptionList = csvToBean.getCapturedExceptions();
if(!exceptionList.isEmpty()){
ret = false;
exceptionList.forEach(e -> {
saveVocExceptionLog(
csvFilePath,
e.getLineNumber(),
String.join(",", e.getLine()),
ARMS,
ExceptionUtils.getStackTrace(e));
});
}
이상으로 포스팅 마치겠습니다!