빠른 비교
{
"unsafeId": 9007199254740993,
"safeId": "9007199254740993",
"amountMinor": 129900,
"amountDecimal": "1299.00"
}정밀도 경계
JSON number 문법과 구현 타입은 다르다
JSON number는 base 10 숫자, fraction, exponent를 문법으로 표현합니다. 하지만 parser가 이를 어떤 런타임 타입으로 읽을지는 구현 언어마다 다릅니다. JavaScript는 일반 number가 IEEE 754 binary64 기반이고, 다른 언어는 integer, float, decimal, big integer를 구분할 수 있습니다.
{
"count": 10,
"ratio": 0.25,
"large": 1e30
}따라서 "문법적으로 유효하다"와 "모든 소비자가 같은 값으로 읽는다"는 별도 문제입니다.
53비트 안전 정수 범위를 넘으면 string을 검토한다
RFC 8259와 I-JSON은 IEEE 754 double precision을 상호운용성 기준으로 언급합니다. I-JSON은 절댓값이 9007199254740991을 넘는 integer를 정확한 값으로 기대할 수 없다고 봅니다. 주문 번호, 사용자 ID, snowflake ID처럼 계산보다 식별이 목적인 값은 string이 더 안전합니다.
{
"userId": "9007199254740993",
"tweetId": "1735683812345678901"
}ID를 number로 보내면 일부 클라이언트에서 마지막 자리가 바뀌거나, string 변환 후 원래 값과 달라지는 문제가 생길 수 있습니다.
decimal 금액은 number보다 단위 계약이 중요하다
금액을 JSON number로 보내면 소수 표현과 반올림 규칙이 소비자마다 달라질 수 있습니다. 결제, 정산, 포인트처럼 정확한 계산이 필요한 값은 minor unit 정수나 decimal string을 사용합니다.
{
"amountMinor": 129900,
"currency": "KRW"
}{
"amount": "1299.00",
"currency": "USD"
}minor unit을 쓰면 계산은 단순해지지만 통화마다 소수 자릿수가 다르므로 currency와 함께 계약해야 합니다.
JSON Schema도 런타임 정밀도를 대신하지 않는다
JSON Schema의 type: "integer"나 multipleOf는 값 형태를 검증할 수 있지만, 파서가 이미 정밀도를 잃은 뒤라면 원래 값을 복구할 수 없습니다. 큰 number를 안전하게 다루려면 파싱 단계부터 string이나 arbitrary precision parser를 사용해야 합니다.
선택 기준
| 값 | 권장 표현 |
|---|---|
| 작은 카운트 | number |
| 53비트 범위 내 정수 계산 | number |
| 큰 ID, 주문 번호 | string |
| 정확한 금액 | minor unit integer 또는 decimal string |
| 과학 계산의 근사값 | number 가능 |
| 원문 정밀도 보존 | string 또는 decimal 전용 parser |
주의할 점
큰 정수 ID를 JSON number로 보내면 일부 클라이언트에서 값이 조용히 바뀔 수 있습니다. 계산하지 않는 식별자는 처음부터 string으로 계약하는 편이 안전합니다.
API 문서에는 단순히 number라고 쓰지 말고 값의 의미, 범위, 단위, 정밀도 보존 여부를 함께 적어야 합니다. 특히 금액은 통화와 minor unit 기준을 분리해서 명시해야 합니다.
참고 링크
2 sources