-
API의 긴 숫자 ID가 JavaScript에서 바뀌는 이유Data Analysis 2026. 10. 6. 00:28728x90반응형

식별자를 문자열로 전달하면 Number 변환에서 생기는 정밀도 손실을 피할 수 있습니다. 큰 식별자는 원문 문자열로 전달하는 계약을 우선 검토하고, JSON 파싱 뒤 이미 잃은 자릿수를 형변환으로 복구하려 하지 마세요.
응답 원문에는
9007199254740993이 있는데 브라우저 객체에는 끝자리가2로 보일 수 있습니다. JSON이 도착하는 동안 문자가 바뀐 것이 아니라, 숫자 토큰을 JavaScript의Number로 해석하는 순간 정밀도를 잃었을 가능성이 있습니다. 객체를 출력한 로그와 응답 원문을 나눠 봐야 하는 이유입니다.아래 ID는 설명용 숫자입니다. 2026년 9월 28일 확인한 JavaScript 명세와 JSON 표준을 바탕으로 하며 실제 서비스 장애 기록을 옮긴 글은 아닙니다.
서로 다른 정수가 같은 Number가 됩니다
JavaScript의 안전한 정수 상한은
Number.MAX_SAFE_INTEGER, 즉9007199254740991입니다. 그보다 큰 정수 중에도 정확히 표현되는 값은 있습니다. 다만 이웃한 모든 정수를 계속 구별할 수는 없습니다. ECMAScript의 안전 정수 정의다음 두 원문을 비교해 보겠습니다.
const first = JSON.parse( '{"id":9007199254740992}' ); const second = JSON.parse( '{"id":9007199254740993}' ); // true first.id === second.id; // true Number.isInteger(second.id); // false Number.isSafeInteger(second.id);문자열의 끝자리는 다르지만 파싱한 값은 같아집니다.
isInteger는 소수 부분이 없는지 확인하므로 이 문제를 잡지 못합니다.isSafeInteger는 안전 범위도 확인하지만 이미 잃은 자릿수를 복원하지는 않습니다.ID가 겹치면 화면에 다른 항목을 합쳐 표시하거나 다음 요청에서 엉뚱한 대상을 가리킬 수 있습니다. 계산 결과를 적당히 반올림하는 상황과 달리 식별자는 원래 대상을 구분할 수 있어야 합니다.
JSON 문법은 큰 숫자 토큰 자체를 금지하지 않습니다. RFC 8259는 수신 구현이 숫자의 범위와 정밀도에 제한을 둘 수 있음을 설명합니다. 서버 언어에서 정확히 저장됐다는 사실만으로 브라우저에서도 같은 값이 된다고 기대할 수 없습니다. JSON 숫자의 상호운용성
처음부터 문자열인 ID를 보내면 됩니다
산술이 필요 없는 식별자라면 API에서 다음처럼 문자열로 보내는 계약이 단순합니다.
{"id":"9007199254740993"}클라이언트도 이를 끝까지 문자열로 다룹니다. 다음 코드는 숫자 문자로만 된 ID를 받는 작은 예시입니다.
function readId(jsonText) { const record = JSON.parse(jsonText); if ( record === null || typeof record !== "object" || Array.isArray(record) || typeof record.id !== "string" || !/^[0-9]+$/.test(record.id) ) { throw new TypeError( "id는 숫자 문자로 된 " + "문자열이어야 합니다." ); } return record.id; } const id = readId( '{"id":"9007199254740993"}' ); const requestBody = JSON.stringify({ id });숫자 타입이 오면
String(record.id)로 살려 주지 않고 실패하게 했습니다. 이미 반올림된 값을 문자열로 고정할 수 있기 때문입니다. 다음 요청의id도 따옴표 안에 남는지 확인해야 왕복 계약이 유지됩니다.이 정규식은 예시 서비스가 0부터 9까지만 허용한다는 가정입니다. UUID나 접두사가 있는 ID라면 해당 스키마로 바꾸고, 실제 허용 길이도 정합니다. 앞자리 0이 의미 있다면 그대로 보존합니다. 문자열
"0012"를 숫자로 바꾸었다가 되돌리면 표기까지 같게 만들 수 없습니다.정렬 규칙도 결정해야 합니다. 문자열 비교에서
"10"은"2"보다 앞설 수 있습니다. 단순히 ID를 보관·전달하는 것과 숫자 크기로 정렬하는 요구는 다릅니다. 계산이나 순서가 정말 필요할 때만 정확한 원문에서 정수 타입으로 변환합니다.BigInt를 쓰더라도 출발점이 정확해야 합니다
큰 정수 연산에는
BigInt를 쓸 수 있습니다. 그러나 이미 Number로 바뀐 입력을 넘기면 그 값을 확장할 뿐입니다.const rounded = JSON.parse( '{"id":9007199254740993}' ).id; // 9007199254740992n const tooLate = BigInt(rounded); // 9007199254740993n const exact = BigInt( "9007199254740993" );둘 다 BigInt지만 같은 정수는 아닙니다. 정확한 문자열에서 바로 변환해야 합니다. 다시 JSON으로 보낼 때는 기본
JSON.stringify가 BigInt를 그대로 처리하지 못하므로 API 계약에 맞게 문자열로 직렬화합니다. BigInt 변환과 JSON서버에서도 같은 점검이 필요합니다. DB 드라이버가 큰 ID를 부정확한 숫자 타입으로 읽은 뒤 JSON 문자열로 내보냈다면 브라우저에 도착할 때 이미 틀렸습니다. DB에서 읽은 값, 서버 객체의 타입, 직렬화된 응답, 브라우저 객체를 순서대로 대조해 처음 달라지는 지점을 찾습니다. 실제 식별자를 공개 로그에 남기는 대신 같은 경계를 가진 테스트 값으로 확인할 수 있습니다.
응답 형식을 당장 바꿀 수 없다면
일반적인
JSON.parse의 reviver는 파싱 뒤의 값을 받습니다. 그 콜백에서BigInt(value)를 호출하는 것만으로는 늦습니다. 현재 명세에는 primitive 값의 원문 토큰을 읽는context.source가 있습니다. 지원하는 런타임에서는 이를 이용할 수 있습니다. JSON.parse의 원문 접근const record = JSON.parse( '{"id":9007199254740993,' + '"label":"sample"}', (key, value, context) => { if (key !== "id") { return value; } if ( typeof value !== "number" || typeof context?.source !== "string" || !/^(0|[1-9][0-9]*)$/.test( context.source ) ) { throw new TypeError( "원문 정수 토큰을 " + "확인할 수 없습니다." ); } return context.source; } );이 예시는
id가 한 곳에만 있는 고정된 응답을 가정하며, 결과 ID는 문자열입니다. 지수나 소수 표현은 거부합니다. 런타임이context.source를 지원하지 않으면 실패하도록 했습니다. 지원 여부를 무시하고 파싱된 숫자를 대신 문자열로 만들면 손실을 숨기게 됩니다.중첩 객체에 이름만 같은
id가 여러 개 있다면 이 함수를 그대로 범용 변환기로 사용하지 않습니다. 각 필드의 의미와 경로를 포함한 스키마가 필요합니다. 응답 형식을 바꿀 수 있다면 생산자 쪽의 문자열 계약이 이런 변환 조건을 줄여 줍니다.경계 양쪽을 왕복시키세요
검사 입력에는 안전 범위 안의 값과 경계 밖의 서로 이웃한 값 두 개를 넣습니다. 정상 문자열뿐 아니라 숫자 타입, 누락, 빈 문자열도 넣어 계약을 어기는 응답이 거절되는지 봅니다. 앞자리 0을 허용한다면 그것도 유지돼야 합니다.
성공 기준은 출력 로그가 길어 보이는 것이 아닙니다. 서로 다른 ID가 서로 다르게 남고, 다음 요청을 받은 쪽에서도 같은 문자열을 읽어야 합니다. 응답의 공백이나 JSON 속성 순서는 달라도 됩니다. 처음부터 끝까지 지켜야 하는 것은 대상을 가리키는 ID의 정확한 문자입니다.
728x90반응형'Data Analysis' 카테고리의 다른 글
CSV를 열었는데 수식이 실행된다면: 따옴표만으로 부족한 이유 (0) 2026.10.06 CSV 한글이 Excel에서만 깨질 때: UTF-8과 가져오기 경로 (0) 2026.10.06 JSON에 같은 키가 두 번 있으면 어느 값이 남을까 (0) 2026.10.06 제외 목록에 NULL 하나 넣었더니 SQL 결과가 사라지는 이유 (1) 2026.10.05 UNIQUE를 걸었는데 빈 값이 여러 개 들어가는 이유 (0) 2026.10.05