跳到正文
DEV Community· bitavu·· 4 小時前AI 評分51

JSON 格式正確,發票資料仍可能出錯

Your JSON is valid and your invoice is wrong

AI 導讀

JSON 可成功解析,並不代表生成的文件正確;模板所需的欄位路徑、資料類型及業務含義仍須符合要求,否則發票可能遺漏客戶或列出錯誤收件人。文章以 customer.legalName → client.name 為例,建議檢查必填欄位,並用不同的帳單與送貨名稱測試映射。

正文

Imagine a perfectly reasonable backend refactor.

client.name becomes customer.legalName. The new shape is clearer. The JSON parses. The application tests are green.

Then someone opens an invoice.

Beautiful margins. Correct logo. No customer.

The document has successfully generated an invoice addressed to absolutely nobody.

This is an illustrative failure in a loosely checked integration, but the underlying problem is useful to think about: valid JSON tells you surprisingly little about whether a document is correct.

I'm building Bitavu, a visual template editor with a JSON-to-PDF API. The boundary between incoming data and a saved document template is one of the places where a visible error can be much more useful than a successful download.

The customer did not disappear

Here is the data the template originally expected:

{
  "client": {
    "name": "Harbor Studio"
  }
}

And here is the new upstream shape:

{
  "customer": {
    "legalName": "Harbor Studio"
  }
}

Both are valid JSON. Both contain the same company name.

But a template bound to client.name does not automatically understand that customer.legalName is its replacement.

If the integration quietly converts a missing value into an empty string, the PDF may still render. The mistake has simply moved from an error response into a document someone will send.

one small refactor

The refactor is reasonable. The unchanged binding is the problem.

Three different checks are involved here:

  • Syntax: can the input be parsed as JSON?

  • Structure: does it contain the paths and types the template requires?

  • Meaning: are those values the right ones for this document?

Passing one does not prove the others.

A blank string is not always a fallback

Some fields really are optional. An absent delivery note might reasonably leave no visible text.

A missing billing recipient deserves a different decision.

If the document requires a customer name, make that requirement explicit before rendering. Even a small guard is better than quietly substituting a blank:

function requireClientName(data) {
  const name = data?.client?.name;

  if (typeof name !== "string" || name.trim() === "") {
    throw new TypeError("Expected a non-empty client.name");
  }

  return name;
}

This checks one field. A real document contract needs to cover its other required fields, types, arrays, and allowed values too.

Notice what happens with the refactored payload: the guard fails.

That is useful. You now have a specific integration change to review before an invoice leaves the system.

An empty string would have been quieter. The customer would have discovered the problem for you.

The more convincing bug passes the type check

Now suppose the input contains both of these:

{
  "billing": {
    "name": "Harbor Studio"
  },
  "shipping": {
    "name": "Harbor Warehouse"
  }
}

Both names are present. Both are strings. Both look like sensible things to put on a business document.

Only one belongs in this invoice's Bill to field.

same type. different meaning.

This is why a mapping can be technically valid and still be wrong.

A structural validator can check that a string exists at a declared path. It cannot infer which business entity you intended to bill just because two field names look related.

When you review a mapping, ask a more useful question than "does the preview have text now?"

Ask: "Is this the person or company the document is supposed to name?"

The wrong name often looks more finished than a blank one. That does not make it a better result.

Treat a proposed mapping like a code change

For the first example, a deliberate mapping might be:

customer.legalName → client.name

That is a small transformation with a specific meaning: this upstream legal name supplies this template's client name.

Review it against the document's purpose. If an AI tool proposes the mapping, the same review still applies. A plausible field match is a proposal, not evidence that the two fields mean the same thing.

Before adopting it, use a few synthetic examples:

Input case Expected result
A normal customer legal name The intended name appears in Bill to
Billing and shipping names that differ The billing recipient is used
A missing or blank required name An explicit failure rather than a blank recipient
A number where the name should be A type error rather than silent coercion

The second case is especially helpful. If every name in your fixture is "Test Company," the wrong mapping can look perfectly correct.

Give the warehouse a different name. Let your test data disagree with itself in the ways real data does.

Make the mismatch visible

In Bitavu, a detected schema mismatch is reported as 422 SCHEMA_DRIFT. After reviewing a transformation, you can save a mapping and reference its mappingId in render requests.

Saved mappings target a particular template revision. That gives the reviewed transformation a defined destination instead of treating it as a universal fix for every document.

make the mismatch visible

Conceptual workflow illustration, not a Studio screenshot.

That mechanism helps make the mismatch explicit. It still needs a well-defined template contract and a person who understands what the fields mean.

The useful outcome is a document whose data and presentation have both been checked.

A green check beside "valid JSON" cannot do all of that work.

What is harder to catch in your documents: a missing value, or a completely believable wrong one?


Disclosure: I'm building Bitavu. All four illustrations are AI-generated conceptual scenes using synthetic examples.

來源:DEV Community · dev.to