REST API 6일차 : 데이터 포맷과 직렬화: JSON 규칙과 요청·응답 스키마 표준화

JSON을 중심으로 데이터 포맷과 직렬화 규칙, 날짜·숫자·NULL 처리, 그리고 일관된 요청·응답 스키마 설계를 시각화한 이미지입니다.

REST API 6일차 : 데이터 포맷과 직렬화: JSON 규칙과 요청·응답 스키마 표준화

한눈에 보는 요약

이 글에서는 백엔드와 프론트엔드, 외부 시스템 간 통신에서 가장 널리 사용되는 JSON 포맷을 중심으로 데이터 직렬화의 기본 개념과 규칙을 정리합니다.

특히 날짜·숫자·NULL과 같이 실무에서 자주 문제가 되는 타입들을 어떻게 표현하고, 어떤 기준으로 일관성을 유지할 것인지에 초점을 둡니다.

마지막으로 표준 응답 JSON 형태를 정의하고, 기존 산발적인 응답을 표준 구조로 통합하는 실습 흐름을 제시하여 팀 차원의 스키마 가이드를 만드는 데 도움을 드립니다.

이 글을 읽은 후에는 API 요청/응답 설계에서 “항상 같은 규칙으로 직렬화된 JSON을 전달하는 것”을 팀 규범으로 삼을 수 있게 됩니다.

목차


핵심 포인트

  • 직렬화는 메모리 상의 객체를 네트워크 전송이나 저장에 적합한 문자열로 변환하는 과정이며, 웹 API에서는 대부분 JSON이 사용됩니다.
  • JSON에서는 키 이름, 날짜 포맷, 숫자 표현, NULL 사용 여부를 팀 차원에서 미리 합의해 두어야 예측 가능한 API를 만들 수 있습니다.
  • 요청과 응답 스키마에 “공통 래퍼 구조”를 정의하고, 모든 엔드포인트가 이를 따르게 하면 에러 처리와 클라이언트 구현이 크게 단순해집니다.
  • 표준 응답 JSON(예: success, code, message, data 구조)으로 통일하면 로그 분석, 모니터링, SDK 개발이 용이해집니다.
  • 실습 단계에서는 기존 제각각이던 응답을 표준 스키마로 리팩토링해 봄으로써, 팀 내 JSON 스타일 가이드를 자연스럽게 정착시킬 수 있습니다.

상세 설명

1. 데이터 포맷과 직렬화 개요

애플리케이션 내부에서 사용하는 데이터 구조는 언어와 프레임워크마다 형태가 다르지만, 네트워크를 통해 다른 시스템과 통신하기 위해서는 공통으로 이해할 수 있는 문자열 형식으로 변환해야 합니다. 이 과정을 직렬화(serialization)라고 부릅니다.

대표적인 직렬화 포맷으로는 JSON, XML, CSV, Protocol Buffers, Avro 등이 있으나, 웹·모바일 API에서는 사람과 기계가 모두 읽기 쉬운 JSON이 사실상의 표준으로 사용되고 있습니다. 직렬화 포맷을 무엇으로 선택하든 중요한 것은 “항상 같은 방식으로 표현되는가”이며, 그 기준을 문서화해 두는 것이 본 글의 핵심 주제입니다.

JSON을 사용할 때는 단순히 JSON.stringify 수준에서 멈추지 않고, 필드명 규칙, 타입 변환 규칙, 공통 응답 스키마 등을 정의하여 팀 전체가 일관된 직렬화 전략을 가지는 것이 바람직합니다.

2. JSON 규칙과 베스트 프랙티스

