justjavac/wechat-miniapp-radar · error · Error
AI response JSON must be an object.
Error message
AI response JSON must be an object.
What it means
Thrown by parseJsonObject() in lib/ai-client.ts after the model's text is unwrapped (markdown fences stripped, then sliced to the first..last brace) and JSON.parse succeeds but yields a value that is not a plain object, i.e. null, an array, or a primitive. It is the last check enforcing the JSON-completion contract (an object) before the value is cast to T and returned from requestChatCompletion(). Note a genuinely malformed JSON string surfaces as a SyntaxError from JSON.parse on the line above, which is a different failure not handled here.
Source
Thrown at lib/ai-client.ts:104
}
return null;
}
function extractJsonText(text: string) {
const trimmed = text.trim();
const fenced = trimmed.match(/^```(?:json)?\s*([\s\S]*?)\s*```$/i);
if (fenced?.[1]) return fenced[1].trim();
const firstBrace = trimmed.indexOf("{");
const lastBrace = trimmed.lastIndexOf("}");
if (firstBrace >= 0 && lastBrace > firstBrace) return trimmed.slice(firstBrace, lastBrace + 1);
return trimmed;
}
function parseJsonObject<T>(text: string): T {
const parsed = JSON.parse(extractJsonText(text)) as unknown;
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
throw new Error("AI response JSON must be an object.");
}
return parsed as T;
}
function parseCompletionPayload(text: string) {
if (!text.trim()) return null;
try {
return JSON.parse(text) as ChatCompletionResponse;
} catch {
return null;
}
}
async function requestChatCompletion<T>({
config,
model,
messages,
timeoutMsView on GitHub (pinned to 02a010ecea)
Solutions
- Tighten the system prompt to require a single JSON object (never an array) and show the exact top-level shape with a literal example.
- Enable response_format json_object for a supporting model (OPENROUTER_JSON_RESPONSE_FORMAT_MODELS already does this for a curated set; pick a model from the set or extend it).
- Rely on createAiJsonCompletion()'s built-in recovery: it catches this throw, records it, and retries config.fallbackModel before returning ok:false.
- When ok is false, use the app's rule-based fallback for advisor/scoring.
Example fix
// before: a prompt like 'list the frameworks' invites a top-level array
// after: require a single object wrapper
// Respond with ONE JSON object, e.g. { recommendations: [...] }
// The top level MUST be an object, never an array. Defensive patterns
Strategy: type-guard
Type guard
function isJsonObject(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
} Try / catch
const result = await createAiJsonCompletion<MyType>({ messages });
if (!result.ok || !result.value) {
// result.error contains 'AI response JSON must be an object.'
return ruleBasedFallback();
}
return result.value; Prevention
- Pin the response shape in the system prompt with a literal example object.
- Prefer models already in OPENROUTER_JSON_RESPONSE_FORMAT_MODELS so response_format json_object is sent.
- Always branch on createAiJsonCompletion's ok/value instead of assuming the cast to T is valid.
When it happens
Trigger: The LLM returns valid JSON that is an array (e.g. [{...}]) or a bare primitive/string when the prompt asked for one object; extractJsonText() slices to the first/last brace so an array parses successfully and then fails the object check; a model wraps the answer as a quoted JSON string which parses to a string.
Common situations: Free/OpenRouter models that ignore response_format json_object and emit arrays; prompts that say 'return a list' while the schema expects an object wrapper; switching to a model whose default serialization differs; temperature/parameter changes that make the model return a quoted scalar.
Related errors
- OPENAI_API_KEY is not configured.
- AI provider rejected ${model}: ${providerError}
- AI provider returned an empty response for ${model}.
AI-assisted analysis of justjavac/wechat-miniapp-radar@02a010ecea (2026-08-12).
Data as JSON: /api/errors/af7cb13f39e65762.
Report an issue: GitHub.