Parsing nested JSON arguments in Python CLIs
The Challenge of Nested JSON in CLI Arguments
When passing complex configuration objects to terminal interfaces, developers frequently encounter json.decoder.JSONDecodeError. This occurs because bash and zsh strip outer quotes or trigger brace expansion. Standard parsers expect flat strings rather than nested objects.
Implementing robust deserialization aligns with modern Advanced Input Parsing & User Experience standards. Explicit parsing prevents silent data corruption. CLI tools must fail fast with actionable feedback instead of propagating invalid payloads downstream.
Minimal Production-Ready Implementation (Python 3.10+)
Avoid manual string manipulation or regex splitting. Register a dedicated parser function that intercepts raw CLI input. The function attempts JSON decoding and returns a native Python dictionary.
This pattern integrates directly with Advanced Argument Validation Strategies to enforce strict schema compliance. Business logic executes only after validation passes.
import argparse
import json
from typing import Any
from pydantic import BaseModel, ValidationError
class TaskConfig(BaseModel):
retries: int
endpoints: list[str]
metadata: dict[str, Any]
def parse_json_arg(value: str) -> dict:
try:
return json.loads(value)
except json.JSONDecodeError as e:
raise argparse.ArgumentTypeError(f"Invalid JSON: {e}")
parser = argparse.ArgumentParser()
parser.add_argument('--config', type=parse_json_arg, required=True)
args = parser.parse_args()
try:
validated = TaskConfig(**args.config)
except ValidationError as e:
print(f'Schema mismatch: {e}')
raise SystemExit(1)
Manage dependencies and runtime configuration using a standard pyproject.toml.
[project]
name = "cli-json-parser"
version = "0.1.0"
requires-python = ">=3.10"
dependencies = [
"pydantic>=2.0",
]
Execute the tool with properly formatted terminal commands.
# Install dependencies
pip install pydantic
# Run with properly escaped JSON
python cli.py --config '{"retries": 3, "endpoints": ["https://api.dev"], "metadata": {"env": "staging"}}'
Resolving Shell Expansion & Type Mismatch Errors
A common pitfall is the TypeError: the JSON object must be str, bytes or bytearray, not dict. This surfaces when unit testing frameworks pass dictionaries directly to the CLI entry point. Implement a type guard that checks isinstance(value, dict) before calling json.loads().
For terminal users, always document single-quote wrapping. This prevents shell globbing and ensures exact string transmission to the Python interpreter. For programmatic CLI generation, utilize shlex.quote() to safely escape dynamic payloads.