DeepSeek JSON Output: Live V4 Tests for Valid JSON, Schema Validation, and Empty Content

Original DeepSeek JSON Output tests across V4 Flash and Pro, covering response_format, thinking, prompt controls, truncation, streaming, JSON parsing, and local schema validation.

DeepSeek JSON Output is enabled by sending "response_format": {"type": "json_object"}, explicitly asking for JSON in the system or user prompt, showing the desired object shape, and allowing enough output tokens to finish it. DeepSeek’s official contract is about syntactically valid JSON. Your application must still verify required keys, primitive types, enums, null handling, ranges, and business rules before it trusts the result.

This guide separates three evidence types. Official means a statement appears in DeepSeek’s current documentation. Live means a dated result from the bounded V4 test matrix and includes an exact denominator. Offline means a deterministic parser or validator fixture that made no provider request. The live and offline result fields below are populated from reviewed, sanitized artifacts.

Official documentation was reviewed on July 27, 2026 UTC. Chat-Deep.ai is an independent technical publication and is not affiliated with or endorsed by DeepSeek. The existing canonical URL remains https://chat-deep.ai/docs/json-output/; this page belongs in the Docs category and uses no WordPress tags.

Live — July 27, 2026 UTC: the bounded execution ran from 09:44:48.338Z to 09:48:42.812Z. One GET /models request returned HTTP 200 in 808.470 ms and listed deepseek-v4-flash and deepseek-v4-pro. The 20 planned completion requests produced 18 HTTP 200 responses and two bounded HTTP 400 controls. Final content was non-empty in 18/18 successful completions; 17/18 parsed as JSON, 17/18 passed the exact local schema, and 16/18 matched every reference fact. Both streaming tests, 2/2, reached [DONE]. The two repetitions in each core configuration are descriptive observations, not population reliability estimates.

DeepSeek JSON Output requirements

Official: DeepSeek’s JSON Output guide lists four practical requirements and cautions. The current Chat Completion reference adds an important whitespace warning and defines the accepted response_format.type values.

RequirementWhy it mattersProduction check
Set response_format.type to json_objectEnables DeepSeek JSON Output instead of the default text formatAssert the request body contains the exact object
Include the word json in a system or user promptThe official guide requires an explicit JSON instructionValidate the final prompt after templates are assembled
Provide an example of the desired JSON shapeGuides the model toward the intended structureUse an example with the same keys and primitive types
Set max_tokens reasonablyAn insufficient allowance can cut the JSON string off midwayTreat finish_reason: "length" as potentially incomplete
Handle empty contentDeepSeek says JSON Output may occasionally return empty contentCheck for a non-empty string before calling JSON.parse
Official requirements summarized from DeepSeek’s JSON Output guide and Chat Completion reference, checked July 27, 2026 UTC.
Official DeepSeek JSON Output notice listing response_format json_object, the JSON prompt instruction, example shape, max_tokens, and empty-content guidance.
Official DeepSeek JSON Output notice, captured July 27, 2026 UTC. The image contains public documentation only.

The reference says response_format.type accepts text or json_object, and text is the default. It also warns that enabling JSON Output without instructing the model to produce JSON can lead to a stream of whitespace until the output limit, making the request appear stuck. This is why the API control and the prompt instruction belong together.

Minimal raw HTTPS request

curl https://api.deepseek.com/chat/completions \
  -H "Authorization: Bearer $DEEPSEEK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "deepseek-v4-flash",
    "messages": [
      {
        "role": "system",
        "content": "Return JSON only. Use exactly this shape: {\"ticket_id\":\"T-204\",\"issue\":\"account_access\",\"reset_attempts\":3,\"order_id\":null,\"urgent\":true}"
      },
      {
        "role": "user",
        "content": "Ticket T-204: The customer cannot sign in after three failed password resets. No order ID was provided."
      }
    ],
    "response_format": {"type": "json_object"},
    "thinking": {"type": "disabled"},
    "max_tokens": 128,
    "stream": false
  }'

The explicit non-thinking setting is an editorial starting configuration for a small extraction task, not an official claim that thinking must be disabled for JSON. DeepSeek’s current model documentation says both V4 models support JSON Output and both thinking modes. The original matrix therefore tests enabled and disabled modes instead of assuming one is universally better.

Valid JSON is not the same as schema-valid data

