JSONPath를 활용한 중첩 JSON 데이터 추출 기법

API 응답으로 받은 JSON 데이터는 계층 구조로 깊게 중첩되어 있는 경우가 많습니다. Python의 json 모듈로 파싱하면 dict 형태가 되지만, 깊은 곳의 값을 꺼내려면 여러 단계의 키 접근이나 반복문이 필요합니다. 특히 필드명이 동적으로 변하거나 조건 필터링이 필요할 때 코드가 복잡해집니다.

문제 상황

다음과 같은 응답 구조에서 특정 데이터를 추출해야 한다고 가정해 봅시다.

payload = {
    "status": 200,
    "message": "조회 완료",
    "result": {
        "members": [
            {"nickname": "Alpha", "sex": "male", "score": 88},
            {"nickname": "Beta", "sex": "female", "score": 92},
            {"nickname": "Gamma", "sex": "male", "score": 76},
            {"nickname": "Delta", "sex": "female", "score": 95},
        ],
        "items": [
            {"label": "keyboard", "cost": 120, "stock": 50},
            {"label": "mouse", "cost": 35, "stock": 200},
            {"label": "monitor", "cost": 300, "stock": 30},
        ]
    }
}

세 가지 요구사항을 해결해야 합니다:

  1. 모든 회원의 닉네임 목록 수집
  2. 필드명이 members에서 members_2024, members_567 등으로 동적 변경될 때 대응
  3. 점수가 90 이상인 여성 회원만 필터링

JSONPath 소개

JSONPath는 JSON 데이터를 XPath 스타일로 탐색하기 위한 쿼리 언어입니다. 복잡한 중첩 구조와 조건 필터링을 간결하게 처리할 수 있습니다.

설치

pip install jsonpath

핵심 문법

표현식의미
$루트 노드
.직계 자식 접근
..모든 하위 노드 재귀 탐색
*임의의 노드 매칭
[n]인덱스 접근
[start:end]슬라이싱
[?(@.field op value)]조건 필터

실전 적용 예제

from jsonpath import jsonpath

payload = {
    "status": 200,
    "message": "조회 완료",
    "result": {
        "members": [
            {"nickname": "Alpha", "sex": "male", "score": 88},
            {"nickname": "Beta", "sex": "female", "score": 92},
            {"nickname": "Gamma", "sex": "male", "score": 76},
            {"nickname": "Delta", "sex": "female", "score": 95},
        ],
        "items": [
            {"label": "keyboard", "cost": 120, "stock": 50},
            {"label": "mouse", "cost": 35, "stock": 200},
            {"label": "monitor", "cost": 300, "stock": 30},
        ]
    }
}

# 1. 모든 닉네임 추출
names = jsonpath(payload, "$.result.members[*].nickname")
print(names)  # ['Alpha', 'Beta', 'Gamma', 'Delta']

# 2. 동적 필드명 대응 - result 하위 임의 필드의 모든 name 계열 키값
dynamic_names = jsonpath(payload, "$.result.*[*].nickname")
print(dynamic_names)

# 3. 조건 기반 필터링: score 90 이상이면서 female인 회원
filtered = jsonpath(
    payload, 
    "$.result.members[?(@.score >= 90 && @.sex == 'female')]"
)
print(filtered)
# [{'nickname': 'Beta', 'sex': 'female', 'score': 92}, 
#  {'nickname': 'Delta', 'sex': 'female', 'score': 95}]

# 4. 재귀 탐색으로 모든 label 값 수집 (items 내부)
labels = jsonpath(payload, "$..label")
print(labels)  # ['keyboard', 'mouse', 'monitor']

# 5. 슬라이싱: 첫 번째와 두 번째 데이터만
top_two = jsonpath(payload, "$.result.members[0:2]")
print(top_two)

# 6. 특정 인덱스 직접 접근
third = jsonpath(payload, "$.result.members[2]")
print(third)  # [{'nickname': 'Gamma', 'sex': 'male', 'score': 76}]

고급 활용 패턴

다중 조건 및 비교 연산

# 재고가 100개 미만이면서 가격이 100 초과인 상품
premium_low_stock = jsonpath(
    payload,
    "$.result.items[?(@.cost > 100 && @.stock < 100)]"
)

부모 참조 없이 전역 검색

# 문서 전체에서 nickname 필드를 가진 객체 검색
all_users = jsonpath(payload, "$..[?(@.nickname)]")

결과가 없을 때의 처리

# 매칭 결과 없으면 False 반환, 주의 필요
result = jsonpath(payload, "$.nonexistent.path")
# result == False

# 안전하게 사용하려면
def safe_jsonpath(data, expr, default=None):
    res = jsonpath(data, expr)
    return res if res is not False else default

주의사항

  • jsonpath() 함수는 결과가 없으면 False를 반환하므로, 리스트 연산이나 조건 분기 시 주의가 필요합니다.
  • 복잡한 논리 연산은 라이브러리 버전에 따라 지원 범위가 다를 수 있어 공식 문서 확인이 권장됩니다.
  • 대용량 JSON의 경우 과도한 재귀 탐색(..)은 성능 저하를 유발할 수 있습니다.

태그: JSONPath python JSON Parsing Data Extraction API Response Handling

6월 1일 20:07에 게시됨