JSON 포맷은 문법 자체는 간단하지만, 실무에서는 다음과 같은 규칙을 정해두지 않으면 곧바로 혼란이 발생합니다.

  • 키 네이밍 컨벤션:
    • 대부분의 프론트엔드(JavaScript/TypeScript) 환경과 일치시키기 위해 camelCase를 많이 사용합니다. 예: createdAt, userName
    • 백엔드 DB가 snake_case라 하더라도, API 응답은 camelCase로 맞추고, 매핑 레이어에서 변환하는 패턴을 자주 사용합니다.
  • 불필요한 중첩/루트 요소 자제:
    • { "user": { "id": 1, "name": "Kim" } } 처럼 한 번만 감싸는 루트 키는 의미가 없다면 제거하는 것이 낫습니다.
    • 반복되는 동일 구조는 배열로 표현하여 클라이언트가 예측하기 쉽게 합니다.
  • 일관된 응답 래퍼 구조:
    • 성공/실패 여부, 상태 코드, 메시지, 실제 데이터(payload)를 고정된 구조로 래핑합니다.
    • 예: { "success": true, "code": "USER_200", "message": "성공", "data": { ... } }
  • 필드 존재 여부와 기본값:
    • 값이 없는 경우 “키를 생략할 것인지”, “null을 보낼 것인지”를 타입별로 정의해 두어야 합니다.
    • 예: 옵션 필드는 미제공 시 키를 생략하고, 서버 생성 후에만 의미가 생기는 필드는 null 허용 등.

이러한 규칙이 코드 곳곳에 암묵적으로 흩어져 있으면 유지보수가 어렵기 때문에, 문서화된 JSON 스타일 가이드와 샘플 응답(JSON 스키마)을 준비하는 것이 중요합니다.

3. 날짜·숫자·NULL 처리 전략

JSON은 기본적으로 문자열, 숫자, 불리언, 객체, 배열, null만을 지원합니다. 따라서 날짜나 고정 소수점 숫자 같은 값은 별도 규칙이 없으면 해석 방식이 시스템마다 달라질 수 있습니다.

3-1. 날짜·시간(Date/Time) 처리

날짜와 시간은 다음과 같은 원칙으로 직렬화하는 것이 일반적입니다.

  • 표준 포맷 사용: ISO 8601 포맷 문자열을 사용합니다. 예: 2025-12-14T10:30:00+09:00
  • UTC vs 로컬: 내부적으로는 UTC로 저장하고, 응답은 타임존을 명시한 문자열로 전달하는 방식이 안전합니다.
  • 일자만 필요한 경우: 시간 정보가 필요 없다면 YYYY-MM-DD 형식으로 일관되게 제공합니다.

예를 들어 주문 데이터를 직렬화할 때 다음과 같이 표현할 수 있습니다.

 { "orderId": 12345, "orderedAt": "2025-12-14T10:30:00+09:00", "expectedDeliveryDate": "2025-12-16" } 

위 예시처럼 날짜·시간 필드는 모든 API에서 같은 포맷을 사용해야 클라이언트와 데이터 파이프라인(ETL, BI 툴 등)에서 별도의 예외 처리가 필요 없습니다.

3-2. 숫자(Number) 처리

숫자 타입은 겉으로 보기에는 단순하지만, 다음 항목들을 명확히 해야 합니다.

  • 숫자는 가능한 한 JSON 숫자 타입으로:
    • 나이, 개수, 점수, 비율, 금액 등은 기본적으로 숫자 타입으로 직렬화합니다. 예: "age": 30, "price": 12000
  • 정밀도가 중요한 금액/지수:
    • 부동소수점 오차가 문제가 되는 도메인(금융 등)에서는 정수(최소 단위) 또는 문자열로 표현하는 전략을 사용합니다.
    • 예: "amount": 123456 (원단위 정수), "amount": "1234.56" (문자열)
  • 식별자는 문자열:
    • 전화번호, 주민등록번호, 카드번호 등은 숫자처럼 보여도 “계산하지 않는 값”이므로 문자열로 직렬화하는 것이 좋습니다.

3-3. NULL 처리

null은 “값이 없음”을 표현하지만, 실무에서는 “키가 아예 없음”과 의미가 혼동되기 쉽습니다. 다음과 같이 원칙을 정해 두는 것이 좋습니다.

  • NULL 허용 필드 정의: ERD나 스키마 문서에서 필드별로 “nullable 여부”를 명시합니다.
  • 생명주기 상 아직 값이 없는 경우: 예를 들어 deletedAt 필드는 삭제 전에는 null, 삭제 후에는 날짜로 채우는 방식을 사용할 수 있습니다.
  • 옵션 파라미터: 요청 파라미터 중 선택 필드는 전달하지 않으면 키를 생략하고, 일부 케이스에서 명시적으로 null을 보내는 패턴은 최소화합니다.

