Firestore arrayUnion에 배열을 넘겼을 때 중첩 배열 오류는 어떻게 해결하나요?
arrayUnion(tags)는 태그 여러 개가 아닌 배열 한 개를 인수로 넘겨 중첩 배열 오류가 날 수 있습니다. 문자열 배열은 arrayUnion(...tags)로 펼치세요. 원래 입력에 중첩 배열이 있다면 펼침만으로 해결되지 않습니다.
적용 조건·범위: 이번 검증은 commit() 전 직렬화 단계까지입니다. 실제 원격 저장, 인증, Security Rules, 네트워크 성공은 검증하지 않았습니다.
본문 근거: Firebase 공식 문서: 배열 요소 업데이트 · Firebase JavaScript API: arrayUnion · Firebase JavaScript SDK 이슈 #3841: Nested arrays 오류 사례
오류 증상: arrayUnion에 배열을 넣자 중첩 배열 오류가 난다
화면에서 선택한 태그가 ["react", "firebase"]처럼 배열로 준비되어 있을 때 다음처럼 그대로 넘기기 쉽습니다.
const tags = ["react", "firebase"];
arrayUnion(tags);
하지만 Firestore Web SDK가 쓰기 데이터를 직렬화하는 단계에서 아래 오류가 발생합니다.
Function arrayUnion() called with invalid data.
Nested arrays are not supported (found in document posts/post-1)
Firebase JavaScript SDK 공개 이슈에도 같은 오류 문구가 보고되었고, 작성자는 실제 데이터에 배열 안의 배열이 있었다고 확인했습니다. 이 오류는 Rules 거부나 네트워크 장애가 아니라 전송할 값의 구조가 잘못된 경우입니다.
환경·재현 조건: 서버에 쓰지 않고 직렬화 단계만 확인한다
arrayUnion 입력 직렬화 비교
배열 그대로 전달한 오류와 spread로 펼친 입력의 SDK 검사 결과를 비교하세요.
예제 크게 보기 (새 탭)검증 환경: Node.js 24.19.0, Firebase Web SDK 12.19.0, ESM 모듈. writeBatch().update()로 쓰기 명령을 구성하되 commit()은 호출하지 않았습니다. 따라서 실제 Firebase 프로젝트·인증·Rules 없이 동일한 직렬화 오류를 재현할 수 있습니다.
먼저 패키지를 설치합니다.
npm install firebase@12.19.0
repro-array-union.mjs · 원래 코드
import { initializeApp } from "firebase/app";
import { arrayUnion, doc, getFirestore, writeBatch } from "firebase/firestore";
const app = initializeApp({ projectId: "blogflow-repro" }, "array-union-repro");
const db = getFirestore(app);
const tags = ["react", "firebase"];
try {
writeBatch(db).update(doc(db, "posts", "post-1"), {
tags: arrayUnion(tags),
});
console.log("검증 통과");
} catch (error) {
console.error(error.message);
process.exitCode = 1;
}
node repro-array-union.mjs를 실행하면 arrayUnion(tags)를 해석하는 시점에 종료 코드 1로 실패합니다.
원인: arrayUnion은 배열 한 개가 아니라 여러 요소를 받는다
JavaScript API의 함수 시그니처는 arrayUnion(...elements)입니다. 인수 하나하나가 Firestore 배열에 추가할 요소입니다.
arrayUnion("react", "firebase"): 문자열 요소 두 개를 전달arrayUnion(tags): 배열 요소 한 개를 전달arrayUnion(...tags): 배열을 펼쳐 문자열 요소 두 개를 전달

