Python で JSON を安全かつ効率的にパースする実践手法

JSON(JavaScript Object Notation)は、Web API や設定ファイル、データ交換の標準フォーマットとして広く採用されています。Python は標準ライブラリ json を通じて、文字列やファイルからの JSON データのデシリアライズをネイティブにサポートしており、型安全性やエラー処理を意識した使い方が可能です。

基本的なパースフロー

JSON を Python のネイティブオブジェクト(dictliststrintfloatboolNone)に変換するには、以下のパターンが主です:

  • json.loads():JSON 形式の文字列からオブジェクトへ
  • json.load():ファイルオブジェクト(テキストモード)から直接読み込み

安全な文字列パース例(バリデーション付き)

不正な JSON 入力への耐性を持つため、例外処理と型検証を組み合わせます:

import json
from typing import Dict, List, Optional, Any

def parse_person_json(raw: str) -> Optional[Dict[str, Any]]:
    try:
        payload = json.loads(raw)
        # 基本フィールドの存在確認
        if not isinstance(payload, dict):
            raise ValueError("root must be a JSON object")
        if "full_name" not in payload or "years" not in payload:
            raise ValueError("required keys 'full_name' and 'years' missing")
        if not isinstance(payload["years"], (int, float)) or payload["years"] < 0:
            raise ValueError("invalid 'years' value")
        return payload
    except json.JSONDecodeError as e:
        print(f"JSON syntax error at position {e.pos}: {e.msg}")
        return None
    except ValueError as e:
        print(f"Data validation failed: {e}")
        return None

# 使用例
sample = '{"full_name": "Yuki Tanaka", "years": 29, "tags": ["backend", "devops"]}'
parsed = parse_person_json(sample)
if parsed:
    print(f"Person: {parsed['full_name']}, Age: {parsed['years']}")

ファイルからのロードと構造化アクセス

JSON ファイルを読み込む際は、エンコーディング指定とコンテキストマネージャーを活用し、リソースリークを防ぎます:

import json
from pathlib import Path

def load_config(path: Path) -> dict:
    try:
        with path.open("r", encoding="utf-8") as f:
            return json.load(f)
    except FileNotFoundError:
        raise RuntimeError(f"Config file not found: {path}")
    except UnicodeDecodeError as e:
        raise RuntimeError(f"Invalid encoding in {path}: {e}")
    except json.JSONDecodeError as e:
        raise RuntimeError(f"Malformed JSON in {path} at line {e.lineno}")

# 設定ファイル config.json の想定内容:
# {"database": {"host": "localhost", "port": 5432}, "debug": true}

config = load_config(Path("config.json"))
db_host = config.get("database", {}).get("host", "127.0.0.1")
is_debug = config.get("debug", False)
print(f"DB Host: {db_host}, Debug Mode: {is_debug}")

ネストされた配列と動的キーの扱い

複雑な構造でも、.get() とデフォルト値を活用することで安全にアクセスできます:

api_response = '''
{
  "status": "success",
  "items": [
    {"id": 101, "meta": {"category": "web", "score": 92.5}},
    {"id": 102, "meta": {"category": "mobile", "score": 87.3}}
  ]
}
'''

data = json.loads(api_response)
for item in data.get("items", []):
    meta = item.get("meta", {})
    category = meta.get("category", "unknown")
    rating = round(meta.get("score", 0.0))
    print(f"ID {item.get('id', '?')} → {category.upper()}: {rating}/100")

タグ: Python JSON Serialization error-handling typing

6月7日 17:06 投稿