JSON Toolkit

팀 협업 FAQ: 계약, 설정, 픽스처

핵심 요약: 이름 스타일, 시간 형식, null 의미, 열거 모양, 설정 형식, 픽스처 조직, 병합 충돌 — 팀이 진짜로 다투는 열두 가지 절충입니다.

중급약 12분 소요2026-10-30 발행2026-10-30 업데이트
팀 협업 FAQ 표지

핵심부터 말하면, 팀 관례에서 한 번 정하세요. HTTP 상태 코드와 {"data":…,"meta":…} 봉투, 사이트 전체 한 가지 이름 스타일, 시간대가 있는 RFC 3339 타임스탬프, 문자열 열거. 그런 다음 모든 새 API는 필드 추가만 하고 변경이나 삭제 없이 같은 계약을 따라 진화시킵니다.

이 질문들에 단일 정답은 없지만, 팀에는 단일 답이 있어야 합니다. 아니면 새 API마다 같은 논쟁을 다시 시작합니다.

1. API 계약

API는 어떤 구조를 반환해야 하나?

흔한 세 모양, 각각 비용이 있습니다:

모양 예 장점 단점
원본 데이터 [{...}, {...}] 간단 메타데이터를 담을 수 없음
고정 봉투 {"code":0,"message":"ok","data":{...}} 균일한 오류 처리 업무 코드가 HTTP 상태를 중복
리소스 봉투 {"data":{...},"meta":{...},"links":{...}} 확장 가능, 의미 명확 중첩이 한 단계 더

내부 업무 API에는 "리소스 봉투 + 결과를 표현하는 HTTP 상태 코드"를 선호하세요:

{
  "data": [{ "id": "1", "name": "示例" }],
  "meta": { "total": 128, "page": 2, "pageSize": 20 }
}

오류에는 또 다른 code를 중첩하지 말고 HTTP 상태 코드와 구조화된 오류 본문을 쓰세요:

{
  "error": {
    "type": "validation_failed",
    "message": "请求参数校验失败",
    "details": [
      { "path": "/email", "reason": "格式不正确" }
    ]
  }
}

요점: HTTP 상태 코드가 이미 성공이나 실패를 표현하므로 업무 코드는 중복입니다. 두 체계가 공존하면 "201인데 code는 500" 같은 불일치가 거의 불가피합니다.

맨 배열은 피하세요. 최상위가 배열이면 페이지네이션, 총계, 커서를 붙일 수 없고, 나중에 더하려면 모든 호출자를 바꿔야 합니다.

필드 이름은 snake_case인가 camelCase인가?

하나를 고르고 사이트 전체에 적용하며, 한 JSON 문서 안에서 절대 섞지 마세요.

스타일 흔한 생태계 예
camelCase JavaScript / TypeScript / Java userName, createdAt
snake_case Python / Ruby / Go(일부) / 데이터베이스 열 user_name, created_at
kebab-case 드묾, 하이픈 키는 접근이 어색 user-name

기준:

  1. 이 JSON을 누가 소비하나: 프런트엔드가 주도하면 camelCase, 데이터 파이프라인과 Python 서비스가 주도하면 snake_case
  2. 데이터베이스 열에 직접 대응하나: 표 구조를 그대로 통과시킬 때 snake_case가 변환 계층을 아낍니다
  3. 기존 시스템의 연속성: 기존 API가 쓰는 방식을 유지하세요. 일관성이 정확성보다 중요합니다.

꼭 변환해야 한다면 업무 코드에 toCamelCase 호출을 흩뿌리지 말고 경계(게이트웨이나 프런트엔드 요청 계층)에서 하세요.

시간 필드는 어떤 형식을 써야 하나?

시간대 오프셋이 있는 RFC 3339 / ISO 8601 문자열을 쓰세요:

{
  "createdAt": "2026-10-30T11:37:57+08:00",
  "updatedAt": "2026-10-30T03:37:57Z"
}

요점:

  • 시간대를 포함하세요: 2026-10-30 11:37:57처럼 존이 없으면 시간대 간 시나리오에서 반드시 깨집니다.
  • Z나 오프셋을 일관되게 쓰세요: 둘 다 올바르지만 한 API 안에서 섞지 마세요.
  • 숫자 타임스탬프를 피하세요: 초인지 밀리초인지, 윤초가 포함되는지 매번 재해석해야 합니다. 간결성이 필요하면 밀리초 정수를 쓰되 문서에 단위를 고정하세요.

createdAt / updatedAt / deletedAt처럼 At 접미사 필드 이름을 선호하세요. 값이 기간이 아니라 시점임이 한눈에 보입니다. 기간에는 timeoutMs, ttlSeconds처럼 이름에 단위를 넣으세요.

