nexu-io/open-design · error · Error
brand is not a JSON object
Error message
brand is not a JSON object
What it means
Thrown by validateBrand when the input is not a JSON object — null, an array, a primitive (string/number/bool), or undefined. validateBrand normalizes agent-produced or hand-authored brand JSON, and the very first invariant is that it must be an object. Earlier extraction usually runs the input through extractJsonBlock, which can yield null/array output.
Source
Thrown at apps/daemon/src/brands/validate.ts:52
function fontSpec(raw: unknown, fallbackFamily: string): BrandFontSpec {
const o = (raw ?? {}) as Record<string, unknown>;
return {
family: isStr(o.family) && o.family ? o.family : fallbackFamily,
fallbacks: strArr(o.fallbacks),
weights: Array.isArray(o.weights) ? o.weights.filter((w) => typeof w === 'number') : [400, 700],
...(isStr(o.googleFontsUrl) && o.googleFontsUrl ? { googleFontsUrl: o.googleFontsUrl } : {}),
...(isStr(o.notes) && o.notes ? { notes: o.notes } : {}),
};
}
/**
* Validate + normalize a brand object. Throws with a precise message on
* unrecoverable problems (missing name/colors); fills sensible defaults for
* everything optional so a slightly-sloppy input still renders.
*/
export function validateBrand(raw: unknown, sourceUrl: string): Brand {
if (!raw || typeof raw !== 'object') throw new Error('brand is not a JSON object');
const o = raw as Record<string, unknown>;
if (!isStr(o.name) || !o.name.trim()) throw new Error('brand: missing required `name`');
const rawColors = Array.isArray(o.colors) ? o.colors : [];
const colors: BrandColor[] = [];
for (const c of rawColors) {
if (!c || typeof c !== 'object') continue;
const co = c as Record<string, unknown>;
const role = BRAND_COLOR_ROLES.includes(co.role as BrandColorRole)
? (co.role as BrandColorRole)
: null;
const hex = isStr(co.hex) && /^#[0-9a-fA-F]{6}$/.test(co.hex) ? co.hex.toLowerCase() : null;
if (!role || !hex) continue;
colors.push({
role,
hex,
oklch: isStr(co.oklch) ? co.oklch : '',View on GitHub (pinned to 5be4028344)
Solutions
- Pre-check the parsed value with `typeof raw === 'object' && raw !== null && !Array.isArray(raw)` before validateBrand.
- Improve the extraction prompt to require a single JSON object, not a list, and to omit null.
- If extraction yields an array, take the first element only after validating it is an object.
- On validateBrand failure, re-prompt the agent with the error message rather than crashing the extraction.
Example fix
// before
const brand = validateBrand(parsed, sourceUrl);
// after
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
throw new Error('expected a brand JSON object from the extractor');
}
const brand = validateBrand(parsed, sourceUrl); Defensive patterns
Strategy: type-guard
Validate before calling
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
throw new Error('expected a brand JSON object from the extractor');
} Type guard
const isPlainObject = (v: unknown): v is Record<string, unknown> => typeof v === 'object' && v !== null && !Array.isArray(v);
Prevention
- Run extracted text through extractJsonBlock and check the result is a plain object.
- Prompt the extractor to emit a single JSON object, not a list or null.
- Re-prompt the agent with the validation error rather than crashing.
When it happens
Trigger: validateBrand(parsed, sourceUrl) where parsed came from JSON.parse of an LLM-produced text block that yielded an array ('[{...}]'), the string 'null', a bare primitive, or null because no JSON block was found.
Common situations: LLM wraps the brand in a list '[...]' or returns the literal 'null'; agent emits prose instead of JSON and extractJsonBlock falls back to grabbing text between { and } that is not an object; a hand-authored brand.json file containing a top-level array; an empty file.
Related errors
- brand: missing required `name`
- brand: `colors` must include at least the background, foregr
- brand.json failed validation: ${errorMessage(err)}
- invalid brand id: ${brandId}
- invalid brand id: ${id}
AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12).
Data as JSON: /api/errors/3d5cb9313dbc8b0b.
Report an issue: GitHub.