DEV Community

Vin Lookup
Vin Lookup

Posted on

Handling Truncated or Partial vPIC JSON Without Displaying Half-Invented Vehicles

NHTSA vPIC DecodeVinValues responses are JSON. Most of the time the body is complete: you JSON.parse, read Results[0], and map catalog fields. The trap is treating a truncated body, a cut-off stream, or a half-parsed object as a successful vehicle card. A free VIN UI that paints Make from a broken payload while inventing Model trains buyers to trust garbage.

This post is about parse honesty: refuse to render incomplete vehicle identity as success, distinguish transport truncation from sparse-but-valid catalog rows, and guard JSON.parse plus result shape. Sparse fields after a valid parse are a different topic; here the failure mode is broken or partial JSON itself.

What "partial JSON" means here

A truncated or partial JSON body is not the same as a successful decode with empty Trim. It answers a narrow question: did we receive a complete, parseable document that matches the expected DecodeVinValues shape?

It does not authorize:

  • Showing a "decoded" card from a string that throws mid-parse
  • Guessing closed braces, inventing missing keys, or repairing Results by hand
  • Promoting a partial object (Make present, ModelYear missing because the stream cut off) into a success state
  • Reusing the last successful card when the new body failed to parse
  • Treating HTTP 200 plus a truncated body as "good enough for identity"

HTTP status alone is not enough. A 200 with a cut-off Content-Length or a proxy that clipped the body is still a parse failure for UI purposes.

Parse, then validate shape -- never invent

Wrap JSON.parse so throwables become an explicit error state. After parse, require the Results array and enough identity keys to justify a card -- or refuse success.

const EMPTY = new Set([
  "",
  "not applicable",
  "n/a",
  "na",
  "null",
  "none",
  "unknown",
]);

export type ParseOutcome =
  | { ok: true; row: Record<string, string> }
  | { ok: false; reason: "truncated_or_invalid_json" | "missing_results" | "incomplete_identity" };

function nonEmpty(raw: string | null | undefined): string | null {
  const t = (raw ?? "").trim();
  if (!t || EMPTY.has(t.toLowerCase())) return null;
  return t;
}

export function parseVpicBody(bodyText: string): ParseOutcome {
  let parsed: unknown;
  try {
    parsed = JSON.parse(bodyText);
  } catch {
    return { ok: false, reason: "truncated_or_invalid_json" };
  }

  if (!parsed || typeof parsed !== "object") {
    return { ok: false, reason: "truncated_or_invalid_json" };
  }

  const results = (parsed as { Results?: unknown }).Results;
  if (!Array.isArray(results) || results.length === 0) {
    return { ok: false, reason: "missing_results" };
  }

  const row = results[0];
  if (!row || typeof row !== "object") {
    return { ok: false, reason: "missing_results" };
  }

  const rec = row as Record<string, string>;
  const make = nonEmpty(rec.Make);
  const model = nonEmpty(rec.Model);
  const year = nonEmpty(rec.ModelYear);
  if (!make || !model || !year) {
    return { ok: false, reason: "incomplete_identity" };
  }

  return { ok: true, row: rec };
}
Enter fullscreen mode Exit fullscreen mode

Incomplete identity after a valid parse (Make present, Model blank because vPIC left it empty) can be a sparse success elsewhere. Here we refuse full success unless Make, Model, and ModelYear are all sourced. Adjust the bar for a deliberate "partial identity" state -- but never invent the missing pieces.

Forbidden upgrades

Product pressure often asks for:

  1. Catching JSON.parse errors and still rendering a card from regex scraps
  2. Auto-closing truncated JSON with "}]}" so the spinner stops
  3. Showing Make from a half-object while labelling Model "estimating..."
  4. Falling back to the previous VIN's card when the new body is truncated
  5. Logging "decode ok" because status was 200 even when parse failed

Refuse those. A truncated body is an error or retry -- not a vehicle.

export type CardState =
  | { kind: "ready"; make: string; model: string; modelYear: string }
  | { kind: "error"; message: string };

const ERROR_MSG = {
  truncated_or_invalid_json:
    "Decode response was truncated or invalid JSON -- not showing a vehicle card",
  missing_results:
    "Decode response had no Results row -- not showing a vehicle card",
  incomplete_identity:
    "Decode response lacked Make, Model, or ModelYear -- not treating as success",
} as const;

export function cardFromBody(bodyText: string): CardState {
  const outcome = parseVpicBody(bodyText);
  if (!outcome.ok) {
    return { kind: "error", message: ERROR_MSG[outcome.reason] };
  }
  return {
    kind: "ready",
    make: String(nonEmpty(outcome.row.Make)),
    model: String(nonEmpty(outcome.row.Model)),
    modelYear: String(nonEmpty(outcome.row.ModelYear)),
  };
}

Enter fullscreen mode Exit fullscreen mode

UI copy that stays honest

Prefer:

  • "Decode response was truncated or invalid JSON -- try again"
  • "No Results row in decode response"
  • "Make, Model, or ModelYear missing -- not showing a complete vehicle card"

Avoid:

  • "Decoded (partial)" with invented Model beside a Make scraped from a broken body
  • Skeleton text that looks like catalog values while parse is still failing
  • Silent reuse of the last successful decode when the new parse failed

Quick checks

import assert from "node:assert/strict";

const truncated = parseVpicBody('{"Count":1,"Results":[{"Make":"TOYOTA"');
assert.equal(truncated.ok, false);
assert.equal(truncated.reason, "truncated_or_invalid_json");

const sparse = JSON.stringify({
  Count: 1,
  Results: [{ Make: "TOYOTA", Model: "", ModelYear: "2019" }],
});
assert.equal(parseVpicBody(sparse).ok, false);
assert.equal(parseVpicBody(sparse).reason, "incomplete_identity");

const good = JSON.stringify({
  Count: 1,
  Results: [
    { Make: "TOYOTA", Model: "Camry", ModelYear: "2019", Trim: "" },
  ],
});
const card = cardFromBody(good);
assert.equal(card.kind, "ready");
if (card.kind === "ready") {
  assert.equal(card.make, "TOYOTA");
  assert.equal(card.model, "Camry");
  assert.equal(card.modelYear, "2019");
}

const badCard = cardFromBody("{not-json");
assert.equal(badCard.kind, "error");
assert.ok(/truncated or invalid JSON/i.test(
  badCard.kind === "error" ? badCard.message : "",
));
Enter fullscreen mode Exit fullscreen mode

Review rule: parse modules must not repair JSON or reuse a previous card. Never paint identity from an unparseable body.

Takeaway

Truncated or partial vPIC JSON is a failure for vehicle-card purposes, not a prompt to invent Model or repair braces. Parse explicitly, validate Results and identity keys, and show error or retry when the document is incomplete. Your free VIN UI stays trustworthy when success means a real, complete parse -- not a half-invented car from scraps of a broken response.

I maintain VIN Lookup, a free VIN decode based on NHTSA data.

Top comments (0)