JSON.parse answers a narrow question: is this string valid JSON syntax? It does not prove that the top-level value is an object, that required keys exist, that unexpected keys are absent, or that values have the right types. A response can be valid JSON and still be unusable or unsafe for the application.

Validation layerExample failure that still passes earlier layersRequired action
Non-empty contentAn empty string has no JSON value to parseReject or follow a bounded recovery policy
JSON syntax{"urgent":"yes"} parses successfullyRun a schema validator after parsing
Top-level shape[] is valid JSON but not the required objectRequire a non-null, non-array object
Exact keysA required field is missing or an unapproved field appearsCompare required and allowed key sets
Primitive types and enumsreset_attempts is a string or issue is outside the enumValidate each property
Reference factsThe schema is valid but the ticket ID or count is wrongCompare extractable facts with the source
Business rulesurgent is false when resets are three or moreRecompute deterministic rules locally
Application validation continues after syntactic JSON parsing.
Valid JSON that passes parsing but fails local schema checks for types, missing keys, and extra keys.
Valid JSON is only the syntax layer; applications must still enforce exact keys, types, enums, null rules, source facts, and business rules.

For the benchmark’s synthetic ticket, the exact object contains five keys: ticket_id, issue, reset_attempts, order_id, and urgent. The local validator checks the exact key set, types, the permitted issue enum, explicit null behavior, source facts, and the rule that three or more failed resets make the ticket urgent. That scoring is a local contract, not a provider-side JSON Schema guarantee.

Original V4 test methodology

The bounded study added reproducible evidence without turning a correctness test into a load test. It used one authenticated model inventory request followed by exactly 20 sequential Chat Completions calls. Application concurrency was one, automatic live retries were disabled, and the safety policy stopped a segment after an unexpected non-success response or transport failure.

  • Fixed origin: https://api.deepseek.com.
  • Allowed completion endpoint: /chat/completions.
  • Allowed models: deepseek-v4-flash and deepseek-v4-pro.
  • Maximum live concurrency: one.
  • Automatic retries: zero.
  • Per-request deadline: 60 seconds.
  • Largest output allowance: 512 tokens.
  • Inputs: synthetic, English-only, and free of private data.
  • Excluded: account, balance, billing, key-management, alias, and deliberate rate-limit tests.

The bounded harness recorded only sanitized metadata: test label, date, public model name, request settings, status, elapsed time, finish reason, content presence and length, hashes, parser and validator flags, token counters, and streaming event counts. It did not publish the API key, Authorization header, balance, provider completion ID, system fingerprint, raw response headers, raw reasoning, or arbitrary failed output. The DeepSeek API key guide owns credential setup and rotation; this page does not reproduce those steps.

GroupPurposeCalls
AFlash/Pro × thinking enabled/disabled, two descriptive repetitions8
BPrompt requirement ablation3
CTruncation, escaping, synthetic injection, and null edge cases5
DStreaming assembly on Flash and Pro2
EStrong prompt with response_format omitted2
Executed bounded matrix: 20 Chat Completions requests, sequential execution, no automatic live retries, and a total configured output allowance of 5,704 tokens.

The execution completed in three safety-bounded segments. Segment one stopped after B2 returned HTTP 400. After review, segment two submitted only the next unexecuted test, B3, and stopped when that bounded control also returned HTTP 400. Segment three resumed at C1 and completed C1–E2. No earlier completion was repeated: the final merged artifact contains one inventory request and each test ID A1–E2 exactly once, for 21 HTTP requests in total.

V4 Flash and Pro with thinking enabled or disabled

Official: the model inventory reference and Chat Completion model enum identify deepseek-v4-flash and deepseek-v4-pro. The current Models & Pricing page marks JSON Output as supported on both. It also says both support thinking and non-thinking modes, with thinking enabled by default.

Official: DeepSeek’s Thinking Mode guide places intermediate reasoning in reasoning_content and the final answer in content. JSON parsing belongs on final content, not on reasoning_content. The benchmark records only whether reasoning exists plus its length and hash; it never publishes the reasoning text. The full implementation context belongs in the site’s DeepSeek Thinking Mode guide.

