{"record":{"id":"f4c3c32aee207d88","repo":"PaddlePaddle/PaddleOCR","slug":"asset-assetname-must-be-an-object","errorCode":null,"errorMessage":"Asset \"${assetName}\" must be an object.","messagePattern":"Asset \"(.+?)\" must be an object\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"paddleocr-js/packages/core/src/resources/model-asset.ts","lineNumber":70,"sourceCode":"  return typeof value === \"string\" && value.length > 0;\n}\n\nfunction isObject(value: unknown): value is Record<string, unknown> {\n  return Boolean(value && typeof value === \"object\" && !Array.isArray(value));\n}\n\nexport function normalizeModelAsset(assetName: string, asset: unknown): ModelAsset {\n  if (isNonEmptyString(asset)) {\n    const resolvedAsset = DEFAULT_MODEL_ASSETS[asset];\n    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- runtime guard for missing Record key\n    if (!resolvedAsset) {\n      throw new Error(`Asset \"${assetName}\" references unknown model asset \"${asset}\".`);\n    }\n    return { url: resolvedAsset.url };\n  }\n\n  if (!isObject(asset)) {\n    throw new Error(`Asset \"${assetName}\" must be an object.`);\n  }\n  if (!isNonEmptyString(asset.url)) {\n    throw new Error(`Asset \"${assetName}\" must define url.`);\n  }\n\n  return {\n    url: asset.url\n  };\n}\n\nexport function normalizeAssets(\n  assets: Record<string, unknown> | undefined\n): Record<string, ModelAsset> {\n  const assetEntries = Object.entries(assets || {});\n\n  if (assetEntries.length === 0) {\n    throw new Error(\"Assets must define at least one asset.\");\n  }","sourceCodeStart":52,"sourceCodeEnd":88,"githubUrl":"https://github.com/PaddlePaddle/PaddleOCR/blob/2661c7c0ef5c613e8f93c6e93b2e052399f0f854/paddleocr-js/packages/core/src/resources/model-asset.ts#L52-L88","documentation":"Thrown by normalizeModelAsset() when the asset value is neither a non-empty string (built-in name) nor a plain object - e.g. a number, boolean, null, or array. Each asset entry must be a preset name string or an object with a url field; anything else is rejected at normalization time before any network activity.","triggerScenarios":"Passing assets: { det: 42 }, { det: null }, { det: [\"https://...\"] }, or spreading undefined over a default entry; JSON configs where the asset got serialized incorrectly.","commonSituations":"Config built from environment variables without casting (\"\" or numbers); YAML/JSON pipeline where the asset key is present but the value is a list or null; accidental nesting like { det: { model: { url } } }.","solutions":["Set each asset entry to a string preset name or { url: \"...\" }","Validate the config shape before calling create() (see type guard)","If the value comes from JSON, add a schema check at load time"],"exampleFix":"// before\nconst ocr = await PaddleOCR.create({ assets: { det: null } });\n\n// after\nconst ocr = await PaddleOCR.create({ assets: { det: { url: \"https://host/det.tar\" } } });","handlingStrategy":"type-guard","validationCode":"function isPlainObject(v: unknown): v is Record<string, unknown> {\n  return Boolean(v) && typeof v === \"object\" && !Array.isArray(v);\n}","typeGuard":"function isAssetShape(v: unknown): v is string | { url: string } {\n  if (typeof v === \"string\") return v.length > 0;\n  return isPlainObject(v) && typeof (v as any).url === \"string\" && (v as any).url.length > 0;\n}","tryCatchPattern":null,"preventionTips":["Type your config as Record<string, string | { url: string }> so TS rejects numbers/null","Validate parsed JSON config before passing to create()"],"tags":["config","validation","type-error","model-assets"],"backgroundTag":null,"analyzedSha":"2661c7c0ef5c613e8f93c6e93b2e052399f0f854","analyzedAt":"2026-08-14T20:17:30.180Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}