null은 null인가 필드 생략인가?

"이 필드가 의미 있는가"에 달려 있습니다:

의미 형태 비고
필드는 존재하지만 아직 값이 없음 "name": null "확인했고 비어 있다"를 명시
필드가 이 리소스와 무관함 생략 예: 일반 사용자에게 vipExpireAt이 없어야 함
부분 업데이트(PATCH) 생략 = 변경 안 함 여기서는 null만 "지우기"를 뜻함

세 번째가 가장 흔히 간과됩니다. PATCH 요청에서:

{ "nickname": null }

은 "nickname을 지운다"를 뜻하고, 요청 본문에서 nickname을 빼는 것은 "nickname을 건드리지 않는다"를 뜻합니다. 둘 다 "지우기"로 취급하면 이메일을 바꾸는 사용자가 nickname을 날려 버립니다.

다시 강조: undefined를 쓰지 마세요. 유효한 JSON 값이 아니고 JSON.stringify가 키를 완전히 버려서, 동작은 "생략"과 같으면서 코드는 "명시적으로 빈 값을 넘겼다"처럼 보입니다.

불리언 필드는 어떻게 이름 짓나?

is / has / can / should 접두사를 쓰고 부정을 피하세요.

{
  "isActive": true,
  "hasPassword": false,
  "canEdit": true
}

세 가지 교훈:

  1. 부정 금지: isDisabled: false는 독자가 한 번 반전해야 하고 isEnabled: true가 더 직접적입니다. 이중 부정(isNotDisabled)은 재앙입니다.
  2. 불리언에 숫자를 쓰지 마세요: "enabled": 1은 모든 소비자가 === 1을 쓰게 합니다.
  3. 문자열을 쓰지 마세요: "enable": "true"는 JavaScript에서 참 같은 값이고 if (data.enable)은 "false"에서도 참입니다. 그런 버그는 추적이 극히 어렵습니다.

열거 필드는 숫자인가 문자열인가?

명확한 저장이나 대역폭 제약이 없으면 문자열을 쓰세요.

{ "status": "active" }

{ "status": 1 }과 비교해 문자열이 이기는 이유:

  • 로그, 패킷 캡처, 데이터베이스가 매핑 표를 찾지 않고 직접 읽힙니다.
  • 열거 값을 추가해도 기존 값의 의미가 바뀌지 않습니다(숫자 열거 가운데에 값을 삽입하면 모두 밀림).
  • 프런트엔드가 1 → 'active' 매핑을 따로 유지할 필요가 없습니다.

비용은 몇 바이트 더인데, 압축 후에는 거의 무시할 수 있습니다.

동반 실천: 문자열 열거 값은 소문자 + 언더스코어(pending_review)를 쓰고, Schema에서 enum으로 제약해 잘못된 값이 검증 단계에서 거부되게 하세요.

2. 설정과 픽스처

설정은 JSON, YAML, TOML 중 무엇인가?

취향이 아니라 목적으로 고르세요:

형식 좋은 곳 나쁜 곳
JSON 프로그램적 읽기/쓰기, 엄격한 구조, Schema 검증 잦은 수동 편집(주석 불가)
YAML 깊이 중첩된 배포 설정(K8s) 손으로 쓴 들여쓰기는 실수 잦음, yes/no가 불리언이 됨
TOML 평평한 의존성과 도구 설정(Cargo.toml, pyproject.toml) 깊은 중첩의 가독성 나쁨
JSONC 주석이 필요한 에디터 설정(tsconfig.json) 시스템 경계를 넘을 때

팀의 흔한 실수는 업무 설정을 YAML에 두는 것입니다. YAML의 암묵적 타입 변환(1.0이 숫자가 되고, NO가 불리언이 되며, 2026-10-30이 날짜가 됨)이 파서마다 다른 결과를 냅니다.

설정을 프로그램으로 엄격히 검증해야 하면 JSON + Schema를 고르고, 설명은 _comment 필드나 별도 문서로 남기세요.

테스트 픽스처는 어떻게 조직하나?

"기준 하나 + 최소 diff"로 조직하세요. 테스트 사례마다 거대한 JSON을 복사하지 마세요.

fixtures/
├─ users/
│  ├─ base.json              # Baseline user object
│  ├─ no-email.json          # Only email is missing
│  └─ invalid-age.json       # Only age is out of range
└─ fixtures.ts               # Utility functions for loading and shallow merging
import baseUser from './users/base.json';

export function makeUser(overrides: Partial<User> = {}): User {
  return { ...baseUser, ...overrides };
}

