{"record":{"id":"3e456e1d355ecf02","repo":"QuantumNous/new-api","slug":"json-must-be-an-object","errorCode":null,"errorMessage":"JSON must be an object","messagePattern":"JSON must be an object","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"web/src/features/models/components/dialogs/update-config-dialog.tsx","lineNumber":69,"sourceCode":"  traffic_port: z.coerce.number().int().min(1).max(65535).optional(),\n  entrypoint: z.string().optional(),\n  args: z.string().optional(),\n  command: z.string().optional(),\n  registry_username: z.string().optional(),\n  registry_secret: z.string().optional(),\n  env_json: z.string().optional(),\n  secret_env_json: z.string().optional(),\n})\n\ntype Values = z.input<typeof schema>\n\nconst UPDATE_CONFIG_FORM_ID = 'update-config-form'\n\nfunction normalizeJsonObject(input?: string) {\n  if (!input || !input.trim()) return undefined\n  const parsed = JSON.parse(input)\n  if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {\n    throw new Error('JSON must be an object')\n  }\n  return Object.fromEntries(\n    Object.entries(parsed as Record<string, unknown>).map(([k, v]) => [\n      k,\n      String(v),\n    ])\n  ) as Record<string, string>\n}\n\nexport function UpdateConfigDialog({\n  open,\n  onOpenChange,\n  deploymentId,\n}: {\n  open: boolean\n  onOpenChange: (open: boolean) => void\n  deploymentId: string | number | null\n}) {","sourceCodeStart":51,"sourceCodeEnd":87,"githubUrl":"https://github.com/QuantumNous/new-api/blob/e2c7aa7b102c2075eae2377df3508658d45e88dc/web/src/features/models/components/dialogs/update-config-dialog.tsx#L51-L87","documentation":"Thrown by normalizeJsonObject in the update-config dialog when parsing env_json or secret_env_json succeeds but the result is not a plain object (null, array, or primitive). It is a plain English literal (not i18n'd) raised during form submit/normalization before the config update is sent.","triggerScenarios":"Entering env_json like [\"A=1\"] or \"A=1\" (bare string) or null in the update-config form; JSON.parse succeeds, the object check fails, submission aborts.","commonSituations":"Users paste a JSON array of env entries or a single-quoted string; copy env objects from docker-compose YAML converted incorrectly; assume null clears the field (empty string is the supported way).","solutions":["Use a top-level object with string values: {\"ENV_NAME\": \"value\"}","Wrap arrays as an object keyed by name instead of a list","Leave the field blank/empty to omit it — blank returns undefined without parsing","Ensure all values are strings (the normalizer String()s them, but start clean)"],"exampleFix":"// before\nsecret_env_json: '[\"API_KEY=xyz\"]'\n// after\nsecret_env_json: '{\"API_KEY\": \"xyz\"}'","handlingStrategy":"validation","validationCode":"const isJsonObjectString = (s?: string): boolean => {\n  if (!s?.trim()) return true\n  try {\n    const v: unknown = JSON.parse(s)\n    return !!v && typeof v === 'object' && !Array.isArray(v)\n  } catch { return false }\n}\n// disable submit unless isJsonObjectString(values.env_json) && isJsonObjectString(values.secret_env_json)","typeGuard":"const isPlainObject = (v: unknown): v is Record<string, unknown> =>\n  typeof v === 'object' && v !== null && !Array.isArray(v)","tryCatchPattern":"try {\n  const env = normalizeJsonObject(values.env_json)\n  const secretEnv = normalizeJsonObject(values.secret_env_json)\n  // proceed with update\n} catch (error) {\n  toast.error((error as Error).message) // 'JSON must be an object' or JSON.parse SyntaxError\n}","preventionTips":["Validate env_json/secret_env_json shape on blur with a zod refinement, not only on submit","Accept blank as 'omit' and document that null is not a valid value","Wrap JSON.parse and rethrow a descriptive field-specific error"],"tags":["validation","json","env-config","update-config","shape-error"],"backgroundTag":null,"analyzedSha":"e2c7aa7b102c2075eae2377df3508658d45e88dc","analyzedAt":"2026-08-15T10:35:18.111Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}