IQ
PayloadIQ
← PayloadIQ Guides

Reading the Quality of an API Schema

The same JSON can be easy or painful to build on. These are the signals that decide which — and what to do about each.

Two payloads can carry the same information and yet one is a pleasure to type and the other a slow leak of bugs. The difference is in a handful of recurring signals. Once you can spot them in a response, you know what to fix before you generate types or a database model from it.

Mixed-type arrays

An array is meant to hold one kind of thing. When the elements differ, every consumer has to special-case:

"items": [
  { "id": 1, "price": 9.99, "discount": 2.0 },
  { "id": 2, "price": 5.0 }
]

The second item drops discount. The correct item type marks it discount?: numberrather than pretending every element is complete. If the shapes differ fundamentally, it's two types in one array — a smell worth flagging to whoever owns the API.

Nullable IDs

An identifier that can be nullis a modelling contradiction — an id is how you refer to a thing, so "a thing with no id" usually means the field is overloaded or the endpoint returns partial records. Model it as string | null if you must consume it, but treat it as a question for the producer.

Large integers

JSON has one number type and JavaScript parses it as a 64-bit float. Anything above Number.MAX_SAFE_INTEGER (about 9 quadrillion) silently rounds:

JSON.parse('{"id": 9007199254740993}').id
// => 9007199254740992   (off by one, silently)

Snowflake IDs, Twitter-style identifiers, and some database keys exceed this. Type them as string even though the JSON shows a number.

Unsafe field names

Keys with spaces, leading digits, dots, or reserved words ("user name", "2fa", "class") can't be dot-accessed and often break code generators. You can consume them with bracket access, but the cleaner fix is a mapping layer that renames them to safe identifiers at the boundary.

Enum candidates

A field that only ever holds a few string values — "active", "paused", "closed" — is an enum waiting to be named. Typing it as string lets typos and unexpected values through. Promote it to a union or z.enum([...]) so invalid states are rejected.

Prisma and SQL pitfalls

When the payload becomes a database model, more traps appear: nullable columns that should have defaults, snake_case JSON mapping onto camelCase fields, deeply nested objects that want a JSON column or a related table, and keys that collide with reserved SQL words like order or user. Generating a model straight from messy JSON bakes these in.

When to run a check

Run a quality pass before you commit to a contract: before generating types, before designing a table, before building a client. It's far cheaper to widen a type or rename a field now than after three services depend on it.

Common mistakes

  • Trusting one sample. A single response can't show you optional fields or the full range of an enum.
  • Treating null and missing as the same. null and absent are different facts that need different handling.
  • Letting big-int IDs round. The bug is invisible until two records collide.
  • Generating a DB model from raw JSON. Review names, nullability, and nesting first.

The PayloadIQ Schema Quality Check and Contract Doctor surface these signals automatically from a pasted payload — a fast way to see the risks before they reach your codebase.

Open the Playground

Related guides

JSON to TypeScriptJSON to ZodAPI Contract Migration