Live testRequested / returned modelThinkingRepHTTPFinal contentJSON.parseExact schemaReference factsFinishCompletion tokensReasoning present / tokensElapsed
A1 — July 27, 2026 UTCdeepseek-v4-flash / deepseek-v4-flashDisabled1200Non-empty, 95 charsPassPassPassstop31No / not reported885.183 ms
A2 — July 27, 2026 UTCdeepseek-v4-flash / deepseek-v4-flashDisabled2200Non-empty, 95 charsPassPassPassstop31No / not reported1,085.063 ms
A3 — July 27, 2026 UTCdeepseek-v4-flash / deepseek-v4-flashEnabled, high1200Non-empty, 95 charsPassPassPassstop77Yes / 451,249.275 ms
A4 — July 27, 2026 UTCdeepseek-v4-flash / deepseek-v4-flashEnabled, high2200Non-empty, 95 charsPassPassPassstop140Yes / 1081,738.955 ms
A5 — July 27, 2026 UTCdeepseek-v4-pro / deepseek-v4-proDisabled1200Non-empty, 95 charsPassPassPassstop31No / not reported997.402 ms
A6 — July 27, 2026 UTCdeepseek-v4-pro / deepseek-v4-proDisabled2200Non-empty, 95 charsPassPassPassstop31No / not reported1,195.830 ms
A7 — July 27, 2026 UTCdeepseek-v4-pro / deepseek-v4-proEnabled, high1200Non-empty, 95 charsPassPassPassstop147Yes / 1152,423.323 ms
A8 — July 27, 2026 UTCdeepseek-v4-pro / deepseek-v4-proEnabled, high2200Non-empty, 95 charsPassPassPassstop102Yes / 702,032.048 ms
Live A1–A8 results from July 27, 2026 UTC. Each requested model name matched the returned public model name. Two repetitions per configuration are descriptive only.
Sanitized live DeepSeek JSON Output matrix for V4 Flash and V4 Pro with thinking enabled and disabled.
Sanitized live results from eight core calls within one bounded 20-completion test; all eight passed JSON parsing, local schema, and reference-fact checks.

Across A1–A8, 8/8 calls returned HTTP 200, non-empty final content, directly parseable JSON, the exact five-key local schema, and all reference facts. All eight ended with finish_reason: "stop"; no core call returned empty, whitespace-only, or length-finished content. Each assembled final string was 95 characters.

Reasoning metadata was present in 4/4 thinking-enabled calls and absent in 4/4 explicitly disabled calls. The enabled calls reported 45, 108, 115, and 70 reasoning tokens; the disabled calls reported none. The final JSON, not the reasoning channel, was parsed and scored, and no raw reasoning is published.

Elapsed time ranged from 885.183 ms to 2,423.323 ms in this tiny sequential sample. Thinking-enabled calls also used more completion tokens here, but two repetitions cannot establish a permanent latency, cost, or quality ranking between Flash, Pro, or thinking modes.

Prompt ablation: the JSON word, an example, and neither

DeepSeek’s official JSON Output notice tells users to include the word json and provide an example. The bounded prompt ablation tests those pieces without hard-coding an expected failure. A strong control contains both. One case retains the word but removes the example; another retains an example while omitting the token json; the final case omits both. Low output caps bound the official whitespace-risk condition.

The correct interpretation is behavioral, not causal. If a weakened prompt happens to produce parseable content once, that does not cancel the official requirement. If it produces empty content, whitespace, invalid JSON, or finish_reason: "length", that result applies to the exact prompt, model, settings, and date tested. It does not prove that every future request will fail in the same way.

Live testJSON wordExampleOutput capHTTP / elapsedContent stateWhitespace-onlyLengthFinish / completion tokensJSON.parseSchema / facts
A1 strong referenceYesYes512200 / 885.183 msNon-emptyNo95 charsstop / 31PassPass / pass
B1YesNo128200 / 1,046.193 msNon-emptyNo117 charsstop / 45PassPass / fail
B2NoYes32400 / 277.191 msNo completion contentNot applicableNot returnedNot returnedNo content to parseNot assessed
B3NoNo32400 / 413.451 msNo completion contentNot applicableNot returnedNot returnedNo content to parseNot assessed
Live prompt-ablation observations from July 27, 2026 UTC. A1 is the strong reference row; its 512-token cap differs from the bounded ablation caps shown. Results apply only to these exact requests.

B1 retained the JSON instruction but removed the example. Its response parsed and passed the exact schema, yet failed the independent reference-fact check. This is a concrete demonstration that parseable, schema-valid JSON can still contain a wrong source fact; the failed value is not reproduced here.

