# 엑셀에서 CSV 파일 인코딩이 깨질 때 (UTF-8 BOM)

## TL;DR

* 맥이나 리눅스 또는 다른 텍스트 에디터로 CSV 파일을 열었을 때 깨지지 않던 UTF-8 인코딩이 엑셀에서는 깨지는 경우 BOM 을 확인할 것.
* 같은 csv 파일인 데 문서 비교 시 같지 않다고 나오는 경우 BOM 을 확인할 것.
* 보이지 않지만 뭔가 하고 있는 녀석, BOM.
* BOM 없어도 다른 애들은 잘 열어주는데 엑셀.. 넌 왜…

## 문제 상황

UTF-8 인코딩으로 저장된 CSV 파일을 Excel 로 열었을 때, 한글이 모두 깨져버리는 문제가 발생했다.

QA 팀에서 예외 이슈를 발급 해주셨는데… 개발팀에서는 테스트 케이스가 재현이 되지 않아서 원인을 파악할 수 없었다.

왜 테스트 케이스가 재현이 안되었던 걸까?

## 문제 분석

당황스러웠던 점은 잘 열린다는 csv 파일을 슬랙으로 전송받아서 맥에서 열면 잘 열리는데, 다시 윈도우 사용자(테스터)에게 보내주면 한글이 깨져버리는 것이었다.

삽질 과정을 거친 결과 이유는 알 수 없지만 슬랙으로 UTF-8(BOM) CSV 파일을 전송하면 별도의 내부 변환과정을 거치는 지 BOM 관련 바이트 코드가 사라진다.

**아무튼, 원인은 BOM 이라고 하는 문서의 인코딩을 명시해주는 기술 때문이었다.**

#### BOM이란?

BOM이란 문서 맨 앞에 눈에 보이지 않는 특정 바이트(byte)를 넣은 다음 이것을 해석해서 정확히 어떤 인코딩 방식이 사용되었는지 알아내는 방법을 나타냅니다. 자세하게 유니코드가 little-endian 인지 big-endian 인지 아니면 UTF-8 인지 쉽게 알 수 있도록, 유니코드 파일이 시작되는 첫부분에 보이지 않게, 2\~3바이트의 문자열을 추가하는데 이것을 BOM이라고 합니다. BOM은 텍스트 에디터 화면에서는 보이지 않고, 헥사 에디터(Hex Editor)\*로 열었을 때만 보입니다.

**BOM : 어떤 인코딩 방식이 사용된 것인지 알려주는 특정 바이트**

#### BOM의 종류

| 인코딩 방식               | Byte Order Mark(BOM) |
| -------------------- | -------------------- |
| UTF-8                | EF BB BF             |
| UTF-16 Big Endian    | FE FF                |
| UTF-16 Little Endian | FF FE                |
| UTF-32 Big Endian    | 00 00 FE FF          |
| UTF-32 Little Endian | FF FE 00 00          |

출처 : <https://brownbears.tistory.com/124>

## 해결 방법

해결 방법은 UTF-8 인코딩에 대한 BOM 정보를 문서 맨 앞에 보이지 않는 바이트로 저장해서 파일을 만들어주는 것이다.

~~무려 이 문제를 해결하면, 슬랙도 인텔리제이도 지원해주지 않는 BOM을 우리 서비스에서는 지원해주는 것이다! (굉장해..)~~

```java
// 윈도우 계열 UTF-8 메타 정보 추가
byte[] bomUtf8ByteArray = {(byte) 0xEF, (byte) 0xBB, (byte) 0xBF};
final String addUtf8MetaInfo = new String(bomUtf8ByteArray);
csvHeader[0] = addUtf8MetaInfo + csvHeader[0]
```

csv 헤더 부분에 BOM 관련 메타 정보를 추가하여 저장해준다.

(참고) 전체 코드

```java
private InputStreamResource getStreamResourceByHeaderAndData(String[] csvHeader, List<List<String>> csvBody) {
        ByteArrayInputStream byteArrayOutputStream;

        // 윈도우 계열 UTF-8 메타 정보 추가
        byte[] bomUtf8ByteArray = {(byte) 0xEF, (byte) 0xBB, (byte) 0xBF};
        final String addUtf8MetaInfo = new String(bomUtf8ByteArray);
        csvHeader[0] = addUtf8MetaInfo + csvHeader[0];

        try (
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            CSVPrinter csvPrinter = new CSVPrinter(new PrintWriter(out), CSVFormat.DEFAULT.withHeader(csvHeader));
        ) {
            for (List<String> record : csvBody) {
                csvPrinter.printRecord(record);
            }
            csvPrinter.flush();
            byteArrayOutputStream = new ByteArrayInputStream(out.toString().getBytes("UTF-8"));

        } catch (IOException e) {
            throw new RuntimeException(e.getMessage());
        }

        return new InputStreamResource(byteArrayOutputStream);
    }
```

## Reference

<https://travelpark.tistory.com/84>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://programmer-jjy.gitbook.io/second-brain/technical/trouble-shooting/csv-utf-8-bom.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
