PaddlePaddle/PaddleOCR · error · ValueError

{path}: missing 'items' array

Error message

{path}: missing 'items' array

What it means

Raised by _load_items in compare_ocr_json.py when a JSON file parses successfully but has no top-level "items" key holding a list. The comparison protocol expects the schema {"items": [{"polygon": ..., "text": ...}, ...]}; anything else (different key name, items as an object, wrong file) fails this validation.

Source

Thrown at deploy/ios_demo/scripts/compare_ocr_json.py:97

        return Polygon(pts)

    p1 = _to_poly(poly_a)
    p2 = _to_poly(poly_b)
    if p1.is_empty or p2.is_empty or not p1.is_valid or not p2.is_valid:
        return 0.0
    inter = p1.intersection(p2).area
    union = p1.union(p2).area
    if union <= 0:
        return 0.0
    return float(inter / union)


def _load_items(path: Path) -> List[Dict[str, Any]]:
    with path.open("r", encoding="utf-8") as f:
        data = json.load(f)
    items = data.get("items")
    if not isinstance(items, list):
        raise ValueError(f"{path}: missing 'items' array")
    return items


def _greedy_match(
    ref_items: List[Dict[str, Any]],
    hyp_items: List[Dict[str, Any]],
    iou_threshold: float,
) -> Tuple[List[Tuple[int, int, float]], List[int], List[int]]:
    """Return (pairs as ref_idx, hyp_idx, iou), unmatched_ref, unmatched_hyp."""
    candidates: List[Tuple[float, int, int]] = []
    for i, ri in enumerate(ref_items):
        ra = ri.get("polygon")
        if not isinstance(ra, list):
            continue
        for j, hj in enumerate(hyp_items):
            ha = hj.get("polygon")
            if not isinstance(ha, list):
                continue

View on GitHub (pinned to 2661c7c0ef)

Solutions

  1. Rewrite both input files to the expected schema: a top-level object whose "items" value is an array of {"polygon", "text"} entries.
  2. If the data lives under a different key, rename it to "items" (e.g. with a small jq/python transform).
  3. Open the file and verify with python -c "import json;print(type(json.load(open('f.json')).get('items')))" before comparing.

Example fix

# before
{"results": [{"polygon": [[0,0],[1,0],[1,1],[0,1]], "text": "hi"}]}

# after
{"items": [{"polygon": [[0,0],[1,0],[1,1],[0,1]], "text": "hi"}]}
Defensive patterns

Strategy: validation

Validate before calling

import json

def load_items_checked(path: str) -> list:
    with open(path, encoding="utf-8") as f:
        data = json.load(f)
    items = data.get("items")
    assert isinstance(items, list) and items, f"{path}: expected non-empty 'items' array"
    return items

load_items_checked(ref_path); load_items_checked(hyp_path)

Type guard

def has_items_schema(data) -> bool:
    return (
        isinstance(data, dict)
        and isinstance(data.get("items"), list)
        and all(isinstance(it, dict) for it in data["items"])
    )

Try / catch

try:
    items = _load_items(path)
except ValueError as e:
    if "missing 'items'" in str(e):
        raise ValueError(f"{path}: wrong schema — expected {{\"items\": [...]}}") from e
    raise

Prevention

When it happens

Trigger: Passing a raw detection output file, an export from a different tool, or a JSON where the array is stored under another key (e.g. "results", "regions") or nested one level deeper.

Common situations: Comparing reference annotations against iOS demo output when one side was regenerated by a newer exporter with a changed schema; feeding an arbitrary OCR JSON downloaded or hand-written in a different shape; empty JSON object {} from a failed upstream export.

Related errors


AI-assisted analysis of PaddlePaddle/PaddleOCR@2661c7c0ef (2026-08-14). Data as JSON: /api/errors/800d431dc934f6b0. Report an issue: GitHub.