{"record":{"id":"c894ff49fc9e4bb3","repo":"affaan-m/ECC","slug":"expected-a-json-object","errorCode":null,"errorMessage":"Expected a JSON object","messagePattern":"Expected a JSON object","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/tasteforge/assets.py","lineNumber":84,"sourceCode":"    if len({identity(info) for info in (before, opened, after, final)}) != 1:\n        raise ValueError(f'File changed while hashing: {path}')\n    if modality == '3d_asset':\n        if len(header) != 12:\n            raise ValueError(f'Truncated GLB header: {path}')\n        magic, version, size = struct.unpack('<4sII', header)\n        if magic != b'glTF' or version != 2 or size != final.st_size:\n            raise ValueError(f'Invalid GLB magic, version or declared size: {path}')\n    return dict(path=str(path), bytes=final.st_size, sha256=digest.hexdigest())\n\n\ndef _load(path: Path) -> dict[str, Any]:\n    _fingerprint(path)\n    try:\n        data = json.loads(path.read_text(encoding='utf-8'))\n    except (ValueError, OSError) as exc:\n        raise ValueError(f'Cannot read JSON object: {path}') from exc\n    if not isinstance(data, dict):\n        raise ValueError('Expected a JSON object')\n    return data\n\n\ndef _assets(value: Any) -> list[dict[str, Any]]:\n    if not isinstance(value, list) or not value:\n        raise ValueError('assets must be a nonempty list')\n    ids = set()\n    for asset in value:\n        if not isinstance(asset, dict):\n            raise ValueError('Each asset must be an object')\n        asset_id = _text(asset.get('id'), 'asset id')\n        if asset_id in ids:\n            raise ValueError(f'Duplicate asset id: {asset_id}')\n        ids.add(asset_id)\n        if asset.get('modality') not in tuple(MODALITIES):\n            raise ValueError('modality must be image, video or 3d_asset')\n        if asset.get('origin') not in tuple(ORIGINS):\n            raise ValueError('Invalid asset origin')","sourceCodeStart":66,"sourceCodeEnd":102,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/tasteforge/assets.py#L66-L102","documentation":"After successfully parsing the file, _load requires the top-level JSON value to be a dict. A valid JSON array, string, number, or null raises ValueError('Expected a JSON object') (raised without the path). The library treats manifests as keyed objects only.","triggerScenarios":"A manifest file whose root is a JSON array (e.g., starts with '['), string, or number — parsed fine but not a dict — passed to _load via _bundle, ingest_assets, or validate_assets.","commonSituations":"Someone exported just the assets list instead of the full manifest object; a tool produced [{...},{...}] at top level; user wrote a bare array of assets to save typing.","solutions":["Wrap the content in an object with the expected key: {\"assets\": [...]}.","Check the producing tool's output format and use its full manifest, not a partial export.","Confirm the file starts with '{' not '[': head -c 1 manifest.json."],"exampleFix":"// before\n[{\"id\": \"a\", \"modality\": \"image\", \"origin\": \"local\", \"path\": \"a.png\"}]\n\n// after\n{\"assets\": [{\"id\": \"a\", \"modality\": \"image\", \"origin\": \"local\", \"path\": \"a.png\"}]}","handlingStrategy":"type-guard","validationCode":"import json\ndata = json.load(open(path))\nassert isinstance(data, dict), 'manifest root must be an object'","typeGuard":"def is_manifest(v) -> bool:\n    return isinstance(v, dict) and isinstance(v.get('assets'), list)","tryCatchPattern":"try:\n    data = _load_manifest(path)\nexcept ValueError as e:\n    if str(e) == 'Expected a JSON object':\n        wrap_raw_list_into_object(path)","preventionTips":["Check the first character of the file is '{'","Always export full manifests from tooling, not partial arrays","Add a schema check (jsonschema) before invoking the library"],"tags":["json","schema","manifest"],"backgroundTag":"unexpected-response-shape","analyzedSha":"8321021c54d670126ce3b2969d5deb880b4b0c2a","analyzedAt":"2026-09-16T10:08:13.343Z","contentChangedAt":"2026-09-16T10:08:13.343Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}