prismadatabaseormschematypescriptconverters

JSON to Prisma Schema: Model Your Database from a JSON Sample

·9 min read·Converters

From a JSON payload to a typed data model

Prisma is the ORM of choice for a lot of TypeScript backends, and its schema — the schema.prisma file — is where you declare your models. A very common starting point is a JSON sample: an API response or a document you want to persist. Converting that JSON into a Prisma model is mostly a mechanical type-mapping, plus a few database concerns that JSON simply doesn't carry. Here's the full mapping. If you've used JSON to TypeScript, this is the same idea aimed at your database layer.

The scalar mapping

Given this JSON:

json
{
  "id": 1,
  "email": "ada@example.com",
  "name": "Ada",
  "age": 36,
  "isActive": true,
  "rating": 4.5,
  "createdAt": "2026-08-03T10:00:00Z"
}

each field maps to a Prisma scalar type:

prisma
model User {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  name      String
  age       Int
  isActive  Boolean
  rating    Float
  createdAt DateTime @default(now())
}

The core rules:

JSON valuePrisma type
"text"String
36 (whole)Int
4.5 (decimal)Float (or Decimal for money)
true / falseBoolean
ISO date stringDateTime
null / may be absentoptional ? (below)

Note Float is wrong for money — use Decimal to avoid the floating-point precision problems that bite currency math.

Optional fields and arrays

A field that can be null or missing becomes optional with a trailing ?. A JSON array of scalars becomes a list with []:

json
{ "nickname": null, "tags": ["admin", "beta"] }
prisma
model User {
  // ...
  nickname String?          // optional — nullable in the database
  tags     String[]         // scalar list (Postgres/CockroachDB)
}

As always with generated models, feed the converter a representative sample: if nickname is null in your one example but usually present, you still want it optional. Getting nullability right is the main judgement call.

Nested objects: relations or Json?

This is the decision JSON can't make for you. A nested object like:

json
{ "id": 1, "profile": { "bio": "...", "avatar": "..." } }

can map two ways, and you must choose based on how you'll use it:

As a relation (separate table) — when the nested object is its own entity you'll query, filter, or join:

prisma
model User {
  id      Int      @id @default(autoincrement())
  profile Profile?
}

model Profile {
  id     Int    @id @default(autoincrement())
  bio    String
  avatar String
  user   User   @relation(fields: [userId], references: [id])
  userId Int    @unique
}

As a `Json` column — when the nested data is variable, document-like, and you never query inside it:

prisma
model User {
  id      Int  @id @default(autoincrement())
  profile Json         // stored as a JSON/JSONB column
}

Rule of thumb: query it → relation; just store it → `Json`. This mirrors the JSON in databases trade-off between typed columns and JSON columns.

What a JSON sample can't tell you

A JSON object shows *shape*, not *database semantics*, so you always add:

  • A primary key@id, usually with @default(autoincrement()) or @default(uuid()).
  • Uniqueness@unique on emails, usernames, slugs.
  • Defaults@default(now()) for timestamps, @default(false) for flags.
  • Indexes@@index([...]) for fields you filter on.
  • The right precisionDecimal for money, BigInt for 64-bit IDs.

These are decisions about your data model, and no converter can infer them from a single payload — treat the generated model as a scaffold you then annotate.

Tips

  • Start from a real response, including optional and edge-case fields, so nullability is correct.
  • Decide relation vs `Json` per nested object before you migrate — it's painful to change later.
  • Fix up money and large IDs to Decimal and BigInt/String immediately.
  • Then run `prisma migrate` to turn the model into tables.

For adjacent conversions see JSON to SQL, JSON to TypeScript interfaces, and JSON to Zod for validating the same data at the API boundary.

Frequently asked questions

Map each field to a Prisma scalar — strings to String, whole numbers to Int, decimals to Float/Decimal, booleans to Boolean, ISO dates to DateTime — then add a primary key with @id. Nullable or missing fields get a trailing ?, and arrays get [].

Make it a relation (a separate model) when you'll query, filter, or join on that data. Use Prisma's Json type when the nested data is variable and document-like and you only ever store and retrieve it whole.

Add a ? after the type, e.g. nickname String?. That makes the column nullable. Base the decision on your real data, not a single sample where the field happened to be null or present.

Use Decimal, not Float. Floating-point types introduce rounding errors in currency arithmetic, so Decimal (backed by an exact numeric database type) is the safe choice for prices and balances.

Database semantics: which field is the primary key, what should be unique or indexed, default values, and precise numeric types like Decimal and BigInt. A JSON payload only shows the shape of the data, so you add those attributes yourself.

Try JSONKit — JSON Developer Tools

Format, validate, convert and diff JSON. Instant, accurate and 100% private.