2026년 08월 22일 | DBMS Error 가이드
이 글에서 다루는 내용
22037 에러의 원인 분석, 해결 SQL, 예방 방법을 실무 관점에서 정리합니다.
22037 non unique keys in a json object 는?
PostgreSQL 에러 코드 22037은 JSON 객체 내에 동일한 키(key)가 두 번 이상 사용되었을 때 발생하는 에러입니다. JSON 표준(RFC 7159)은 기술적으로 중복 키를 허용하지만, PostgreSQL의 특정 함수와 연산자는 키의 유일성을 엄격하게 요구합니다. 주로 jsonb 타입을 다루거나 JSON 경로(JSON Path) 표현식을 사용할 때 이 에러와 마주칠 수 있으며, 외부 시스템에서 유입된 데이터나 애플리케이션 레벨에서 동적으로 생성된 JSON에서 빈번하게 나타납니다.
주요 발생 원인
1. 중복 키를 포함한 JSON 문자열을 jsonb로 캐스팅
가장 흔한 원인입니다. json 타입은 중복 키를 그대로 저장하지만, jsonb 타입은 내부적으로 키를 정렬하고 유일성을 보장하는 구조로 변환합니다. 외부 API, 레거시 시스템, 또는 잘못 설계된 애플리케이션에서 {"name": "Alice", "name": "Bob"} 같은 중복 키가 포함된 JSON을 수신하여 jsonb 컬럼에 삽입하거나 캐스팅하려 할 때 이 에러가 발생합니다. PostgreSQL 버전에 따라 경고(WARNING)로 처리되거나 에러로 처리되는 방식이 달라질 수 있으므로 주의가 필요합니다.
2. JSON 경로(jsonpath) 함수에서 중복 키 처리
jsonb_path_query, jsonb_path_exists, @? 연산자 등 jsonpath 관련 함수들은 엄격한 모드(strict mode)에서 중복 키를 감지하면 에러를 발생시킵니다. 예를 들어, json 타입으로 저장된 중복 키 데이터를 jsonpath로 조회하거나 변환하는 과정에서 에러가 발생할 수 있습니다. 특히 데이터 마이그레이션 또는 ETL 파이프라인에서 json 컬럼을 jsonb로 일괄 변환할 때 숨어 있던 중복 키 문제가 표면으로 드러나는 경우가 많습니다.
3. 애플리케이션 코드에서 동적 JSON 생성 시 키 중복
서버 사이드 코드(Python, Java, Node.js 등)에서 딕셔너리나 맵 구조 없이 문자열을 직접 조합하여 JSON을 생성할 때, 또는 여러 데이터 소스를 병합하는 과정에서 동일한 키가 중복으로 포함될 수 있습니다. 특히 ORM이나 쿼리 빌더를 사용하지 않고 원시(raw) SQL로 JSON을 조립할 때 json_build_object 함수에 동일한 키를 여러 번 전달하면 이 에러가 발생합니다. 이 경우 에러가 개발 환경이 아닌 프로덕션 환경의 특정 데이터 조건에서만 간헐적으로 나타나 디버깅이 까다롭습니다.
해결 방법
원인 1 해결: 중복 키 사전 감지 및 정규화
데이터를 jsonb로 삽입하기 전에 중복 키 여부를 확인하거나, json 타입으로 먼저 받아서 처리하는 방법을 사용합니다.
-- 1. 중복 키 포함 여부 확인 쿼리
-- json 타입은 중복 키를 허용하므로, 키 목록을 추출하여 중복 여부를 먼저 점검
SELECT
id,
raw_json,
(
SELECT COUNT(*) FROM json_object_keys(raw_json)
) AS total_keys,
(
SELECT COUNT(DISTINCT key) FROM json_object_keys(raw_json) AS key
) AS unique_keys
FROM staging_table
WHERE (
SELECT COUNT(*) FROM json_object_keys(raw_json)
) <> (
SELECT COUNT(DISTINCT key) FROM json_object_keys(raw_json) AS key
);
-- 2. 중복 키가 있는 json을 jsonb로 안전하게 변환
-- 마지막 값을 우선으로 중복 키 제거 (PostgreSQL은 기본적으로 마지막 키를 사용)
UPDATE my_table
SET jsonb_col = raw_json::jsonb
WHERE raw_json IS NOT NULL;
-- 3. 중복 키 데이터를 정제하여 삽입하는 함수 생성
CREATE OR REPLACE FUNCTION safe_to_jsonb(input_json json)
RETURNS jsonb
LANGUAGE sql
IMMUTABLE
AS $$
-- json -> text -> jsonb 변환 과정에서 PostgreSQL이 자동으로 마지막 키를 선택
-- 단, 이 동작은 버전에 따라 WARNING 또는 ERROR를 발생시킬 수 있음
SELECT input_json::text::jsonb;
$$;
원인 2 해결: jsonpath 쿼리에서 중복 키 우회
-- 문제 발생 예시: json 컬럼에 중복 키가 있을 경우 jsonb 변환 시 에러
-- 에러 재현
SELECT '{"id": 1, "id": 2}'::jsonb;
-- ERROR: 22037: non unique keys in a json object
-- 해결 방법 1: json_populate_record를 활용한 우회 처리
SELECT json_populate_record(
null::my_record_type,
'{"name": "Alice", "name": "Bob"}'::json
);
-- 해결 방법 2: 중복 키 데이터를 사전에 정제하는 CTE 활용
WITH raw_data AS (
SELECT id, data::json AS json_data
FROM external_staging
),
deduplicated AS (
SELECT
id,
-- 각 키의 첫 번째 값만 추출하여 jsonb 재구성
jsonb_object_agg(key, value ORDER BY ordinality) AS clean_jsonb
FROM raw_data,
LATERAL (
SELECT DISTINCT ON (key) key, value, ordinality
FROM json_each(json_data) WITH ORDINALITY AS t(key, value, ordinality)
ORDER BY key, ordinality ASC
) AS keys_dedup
GROUP BY id
)
SELECT id, clean_jsonb
FROM deduplicated;
-- 해결 방법 3: 배치 마이그레이션 시 예외 처리
DO $$
DECLARE
rec RECORD;
BEGIN
FOR rec IN SELECT id, raw_json FROM staging_table LOOP
BEGIN
UPDATE target_table
SET data = rec.raw_json::jsonb
WHERE id = rec.id;
EXCEPTION
WHEN sqlstate '22037' THEN
-- 중복 키 에러 발생 시 로그 테이블에 기록
INSERT INTO error_log (table_name, record_id, error_code, error_msg, created_at)
VALUES ('staging_table', rec.id, '22037', 'non unique keys in json', NOW());
END;
END LOOP;
END;
$$;
원인 3 해결: 애플리케이션 레벨의 안전한 JSON 생성
-- 잘못된 예: json_build_object에 중복 키 전달
-- 아래는 일부 버전에서 에러, 일부에서 경고 처리
SELECT json_build_object(
'user_id', 1001,
'status', 'active',
'status', 'inactive' -- 중복 키!
);
-- 올바른 예: 중복 없이 JSON 객체 구성
SELECT json_build_object(
'user_id', 1001,
'status', 'active',
'last_login', NOW()::text
);
-- 동적 JSON 조립 시 안전한 패턴: WITH 절로 키-값 쌍 정의 후 집계
WITH kv_pairs AS (
SELECT 'user_id' AS key, '1001'::text AS val, 1 AS priority
UNION ALL
SELECT 'status', 'active', 2
UNION ALL
SELECT 'status', 'inactive', 3 -- 중복 키를 priority로 제어
),
deduped AS (
SELECT DISTINCT ON (key) key, val
FROM kv_pairs
ORDER BY key, priority ASC -- 우선순위가 낮은(먼저 등록된) 값 사용
)
SELECT jsonb_object_agg(key, val) AS safe_json
FROM deduped;
-- 결과: {"status": "active", "user_id": "1001"}
-- 기존 jsonb에 키 추가 시 중복 방지 (|| 연산자 활용)
UPDATE user_profiles
SET profile_data = profile_data || '{"verified": true}'::jsonb
WHERE user_id = 1001;
-- jsonb || jsonb 연산은 오른쪽 값이 우선되어 자동으로 중복 키를 덮어씀
예방 방법
1. 입력 데이터 검증 레이어(Validation Layer) 구축
외부에서 유입되는 JSON 데이터는 DB에 저장되기 전에 반드시 검증 단계를 거쳐야 합니다. PostgreSQL의 CHECK 제약조건과 트리거를 활용하여 jsonb 컬럼에 데이터가 저장되기 전 중복 키 여부를 자동으로 감지하는 구조를 마련하세요.
-- jsonb 컬럼에는 타입 캐스팅 자체가 검증 역할을 수행
-- staging 테이블에서 json으로 받고, 뷰 또는 트리거로 jsonb 변환 시 예외 처리
CREATE OR REPLACE FUNCTION validate_no_duplicate_keys()
RETURNS TRIGGER AS $$
BEGIN
-- json 타입 컬럼의 중복 키 사전 검사
IF (
SELECT COUNT(*) FROM json_object_keys(NEW.raw_json)
) <> (
SELECT COUNT(DISTINCT k) FROM json_object_keys(NEW.raw_json) AS k
) THEN
RAISE EXCEPTION '22037: JSON object contains non unique keys in record id=%', NEW.id;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_validate_json_keys
BEFORE INSERT OR UPDATE ON staging_table
FOR EACH ROW EXECUTE FUNCTION validate_no_duplicate_keys();
2. 애플리케이션 레벨에서 jsonb 타입 우선 사용 및 코드 리뷰 강화
가능하면 json 타입 대신 jsonb 타입을 사용하면, 데이터 삽입 시점에 중복 키를 조기에 감지할 수 있습니다. CI/CD 파이프라인에 JSON 유효성 검사 단계를 추가하고, json_build_object 호출 시 중복 키를 검출하는 정적 분석 도구 또는 코드 리뷰 규칙을 팀 내에 정착시키는 것을 권장합니다.
관련 에러
- 22032 (invalid_json_text): JSON 문자열 자체가 문법적으로 올바르지 않을 때 발생합니다. 22037 에러와 함께 JSON 데이터 품질 문제의 양대 축을 이룹니다.
- 22P02 (invalid_text_representation): 잘못된 형식의 문자열을
jsonb로 캐스팅할 때 발생하며, 중복 키 에러와 혼동될 수 있습니다. - 22023 (invalid_parameter_value):
json_build_object등 JSON 빌더 함수에 잘못된 인자가 전달될 때 발생합니다. - 42804 (datatype_mismatch): JSON/JSONB 연산 중 타입 불일치로 발생하며, 중복 키 문제를 해결하는 과정에서 함께 마주칠 수 있습니다.
주요 DBMS error code를 정리하는 시리즈입니다.
블로그 홈에서 다른 에러도 확인하세요.
본 포스트는 AI가 생성한 기술 가이드입니다. 운영 환경 적용 전 충분한 검토를 권장합니다.