B2 and B3 omitted the word json and returned HTTP 400 before completion content was available. They therefore produced neither an empty completion nor a whitespace-only completion: parsing, finish reason, and completion-token fields were unavailable. These were accepted as bounded controls in the reviewed artifact, but each still triggered the safety stop for its segment. No automatic retry was sent.

Live DeepSeek JSON Output prompt ablation comparing the word JSON and an example-object control.
Live prompt controls: the word-only run parsed but missed a reference fact; the example-only and neither variants returned HTTP 400.

Prompt-only controls without response_format

Two additional controls kept the strong JSON prompt and example but omitted response_format. These are dated one-call observations of prompt behavior, not a replacement for the documented API control.

Live testModelresponse_formatHTTP / elapsedContentFinish / tokensParse / schema / facts
E1deepseek-v4-flashOmitted200 / 1,029.117 msNon-empty, 95 charsstop / 31Pass / pass / pass
E2deepseek-v4-proOmitted200 / 1,360.094 msNon-empty, 95 charsstop / 31Pass / pass / pass
Live prompt-only controls from July 27, 2026 UTC. Both happened to return the expected JSON once; neither result makes response_format unnecessary.

Empty content, whitespace, and finish_reason length

Official: DeepSeek says JSON Output may occasionally return empty content and suggests modifying the prompt to mitigate the issue. Empty content is not invalid JSON—it contains no JSON value at all. Check for a string and then check content.trim().length before parsing. Do not let a JSON-decoding exception hide the more precise diagnosis that no final content was returned.

Official: the Chat Completion reference warns that a missing JSON instruction can produce whitespace until the token limit. Track empty and whitespace-only states separately. Whitespace is legal around a JSON value, but a response containing only whitespace has no value. A client should not strip Markdown fences or repair malformed output silently and then report a clean JSON success; that conceals the provider response your application actually received.

Official: finish_reason: "length" can mean generation reached max_tokens or the conversation reached the model context limit. The returned content may be partially cut off. A deliberate eight-token truncation control in the bounded matrix records finish reason, content length, and parse status, but one result cannot define how every large object will terminate.

Live — July 27, 2026 UTC: empty final content was observed in 0/18 successful completions, and whitespace-only final content was observed in 0/18. Across all 20 planned completion requests, 18 returned non-empty content; B2 and B3 returned HTTP 400 without completion content and are not counted as empty model outputs. This bounded observation does not contradict the official warning that empty content may occasionally occur.

Live testPurposeModelOutput capHTTPContent state / lengthWhitespace-onlyFinish reasonCompletion tokensJSON.parse
C1Deliberate truncationdeepseek-v4-flash8200Non-empty / 21 charsNolength8Fail
C1 reached the eight-token output cap, returned partial non-empty content, and did not parse as JSON. Treat a length-finished structured response as incomplete.

Use bounded recovery, not blind JSON retries

Recovery should start by preserving the failure class. Empty content, whitespace-only content, invalid JSON, a schema mismatch, and finish_reason: "length" are different outcomes. A corrected prompt may be appropriate for empty or whitespace behavior; a larger but still reasonable output allowance may address a known truncation; and a schema mismatch may need a clearer example or smaller task. Do not send the identical request repeatedly without a finite attempt budget.

HTTP failures belong to a separate decision path. Correct 400, 401, 402, or 422 conditions before resubmitting. Apply bounded backoff to transient 429, 500, or 503 responses according to application policy, and remember that a network timeout does not prove the provider performed no work. If a completion can trigger an external write, keep parsing and validation separate from that write and use an application-level duplicate-work guard.

Escaping, nulls, and synthetic prompt injection

Structured-output code must survive ordinary JSON edge cases. Quotes and backslashes need escaping, newline characters must be encoded correctly, and missing facts should use an explicit contract such as null instead of an invented value. Unicode is valid JSON; store and transmit it as UTF-8, and validate its meaning at the application layer.

The benchmark also includes a synthetic input that tells the model to ignore the schema and produce Markdown. The local validator checks whether the final object still satisfies the exact contract. This is a narrow instruction-conflict control, not a security benchmark. Production systems need input isolation, authorization, output validation, least-privilege actions, and human review where consequences are material.

