Firestore arrayUnion 중첩 배열 오류: 배열을 그대로 넘기면 실패하는 이유

··설명 약 6분·작성: 해비·블로그 소개

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): 배열을 펼쳐 문자열 요소 두 개를 전달
arrayUnion에 배열 전체를 넣은 경우와 펼침 연산자로 요소를 나눈 경우 비교
배열 변수 자체를 한 요소로 넣지 말고, 추가할 요소들을 펼쳐 전달합니다.

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 문서도 여러 항목을 추가할 때 여러 인수를 사용하거나 배열에 펼침 연산자를 적용하는 예제를 제공합니다.

실제 검증 결과: 같은 입력에서 실패와 통과를 비교했다

Firebase Web SDK 12.19.0 직렬화 실행 결과
실행 입력 기대 결과 실제 결과 종료
원래 코드 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가 빈 배열일 때 업데이트 자체를 생략할지는 애플리케이션 정책으로 정해야 합니다.

이어서 학습하기

공식 자료와 공개 사례

글 소개·학습 준비·전체 목차

arrayUnion(tags)는 태그 배열을 여러 요소가 아니라 배열 하나로 전달합니다. Firestore Standard edition은 배열 안에 배열을 허용하지 않으므로 Nested arrays are not supported 오류가 발생합니다. 여러 값을 추가하려면 arrayUnion(...tags)처럼 펼쳐서 전달합니다.

이 글이 도움이 되었나요?

조회 중

읽음 기록

읽음 — / 전체 20개 · 필수 14개

이 글을 쓴 해비

웹사이트를 제작·운영하고, 개인 서비스와 자동화·AI 도구를 만듭니다. 직접 만들며 배운 내용과 문제 해결 과정을 BlogFlow에 기록합니다.

새 글 받아보기

RSS 리더에서 BlogFlow의 새 글을 확인할 수 있습니다.

RSS 피드 구독하기

댓글 남기기