팀 내 합의 없이 null과 미존재를 혼용하면, 클라이언트는 조건 분기를 계속 추가해야 하고 버그 가능성이 높아집니다. 따라서 JSON 스키마 레벨에서 nullable 여부를 명확히 문서화해야 합니다.

4. 요청/응답 스키마의 일관성 유지

API가 많아질수록 중요한 것은 일관성입니다. 동일한 도메인에 대해 서로 다른 응답 구조를 사용하면 클라이언트와 문서 모두 복잡해집니다. 다음과 같은 방향으로 스키마 일관성을 설계할 수 있습니다.

  • 공통 응답 래퍼 정의:
    • 모든 응답에 공통으로 포함되는 필드: success, code, message, data, meta 등.
    • 에러 응답도 동일한 구조를 유지하되 success=false, code에 에러 코드를 넣는 방식으로 일관성을 유지합니다.
  • 리스트/페이지 응답 패턴 통일:
    • 목록 조회의 경우 data 아래에 items 배열과 page, size, totalCount 등의 메타 정보를 두는 패턴을 사용할 수 있습니다.
  • 요청 스키마 재사용:
    • 등록/수정 요청 바디가 대부분 동일하다면, 동일한 JSON 스키마를 재사용하고 일부 필드만 optional로 두는 방식이 유지보수에 유리합니다.

이러한 원칙을 OpenAPI(Swagger) 등으로 스키마화하고, 예시 JSON을 함께 제공하면 클라이언트 개발자가 API를 빠르게 이해할 수 있습니다.

5. 실습: 응답 JSON 형태 표준화

실습 관점에서, 다음과 같은 비표준 응답을 표준 구조로 리팩토링해 보겠습니다.

기존 응답 예시 (비표준)

 { "status": "ok", "result": { "user_id": "123", "name": "Kim", "age": "30", "created": "2025/12/14", "isActive": "Y" } } 

위 예시는 다음과 같은 문제가 있습니다. 키 네이밍이 snake_case와 문자열 숫자 혼용, 날짜 포맷이 비표준(YYYY/MM/DD), 불리언을 Y/N 문자열로 표현하는 등 여러 비일관성이 존재합니다.

표준 응답 구조로 리팩토링한 예시

 { "success": true, "code": "USER_200", "message": "사용자 조회에 성공했습니다.", "data": { "id": 123, "name": "Kim", "age": 30, "isActive": true, "createdAt": "2025-12-14T10:30:00+09:00", "deletedAt": null } } 

리팩토링된 응답에서는 식별자를 id로 단일화하고, 숫자는 숫자 타입으로, 불리언은 true/false로, 날짜는 ISO 8601 포맷으로 통일했습니다. 또한 공통 필드 success, code, message, data를 사용해 에러 응답에서도 동일한 구조를 유지할 수 있습니다.


코드 예시