Firestore Standard edition의 배열에는 다른 배열을 요소로 넣을 수 없습니다. 그래서 arrayUnion(tags)가 만들려는 구조는 개념적으로 [["react", "firebase"]]가 되어 거부됩니다.
변경 코드: 펼침 연산자로 태그를 개별 인수로 전달한다
fix-array-union.mjs · 수정 코드
import { initializeApp } from "firebase/app";
import { arrayUnion, doc, getFirestore, writeBatch } from "firebase/firestore";
const app = initializeApp({ projectId: "blogflow-repro" }, "array-union-fix");
const db = getFirestore(app);
const tags = ["react", "firebase"];
try {
writeBatch(db).update(doc(db, "posts", "post-1"), {
tags: arrayUnion(...tags),
});
console.log(`전달 요소: ${JSON.stringify(tags)}`);
console.log("Firestore 직렬화 검증 통과");
} catch (error) {
console.error(error.message);
process.exitCode = 1;
}
수정은 괄호 안에 ...를 추가하는 한 줄이지만 의미는 분명합니다. tags 배열을 하나의 값으로 전달하지 않고 각 문자열을 독립된 인수로 전달합니다. 공식 Firestore 문서도 여러 항목을 추가할 때 여러 인수를 사용하거나 배열에 펼침 연산자를 적용하는 예제를 제공합니다.
실제 검증 결과: 같은 입력에서 실패와 통과를 비교했다
| 실행 | 입력 | 기대 결과 | 실제 결과 | 종료 |
|---|---|---|---|---|
| 원래 코드 | arrayUnion(tags) |
중첩 배열 오류 | Nested arrays are not supported |
1 |
| 수정 코드 | arrayUnion(...tags) |
직렬화 통과 | ["react","firebase"] 전달 후 통과 |
0 |
두 파일을 실제로 실행했고, 원래 코드는 문서 경로 posts/post-1에서 실패했습니다. 수정 코드는 동일한 태그 배열을 사용하면서 오류 없이 쓰기 명령을 구성했습니다.
폼 입력이 배열일 때 한 번 더 확인하기
위 수정은 tags가 문자열 배열이라는 전제에서만 안전합니다. tags가 [‘react’, [‘firebase’]]처럼 이미 중첩됐다면 …tags를 붙여도 두 번째 인수는 배열입니다. 펼침 연산자는 한 단계의 바깥 배열만 풉니다. fix-array-union.mjs에서 writeBatch(db).update(…) 앞에 다음 검사를 추가해 입력 계약을 드러내세요.
if (!Array.isArray(tags) || tags.length === 0 ||
!tags.every((tag) => typeof tag === 'string' && tag.trim().length > 0)) {
throw new Error('태그는 비어 있지 않은 문자열 배열이어야 합니다.');
}
그다음 const tags를 한 번에 하나씩 아래 값으로 바꾸고 같은 파일을 실행하세요.
node repro-array-union.mjs
node fix-array-union.mjs
[‘react’, ‘firebase’]는 검사 통과, [‘react’, [‘firebase’]]와 []는 입력 검사에서 실패해야 합니다. 마지막 두 사례는 새 검사를 추가한 뒤의 예상 결과이며 이 편집에서는 SDK로 재실행하지 않았습니다. 비어 있는 태그 목록을 허용해야 하는 제품이라면 오류를 던지는 대신 업데이트를 생략하는 정책을 정하세요. 직렬화 검사 통과도 commit 성공이나 실제 저장을 증명하지 않습니다.
적용 한계: 펼침 연산자가 모든 배열 문제를 해결하지는 않는다
- 이번 검증은
commit()전 직렬화 단계까지입니다. 실제 원격 저장, 인증, Security Rules, 네트워크 성공은 검증하지 않았습니다. arrayUnion()은 기존 배열에 없는 요소만 추가합니다. 원하는 순서에 끼워 넣거나 특정 위치를 수정하는 API는 아닙니다.- 객체도 요소로 전달할 수 있지만, 객체의 일부 키만 같은 경우가 아니라 전체 값이 같은지를 기준으로 중복 여부가 판단됩니다.
- Standard edition에서 실제로 중첩 배열 구조가 필요하다면 배열 대신 map 객체 등 다른 데이터 구조를 설계해야 합니다.
tags가 빈 배열일 때 업데이트 자체를 생략할지는 애플리케이션 정책으로 정해야 합니다.
이어서 학습하기
- Firestore CRUD 사용법: 컬렉션 구조와 읽기 쓰기 흐름
- Firestore undefined 오류 해결: 선택 필드가 저장을 막을 때
- Firebase 실무 오류 해결 모음
공식 자료와 공개 사례
이 글이 도움이 되었나요?
읽음 기록
읽음 — / 전체 20개 · 필수 14개
새 글 받아보기
RSS 리더에서 BlogFlow의 새 글을 확인할 수 있습니다.