Live testControlModelHTTP / elapsedContentParseSchemaFactsMissing / extra keysFinish / output tokens
C1Eight-token truncationdeepseek-v4-flash200 / 1,068.862 msNon-empty, 21 charsFailFailFailNot assessed after parse failurelength / 8
C2Quotes, backslashes, and newline escapingdeepseek-v4-flash200 / 912.826 msNon-empty, 101 charsPassPassPassNone / 0stop / 33
C3Quotes, backslashes, and newline escapingdeepseek-v4-pro200 / 1,239.678 msNon-empty, 101 charsPassPassPassNone / 0stop / 33
C4Synthetic conflicting instructiondeepseek-v4-flash200 / 1,043.989 msNon-empty, 95 charsPassPassPassNone / 0stop / 31
C5Missing facts represented as nulldeepseek-v4-pro200 / 1,424.654 msNon-empty, 99 charsPassPassPassNone / 0stop / 31
Live C1–C5 edge-case results from July 27, 2026 UTC. Four of five passed syntax, exact schema, and source-fact validation; the intentionally truncated C1 response did not parse.

The Flash and Pro escaping controls both preserved the synthetic path, quotes, and newline after parsing. C5 preserved the explicit nullable contract for missing reset-attempt and order facts. C4 returned the exact expected object despite a synthetic request to ignore the schema and use Markdown, but one narrow pass is not evidence of prompt-injection safety.

DeepSeek JSON Output live edge-case results for truncation, escaping, synthetic injection, and missing facts.
Four of five edge-case calls passed parsing, schema, and reference checks; the deliberate eight-token truncation control ended at length and did not parse.

Streaming DeepSeek JSON safely

Official: with stream: true, DeepSeek sends data-only server-sent events and terminates the stream with data: [DONE]. If stream_options.include_usage is enabled, an additional usage chunk appears before [DONE]; its choices array is empty. A parser must not assume every event contains choices[0].

Streaming does not make each delta valid standalone JSON. The client should concatenate final-content deltas, keep reasoning deltas separate, ignore colon-prefixed SSE comments, recognize the usage-only event, require [DONE], and call JSON.parse only after the final content is assembled. If the stream ends without [DONE], treat the result as incomplete even when the collected text looks parseable.

Live testModel / modeHeaders / first data / totalSSE data eventsReasoning eventsKeep-alive / invalidUsage / DONEAssembled final contentFinish / tokensParse / schema
D1deepseek-v4-flash / disabled280.960 / 312.028 / 993.286 ms34Not separately counted; reasoning absent0 / 01 / yes95 chars; SHA-256 d21923f03e8c029cead4670cd0eba9b115a4cf10db993f1e6f527404bd4c6f2fstop / 31 completionPass / pass
D2deepseek-v4-pro / enabled, high336.731 / 340.433 / 2,992.096 ms214Not separately counted; reasoning present, 180 tokens0 / 01 / yes95 chars; SHA-256 d21923f03e8c029cead4670cd0eba9b115a4cf10db993f1e6f527404bd4c6f2fstop / 212 completionPass / pass
Live streaming observations from July 27, 2026 UTC. Times are milliseconds from request start. The harness counted all SSE data events but did not split them into separate content-event and reasoning-event counters.

Both streams, 2/2, reached [DONE], included one usage-only event, ended with an empty trailing buffer, and contained zero invalid SSE data events. After final-content assembly, both strings were non-empty, had the same sanitized SHA-256 digest, parsed directly, passed the exact schema, and matched the reference facts.

D2 exposed reasoning metadata, but reasoning was kept out of the final-content buffer and no raw reasoning is published. Because this harness did not count content-delta and reasoning-delta events separately, the table reports the exact available all-data-event count rather than inventing a finer breakdown.

