roboflow/supervision · error · ValueError

CreateML annotation file must contain a JSON list at the roo

Error message

CreateML annotation file must contain a JSON list at the root, got {type(createml_data).__name__}.

What it means

Raised by load_createml_annotations when the parsed JSON root is not a list. The CreateML annotation format is a JSON array of {image, annotations} objects; read_json_file may return a dict (object root) or other types, and this check fails fast with the actual type name.

Source

Thrown at src/supervision/dataset/formats/createml.py:174

        - ``image_paths`` (``list[str]``): canonical resolved path for every
          entry in the JSON, in file order.
        - ``annotations`` (``dict[str, Detections]``): mapping from canonical
          resolved image path to its ``Detections``.

    Raises:
        ValueError: If the JSON root is not a list.
        ValueError: If an entry is missing the required ``"image"`` key.
        ValueError: If an annotation is missing required coordinate or label keys.
        ValueError: If two entries resolve to the same image path.
        ValueError: If an annotation's ``image`` field resolves to the images
            directory itself or to a path outside it (e.g. via ``..`` traversal
            or an absolute path).
    """
    createml_data = cast(
        "list[CreateMLDict]", read_json_file(file_path=annotations_path)
    )
    if not isinstance(createml_data, list):
        raise ValueError(
            f"CreateML annotation file must contain a JSON list at the root, "
            f"got {type(createml_data).__name__}."
        )

    try:
        classes = sorted(
            {
                annotation["label"]
                for entry in createml_data
                for annotation in (entry.get("annotations") or [])
            }
        )
    except (KeyError, TypeError) as exc:
        raise ValueError(
            f"Malformed CreateML annotation entry "
            f"(missing or non-string 'label'): {exc}"
        ) from exc
    class_to_index = {class_name: index for index, class_name in enumerate(classes)}

View on GitHub (pinned to 7f254d9784)

Solutions

  1. Verify the file starts with '[' — CreateML format is a top-level array of entries.
  2. If your data is COCO format, use load_coco_annotations instead.
  3. If wrapped in an object, unwrap: data = json.load(f); entries = data['images'] and re-save as a list.

Example fix

// before: file content
{"images": [{"image": "a.jpg", "annotations": []}]}

// after
[{"image": "a.jpg", "annotations": []}]
Defensive patterns

Strategy: type-guard

Validate before calling

import json
from pathlib import Path

def load_createml_list(annotations_path: str) -> list:
    """Parse CreateML JSON and assert a list root before calling the loader."""
    data = json.loads(Path(annotations_path).read_text())
    if not isinstance(data, list):
        raise TypeError(f"Expected JSON list root, got {type(data).__name__}")
    return data

Type guard

def is_createml_format(data: object) -> bool:
    """True when data is a list of dicts with an 'image' key."""
    return isinstance(data, list) and all(isinstance(e, dict) and "image" in e for e in data)

Try / catch

try:
    sv.DetectionDataset.from_createml(images_directory_path=d, annotations_path=a)
except ValueError as exc:
    if "JSON list at the root" in str(exc):
        raise TypeError(f"{a} is not a CreateML file; check the format (COCO?)") from exc
    raise

Prevention

When it happens

Trigger: Calling load_createml_annotations / DetectionDataset.from_createml with a file whose root is a JSON object (e.g. {"images": [...]}, a COCO-style file) or any non-array JSON.

Common situations: Passing a COCO annotations JSON to the CreateML loader by mistake; tool exporting CreateML data wrapped in an object with metadata; passing the wrong file path (e.g. a config JSON).

Related errors


AI-assisted analysis of roboflow/supervision@7f254d9784 (2026-08-15). Data as JSON: /api/errors/2e50d7561ead3ade. Report an issue: GitHub.