안녕하세요. 동쪽별입니다.
최근 프로젝트를 진행하다가 로컬에 저장되어 있는 xlsx 파일을 읽고, 파싱하는 기능을 구현해 보았어요.
보통 서버에서 이를 진행하여 데이터를 보내주지만, 서버 없는 SPA를 구현 중이라 클라이언트 단에서 이를 수행해야 했답니다..
위 파일 구조의 루트 디렉터리에 있는 xlsx 파일을 ./src/utils/api.js 파일에서 다룰 수 있게 했습니다.
xlsx 파일 읽기
xlsx 파일을 가져오기 위해 Fetch API를 사용했어요.
Fetch API?
Fetch API는 HTTP 파이프라인을 구성하는 요청과 응답 등의 요소를 JavaScript에서 접근하고 조작할 수 있는 인터페이스를 제공합니다. Fetch API가 제공하는 전역 fetch 메서드로 네트워크의 리소스를 쉽게 비동기적으로 가져올 수도 있습니다.
그리고 가져온 xlsx 파일을 읽기 위해 SheetJS 라이브러리의 read 메서드를 적용했습니다.
Sheet JS?
복잡한 스프레드시트에서 유용한 데이터를 추출하고 레거시 소프트웨어와 최신 소프트웨어에서 모두 작동하는 새 스프레드시트를 생성하기 위해 검증된 오픈 소스입니다. 스프레드시트를 단순화, 읽기, 편집 및 내보내기 등 작업을 할 때 유용합니다.
그래서 Sheet JS 는 어떻게 쓰냐고요?
<script src="sheetjs/dist/xlsx.full.min.js"></script>
위 스크립트 태그를 HTML 파일에 추가하면 돼요.
그런데 저는 이렇게 하지 않고,
import { read, utils } from "https://cdn.sheetjs.com/xlsx-latest/package/xlsx.mjs";
위 코드와 같이 import 를 통해 CDN 주소를 가져와 모듈의 필요한 메서드만 Destructuring(디스트럭처링)으로 가져왔습니다!
이렇게 가져온 SheetJS의 read 메서드는 문자열, binary, NodeJS buffer, 배열(Uint8Array 또는 ArrayBuffer 타입)로 저장된 스프레드시트로부터 데이터를 추출해줘요.
따라서, Fetch API로 로드한 xlsx 파일을 SheetJS의 read 메서드의 매개변수로 전달하기 위해, 위에서 언급했던 타입들 중 하나로 변형해주어야 합니다.
저는 fetch 메서드가 반환하는 프로미스 객체의 arrayBuffer 메서드를 사용해 ArrayBuffer 타입의 배열을 넘겨주도록 했어요.
↓ 그렇게 구현한 코드!
export const requestGeocoding = async () => {
try {
const res = await fetch('../../geographic_coordinates.xlsx');
if (!res.ok) {
throw new Error(`xlsx file 요청 실패 ${e.message}`);
}
const buffer = await res.arrayBuffer()
const workbook = read(buffer, {type: 'array'});
return parseXLSX(workbook);
} catch (e) {
throw new Error(e);
}
}
위 코드에서 xlsx 파일은 읽은 후 반환되는 객체를 저장한 workbook 을 출력하면 아래와 같아요!
이는 '기상청_단기예보 조회서비스_오픈API활용가이드_격자_위경도' xlsx 파일을 읽은 결과입니다.
xlsx 파일 파싱
xlsx 파일을 읽고 반환된 객체를 이용하기 쉽도록 파싱 작업 또한 수행했어요.
이때, Sheet JS의 utils.sheet_to_row_object_array 메서드를 사용했습니다!
이는 key가 시트의 이름이고 value가 워크시트 객체인 workbook.Sheets 객체를 행 우선순위로 100개씩 나누어 담은 배열들을 원소로 가지는 배열을 반환합니다.
이해를 돕기 위해 아래 코드를 실행해볼게요!
const sheet = workbook.Sheets[workbook.SheetNames[0]];
const rows = utils.sheet_to_row_object_array(sheet);
console.log(rows);
그럼 아래와 같은 결과가 나옵니다.
행을 100개씩 나눈 배열들이 보이네요.
그럼 원소들 중 한 배열에 대한 정보도 확인해볼까요?
짜잔 🤗
↓ 이렇게 반환된 배열을 사용하기 쉽게 파싱하는 코드입니다.
const parseXLSX = (workbook) => {
const sheet = workbook.Sheets[workbook.SheetNames[0]];
const rows = utils.sheet_to_row_object_array(sheet);
const geocoding = new Object();
rows.forEach((row) => {
const address1 = row['1단계'];
const address2 = row['2단계'];
const address3 = row['3단계'];
const coordinate = {
lat: row['경도(시)'],
lon: row['위도(시)'],
};
if (!geocoding[address1]) {
geocoding[address1] = coordinate;
} else if (!geocoding[address1][address2]) {
geocoding[address1][address2] = coordinate;
} else {
geocoding[address1][address2][address3] = coordinate;
}
});
return geocoding;
};
위 코드를 실행해서 리턴하는 객체는 아래와 같아요!
이제 지역별 경도, 위도를 쉽게 가져올 수 있게 됐네요 👏👏👏
아래는 전체 코드입니다.
import { read, utils } from "https://cdn.sheetjs.com/xlsx-latest/package/xlsx.mjs";
export const requestGeocoding = async () => {
try {
const res = await fetch('../../geographic_coordinates.xlsx');
if (!res.ok) {
throw new Error(`xlsx file 요청 실패 ${e.message}`);
}
const buffer = await res.arrayBuffer()
const workbook = read(buffer, {type: 'array'});
return parseXLSX(workbook);
} catch (e) {
throw new Error(e);
}
}
const parseXLSX = (workbook) => {
const sheet = workbook.Sheets[workbook.SheetNames[0]];
const rows = utils.sheet_to_row_object_array(sheet);
const geocoding = {};
rows.forEach((row) => {
const address1 = row['1단계'];
const address2 = row['2단계'];
const address3 = row['3단계'];
const coordinate = {
lat: row['경도(시)'],
lon: row['위도(시)'],
};
if (!geocoding[address1]) {
geocoding[address1] = coordinate;
} else if (!geocoding[address1][address2]) {
geocoding[address1][address2] = coordinate;
} else {
geocoding[address1][address2][address3] = coordinate;
}
});
return geocoding;
};
.
.
.
저도 이번 프로젝트를 하면서 Fetch API로 로컬 파일을 가져올 수 있다는 것과 Sheet JS에 대해 처음 알게 되었어요.
그래서 xlsx 파일 다루느라 삽질 조금 했답니다..😅
저와 비슷한 기능을 구현해야 하는데 이에 대해 잘 모르시는 분들에게 조금이나마 도움이 되었으면 좋겠네요!