DeepSeek JSON streaming timeline from response headers through SSE events, usage, DONE, final parsing, and schema validation.
Both bounded streams reached DONE and passed parsing, schema, and reference checks after complete assembly.
async function collectDeepSeekJsonStream(response) {
  if (!response.ok || !response.body) {
    await response.body?.cancel();
    throw new Error(`DeepSeek stream failed with HTTP ${response.status}`);
  }

  const reader = response.body.getReader();
  const decoder = new TextDecoder();
  let buffer = "";
  let finalContent = "";
  let doneSeen = false;
  let reasoningCharacters = 0;
  let usage = null;

  const processEvent = (eventText) => {
    const data = eventText
      .split("\n")
      .filter((line) => !line.startsWith(":"))
      .filter((line) => line.startsWith("data:"))
      .map((line) => line.slice(5).trimStart())
      .join("\n");

    if (!data) return;
    if (data === "[DONE]") {
      doneSeen = true;
      return;
    }

    const event = JSON.parse(data);
    if (event.usage && event.choices?.length === 0) {
      usage = event.usage;
      return;
    }

    const delta = event.choices?.[0]?.delta;
    if (typeof delta?.reasoning_content === "string") {
      reasoningCharacters += delta.reasoning_content.length;
    }
    if (typeof delta?.content === "string") {
      finalContent += delta.content;
    }
  };

  while (true) {
    const { value, done } = await reader.read();
    buffer += decoder.decode(value, { stream: !done });
    buffer = buffer.replaceAll("\r\n", "\n");

    let boundary = buffer.indexOf("\n\n");
    while (boundary !== -1) {
      processEvent(buffer.slice(0, boundary));
      buffer = buffer.slice(boundary + 2);
      boundary = buffer.indexOf("\n\n");
    }
    if (done) break;
  }

  if (buffer.trim()) processEvent(buffer);
  if (!doneSeen) throw new Error("DeepSeek stream ended before [DONE]");
  if (!finalContent.trim()) throw new Error("DeepSeek returned empty final content");

  return {
    value: JSON.parse(finalContent),
    usage,
    reasoningCharacters,
  };
}

The example counts reasoning characters but does not print or persist reasoning. Applications that need full thinking-mode history behavior should follow the dedicated Thinking Mode documentation rather than expanding this JSON parser into a general reasoning logger.

Production Node.js parsing and validation

The following dependency-free Node.js example makes one non-streaming request, refuses to run without a server-side environment variable, rejects redirects, checks the HTTP status, distinguishes empty content, treats length as incomplete, parses once, and applies the benchmark’s exact local contract. The complete reproducibility harness adds request ceilings, plan validation, artifact redaction, and offline fixtures. For SDK installation and TypeScript-specific behavior, use the DeepSeek Node.js and TypeScript guide.

const API_URL = "https://api.deepseek.com/chat/completions";
const EXPECTED_KEYS = [
  "issue",
  "order_id",
  "reset_attempts",
  "ticket_id",
  "urgent",
];
const ALLOWED_ISSUES = new Set([
  "account_access",
  "billing",
  "technical",
]);

function validateTicket(value) {
  const issues = [];
  if (!value || typeof value !== "object" || Array.isArray(value)) {
    return ["top-level value must be an object"];
  }

  const keys = Object.keys(value).sort();
  const missing = EXPECTED_KEYS.filter((key) => !keys.includes(key));
  const extra = keys.filter((key) => !EXPECTED_KEYS.includes(key));
  if (missing.length) issues.push(`missing keys: ${missing.join(", ")}`);
  if (extra.length) issues.push(`extra keys: ${extra.join(", ")}`);

  if (typeof value.ticket_id !== "string") {
    issues.push("ticket_id must be a string");
  }
  if (!ALLOWED_ISSUES.has(value.issue)) {
    issues.push("issue is outside the allowed enum");
  }
  if (
    value.reset_attempts !== null &&
    !Number.isInteger(value.reset_attempts)
  ) {
    issues.push("reset_attempts must be an integer or null");
  }
  if (value.order_id !== null && typeof value.order_id !== "string") {
    issues.push("order_id must be a string or null");
  }
  if (typeof value.urgent !== "boolean") {
    issues.push("urgent must be a boolean");
  }

  // Deterministic reference checks for this synthetic benchmark only.
  if (value.ticket_id !== "T-204") issues.push("ticket_id fact mismatch");
  if (value.reset_attempts !== 3) issues.push("reset_attempts fact mismatch");
  if (value.order_id !== null) issues.push("order_id should be null");
  if (value.urgent !== true) issues.push("urgent business rule mismatch");
  return issues;
}