아래는 서버 내부 객체를 위에서 정의한 표준 JSON 응답으로 직렬화하는 예시 코드입니다(의사 코드 수준).

 function toUserResponse(user) { return { success: true, code: "USER_200", message: "사용자 조회에 성공했습니다.", data: { id: user.id, name: user.name, age: user.age, isActive: user.isActive, // boolean createdAt: user.createdAt.toISOString(), // ISO 8601 deletedAt: user.deletedAt ? user.deletedAt.toISOString() : null } }; } 

위 코드에서는 도메인 객체 user를 입력 받아, 항상 동일한 응답 래퍼 구조로 JSON 직렬화를 수행합니다. 날짜 필드는 toISOString()을 통해 ISO 8601 포맷으로 변환하고, 삭제되지 않은 경우에는 deletedAtnull을 명시적으로 설정하여 스키마를 일관되게 유지합니다. 이러한 변환 로직을 중앙화하면, 엔드포인트마다 다른 응답 구조가 생기는 것을 방지할 수 있습니다.

타입별 표현 방식 비교 표

아래 표는 주요 데이터 타입별로 JSON에서 권장되는 표현 방식과 예시, 주의사항을 정리한 것입니다.

타입 권장 JSON 표현 예시 값 피해야 할 표현 주의사항
날짜·시간 문자열 (ISO 8601) "2025-12-14T10:30:00+09:00" "2025/12/14", 20251214 타임존 포함 여부를 통일하고, 포맷을 문서화합니다.
날짜(일자) 문자열 (YYYY-MM-DD) "2025-12-14" "14-12-2025", "2025.12.14" 일자만 필요한 경우에도 포맷을 혼용하지 않도록 합니다.
정수 숫자 숫자 30, 12000 "30", "012000" 앞에 0이 붙는 표현은 문자열 오해를 부를 수 있습니다.
금액(정밀도 중요) 정수(최소 단위) 또는 문자열 123456, "1234.56" 1234.5600000001 부동소수점 오차를 피하기 위해 표현 방식 기준을 정합니다.
불리언 true/false true, false "Y", "N", 1, 0 프론트에서 조건식이 단순해지도록 순수 불리언을 사용합니다.
NULL null 또는 키 생략 "deletedAt": null "N/A", "" 필드별로 nullable 정책과 미존재 정책을 문서화합니다.
식별자(전화번호 등) 문자열 "01012345678" 1012345678 앞자리 0 유지, 포맷(하이픈 포함 여부)을 일관되게 합니다.

실행 단계

  1. 현재 API 응답 패턴 인벤토리 작성
    주요 엔드포인트들의 요청/응답 JSON을 수집하여, 키 네이밍, 날짜 포맷, 숫자/문자열 혼용, NULL 사용 방식 등을 표로 정리합니다. 이 단계에서 중복 패턴과 예외 케이스를 모두 파악해 두는 것이 중요합니다.
  2. 팀 공통 JSON 스타일 가이드 초안 작성
    수집한 인벤토리를 바탕으로 “권장/비권장” 규칙을 정리하고, 날짜·숫자·NULL 처리, 키 네이밍, 공통 응답 구조 등에 대해 합의를 시도합니다. 가능한 한 구체적인 예시(JSON 스니펫)를 포함시키는 것이 좋습니다.
  3. 표준 응답 스키마 정의 및 샘플 작성
    success, code, message, data, meta 등 공통 필드를 포함하는 표준 응답 스키마를 정의합니다. 성공/에러/페이지 응답 등 대표 유형에 대한 샘플 JSON을 함께 준비합니다.
  4. 직렬화 레이어 구현
    컨트롤러 단에서 직접 JSON을 구성하지 말고, 도메인 객체를 입력 받아 표준 응답 JSON을 생성하는 공통 헬퍼(직렬화 레이어)를 구현합니다. 이를 통해 모든 엔드포인트가 동일한 로직을 거치도록 강제합니다.
  5. 기존 엔드포인트 단계적 리팩토링
    우선순위가 높은 엔드포인트부터 직렬화 레이어를 사용하도록 변경하고, 응답 스키마 변경으로 인한 클라이언트 영향 범위를 검토합니다. 필요 시 버전 분리(/v1, /v2)를 통해 점진적으로 마이그레이션합니다.
  6. 문서 및 자동 테스트 정비
    OpenAPI 스펙, API 문서, 샘플 코드, 계약 테스트 등을 표준 스키마 기준으로 업데이트합니다. 스냅샷 테스트를 활용해 응답 JSON 구조가 의도치 않게 변경되지 않도록 보호하는 것이 좋습니다.

추가로 생각해볼 점

  • 장기적으로는 JSON 직렬화 규칙을 서버 내부에서만 사용하는 것이 아니라, 데이터 파이프라인, 로그 스키마, 이벤트 스트림(Kafka 등)까지 일관되게 적용할 수 있는지 검토하는 것이 좋습니다.
  • 클라이언트(SPA, 모바일 앱, 외부 파트너)와의 계약 관점에서 응답 스키마 변경은 큰 비용을 수반하므로, 버전 전략과 호환성 정책을 미리 정해 두는 것이 중요합니다.
  • JSON만이 유일한 해답은 아니며, 성능·용량 요구사항에 따라 Protobuf 등의 바이너리 포맷을 보조적으로 도입하는 것도 고려할 수 있습니다. 다만 이 경우에도 “스키마 일관성”이라는 원칙은 그대로 유지해야 합니다.
Reactions

댓글 쓰기

0 댓글