그러면 새 테스트 사례는 다른 필드만 쓰면 되고, 기준 필드를 바꾸면 모든 사례가 자동으로 갱신됩니다.

세 규칙:

  1. 픽스처는 Schema 검증을 통과해야 합니다: 기준 픽스처 자체가 유효한 데이터여야 합니다.
  2. 운영 실제 데이터를 내보내지 마세요: 전화번호, 주민등록번호, 주소는 모두 개인 정보입니다.
  3. 픽스처는 버전 관리에 두되 작게 유지하세요: 수십 KB를 넘는 단일 파일은 정리하거나 생성할 자격이 있습니다.

JSON 파일의 병합 충돌을 어떻게 줄이나?

JSON은 쉽게 충돌합니다. 포매터가 파일 전체를 한 번에 재배치해 두 사람이 각각 한 줄을 바꿔도 파일 전체에서 충돌합니다.

네 가지 책략:

  1. 형식을 통일하고 .editorconfig에 더하세요: 일관된 들여쓰기, 줄 끝, 끝 줄 바꿈이 무관한 diff를 피합니다.
  2. 키를 정렬하세요: 알파벳순을 합의해 새 필드가 다른 사람의 삽입 지점과 충돌하지 않게 합니다.
  3. 파일을 나누세요: 거대한 config.json 하나를 도메인별 여러 파일로 나누면 충돌 표면이 자연히 줄어듭니다.
  4. "전체 재형식화" 커밋을 피하세요: 형식과 업무 변경을 두 커밋으로 분리해 리뷰가 읽히게 하세요.

팀이 잠금 파일(pnpm-lock.yaml, package-lock.json)을 쓴다면 손으로 병합하지 말고 재생성하세요:

git checkout --theirs pnpm-lock.yaml
pnpm install

버전 호환성을 어떻게 유지하나?

원칙은 "추가하되, 변경하지 말고, 삭제하지 말 것"입니다.

변경 호환성 접근
선택 필드 추가 호환 추가, 소비자는 알 수 없는 필드를 무시
필수 필드 추가 비호환 버전을 올리거나 기본값 제공
필드 제거 비호환 먼저 deprecated 표시, 호출자 관찰 후 제거
필드 타입 변경 비호환 새 필드 이름을 더하고 전환 중 듀얼 쓰기
열거 값 집합 변경 경우에 따라 값 추가는 호환, 제거는 비호환

지원 메커니즘:

  • 소비자는 "관대한 파싱"을 씁니다. 알 수 없는 필드를 무시하고 누락 필드에 기본값을 공급합니다.
  • 서버는 옛 필드를 한동안 유지하고 응답에 Deprecation 헤더를 힌트로 반환합니다.
  • Schema에서 새 필드를 required로 표시하지 말고, deprecated 필드는 deprecated: true로 태그합니다.

CI에서 계약을 어떻게 검증하나?

Schema를 코드의 일부로 취급하고 커밋마다 검증하세요.

# Validate sample data against the Schema (using ajv-cli)
npx ajv-cli validate -s schemas/user.json -d "fixtures/users/*.json" --strict=false

CI에서 세 가지를 실행하세요:

  1. Schema 자체의 유효성: $schema 선언이 유효한지, $ref가 해석되는지
  2. 픽스처가 Schema와 맞는지: 테스트 데이터가 어긋나지 않게
  3. 계약이 역호환인지: 이전 버전과 비교해 제거된 필드나 변경된 타입 확인

세 번째는 도구 지원(openapi-diff, json-schema-diff 등)이 필요하고, 없으면 적어도 스크립트로 required 배열과 properties 키 집합의 변경을 비교하세요.

3. 빠른 참조

질문 권장
응답 구조 {"data":..., "meta":...}, 오류는 HTTP 상태 코드로
이름 스타일 camelCase(프런트엔드 우선) 또는 snake_case(데이터 우선), 사이트 전체 일관
시간 형식 시간대가 있는 RFC 3339 문자열, At으로 끝나는 필드 이름
null 값 없음은 null, 무관한 필드는 생략, PATCH의 생략은 "변경 안 함"
불리언 이름 is/has/can 접두사, 부정 없음, 0/1 없음
열거 문자열, 소문자와 언더스코어, Schema에 enum 추가
설정 형식 프로그램 검증은 JSON, 수동 편집은 TOML이나 JSONC
픽스처 기준 하나 + 최소 diff 오버라이드, Schema에 유효
병합 충돌 형식 통일, 키 정렬, 파일 분할
버전 호환성 추가만 하고, 변경·삭제 없이, 제거 전 deprecated 표시