async function main() {
  const apiKey = process.env.DEEPSEEK_API_KEY;
  if (!apiKey) throw new Error("DEEPSEEK_API_KEY is not set");

  const response = await fetch(API_URL, {
    method: "POST",
    redirect: "manual",
    signal: AbortSignal.timeout(60_000),
    headers: {
      "Authorization": `Bearer ${apiKey}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      model: "deepseek-v4-flash",
      messages: [
        {
          role: "system",
          content:
            "Return JSON only. Use exactly this example shape: " +
            "{\"ticket_id\":\"T-204\",\"issue\":\"account_access\"," +
            "\"reset_attempts\":3,\"order_id\":null,\"urgent\":true}",
        },
        {
          role: "user",
          content:
            "Ticket T-204: The customer cannot sign in after three " +
            "failed password resets. No order ID was provided.",
        },
      ],
      response_format: { type: "json_object" },
      thinking: { type: "disabled" },
      max_tokens: 128,
      stream: false,
    }),
  });

  if (!response.ok) {
    await response.body?.cancel();
    throw new Error(`DeepSeek returned HTTP ${response.status}`);
  }

  const payload = await response.json();
  const choice = payload.choices?.[0];
  if (choice?.finish_reason === "length") {
    throw new Error("DeepSeek JSON may be truncated");
  }

  const content = choice?.message?.content;
  if (typeof content !== "string" || !content.trim()) {
    throw new Error("DeepSeek returned empty final content");
  }

  const value = JSON.parse(content);
  const issues = validateTicket(value);
  if (issues.length) {
    throw new Error(`Schema validation failed: ${issues.join("; ")}`);
  }

  console.log(value);
}

main().catch((error) => {
  console.error(error.message);
  process.exitCode = 1;
});

In a real product, replace the benchmark-specific checks with a maintained schema validator and business-rule layer. Keep the original response classification in telemetry: HTTP status, finish reason, content state, parser result, schema result, model, token counters, and latency. Do not log the API key, raw Authorization header, private prompts, or hidden reasoning. The DeepSeek Observability guide owns the broader logging and alerting design.

JSON Output versus Tool Calls

NeedJSON OutputTool Calls
Return structured final content to the applicationUse response_format: json_objectUsually unnecessary
Ask the model to select a functionNot the function-selection interfaceDefine tools and inspect tool_calls
Validate generated structureValidate parsed final contentValidate generated function arguments
Execute an external actionNever execute merely because JSON parsedAuthorize and execute the selected function in application code
Handle side effectsKeep parsing separate from writesUse least privilege, idempotency, and authorization
JSON Output and Tool Calls solve different application problems. Both require local validation.

DeepSeek’s API reference explicitly warns that generated tool arguments may be invalid JSON or contain hallucinated parameters, so they must be validated before a function runs. The DeepSeek Tool Calls guide owns function schemas, argument validation, and execution loops.

Troubleshooting DeepSeek JSON mode

SymptomCheck firstSafe response
Empty contentConfirm the prompt explicitly requests JSON and shows the shapeDo not parse; modify the prompt and retry only within a bounded policy
Whitespace-only output or apparently stuck requestCheck that the final assembled prompt contains the word jsonCancel at the application deadline; correct the prompt before resubmitting
JSON.parse throwsInspect content state, finish reason, and output allowanceDo not silently strip fences or repair arbitrary output as a success
HTTP 200 but incomplete objectCheck finish_reason for lengthIncrease a reasonable output allowance or reduce the requested structure
JSON parses but fields are wrongRun exact schema, enum, reference-fact, and business-rule checksReject the object or request a corrected response
Unexpected extra keysCompare against an explicit allow-listReject by default when extra properties affect downstream safety
Streaming parser fails on usage eventCheck for an empty choices array with a top-level usage objectRecord usage separately and continue until [DONE]
Thinking text is mixed with JSONParse final content, not reasoning_contentKeep the two channels separate and avoid logging raw reasoning
HTTP 400, 401, 402, 422, 429, 500, or 503Classify the HTTP failure before touching JSON parsingUse the DeepSeek Error Codes guide
Troubleshooting should preserve the original failure class instead of converting everything into a JSON parse error.

Reproducibility and limitations

The public harness is safe by default: no argument prints usage, plan validation and offline fixtures cannot read an API key or access the network, and only an explicit live mode can call the fixed DeepSeek origin. The plan validator enforces the 20-call ceiling, sequential execution, model allow-list, output limits, exact prompt variants, and prohibited endpoint list.

Offline checkExact result
JavaScript syntaxPass
Live-plan contractPass: one inventory request; 20 completion IDs exactly once; group counts 8/3/5/2/2; concurrency 1; retries 0; maximum output cap 512; total configured output allowance 5,704
Offline safety modevalid: true; provider requests 0; API key read false
Non-streaming fixtures8/8 passed: valid core, valid JSON with wrong schema, invalid JSON, truncated JSON, empty, whitespace-only, valid escaping, and valid nullable data
SSE fixturePass: 2 keep-alive comments, 4 data events, 1 usage-only event, [DONE] seen, reasoning redacted, and parse/schema/reference-fact checks all passed
Sanitized artifact scansJSON scan passed; CSV scan passed; a deliberately injected leak was detected by the negative control
Offline — no provider requests. These deterministic checks validate the plan, local parsers, validators, and redaction guardrails; they are not live model evidence.

The English-only harness, sanitized summary, fixtures, and editable visuals are available in the public DeepSeek JSON Output evidence directory on GitHub. Raw provider output and hidden reasoning are intentionally excluded, so the public artifact supports request-accounting and implementation review but not independent re-parsing of the omitted response text.

DeepSeek JSON Output test guardrails showing a 20-call ceiling, one inventory request, concurrency one, zero retries, and redaction.
Local safety guardrails enforced one model inventory, 20 sequential completions, zero automatic retries, and a strict redaction policy.

The live matrix remains intentionally small: two repetitions per core configuration and one call for most edge cases. It can document compatibility and reveal concrete failure shapes on the test date, but it cannot establish a universal parse rate, schema-adherence rate, security guarantee, or permanent model ranking. A timeout leaves provider work uncertain. A successful prompt-only control does not make response_format unnecessary. A successful synthetic injection control does not prove production prompt-injection safety.

Model behavior, validation, and documentation can change. Recheck DeepSeek’s primary sources and rerun a bounded compatibility matrix before a material production change. Use the DeepSeek API overview for the general request lifecycle, the DeepSeek Models page for model selection context, and the DeepSeek Pricing page for current copied pricing context.

Frequently asked questions

How do I make DeepSeek return JSON?

Set response_format to {"type":"json_object"}, explicitly use the word json in the system or user prompt, provide an example of the desired object, and choose a reasonable max_tokens allowance. Then check for non-empty final content, parse it, and validate it locally.

Does json_object enforce my JSON Schema?

No. DeepSeek documents valid JSON strings, not conformance to every application-specific schema. Required properties, exact keys, types, enums, ranges, null behavior, source facts, and business rules still need local validation.

Why is DeepSeek JSON Output empty?

DeepSeek officially says empty content may occasionally occur and recommends modifying the prompt to mitigate it. Do not assign one universal cause. Record the exact prompt, model, settings, finish reason, content state, and date, then use a bounded recovery policy.

Why does DeepSeek JSON mode appear stuck?

The Chat Completion reference warns that omitting a JSON instruction can lead to whitespace until the output limit. Ensure the assembled prompt explicitly requests JSON, set an application deadline, and distinguish whitespace-only output from a connection that is still open.

Do V4 Flash and V4 Pro support JSON Output?

Yes. DeepSeek’s current Models & Pricing page marks JSON Output as supported for both deepseek-v4-flash and deepseek-v4-pro. Model choice remains a workload decision; official feature support alone does not prove one model is always more schema-reliable.

Can I use DeepSeek JSON Output with Thinking Mode?

Both current V4 models support JSON Output and thinking mode. Parse the final answer from content; keep reasoning_content separate. Thinking defaults to enabled, so set the toggle explicitly when reproducibility matters.

Can I parse each streaming JSON delta?

Not as a complete object. Concatenate final-content deltas, handle the optional usage-only event, wait for [DONE], and then parse and validate the assembled string. Keep reasoning deltas out of the final JSON buffer.

Should I remove Markdown fences before JSON.parse?

Not in the primary success path. Silent cleanup can hide a contract failure. Record that the original content was not directly parseable, improve the prompt, and decide explicitly whether a narrowly defined recovery parser is acceptable for your application.

Is JSON Output the same as Tool Calls?

No. JSON Output structures the assistant’s final content. Tool Calls let the model select a defined function and generate arguments. Both outputs require validation, but only authorized application code should execute an external action.

Official DeepSeek documentation checked July 27, 2026 UTC. Live observations and offline checks were populated from reviewed sanitized artifacts; no secrets, account data, raw reasoning, or arbitrary failed output are published.