Describing JSON as a GraphQL type
GraphQL's schema is written in SDL (Schema Definition Language), and a frequent task is turning an existing JSON response into the SDL type that describes it. The mapping is straightforward, with one twist GraphQL adds that JSON doesn't have: an explicit, first-class nullability model. Get that right and the rest is mechanical. This complements JSON to TypeScript — same shape, different target.
The scalar mapping
Given this JSON:
{
"id": "u_1",
"name": "Ada",
"age": 36,
"rating": 4.5,
"isActive": true
}it maps to a GraphQL type using the five built-in scalars:
type User {
id: ID!
name: String!
age: Int!
rating: Float!
isActive: Boolean!
}| JSON value | GraphQL scalar |
|---|---|
| identifier string | ID |
"text" | String |
36 (whole) | Int |
4.5 (decimal) | Float |
true / false | Boolean |
GraphQL has only these five built-in scalars — there's no native Date, so ISO date strings map to String or a custom scalar like DateTime you define yourself.
The twist: nullability and the `!`
This is the concept JSON lacks. In GraphQL, a field is nullable by default, and a trailing ! means non-null (guaranteed present). So the mapping from JSON isn't just about type — it's about whether each field can be missing:
type User {
id: ID! # ! = always present, never null
name: String! # required
nickname: String # no ! = nullable, may be null
}The judgement call: mark a field ! only if it's truly always present in every response. A single JSON sample can't prove that — if nickname is present in your example but sometimes absent, it must stay nullable (no !). Over-using ! is a common mistake, because a non-null field that later returns null becomes a hard query error.
Lists
A JSON array maps to a GraphQL list with square brackets — and lists have *two* nullability positions, the item and the list itself:
{ "tags": ["a", "b"], "scores": [90, 85] }type User {
tags: [String!]! # non-null list of non-null strings
scores: [Int!] # nullable list of non-null ints
}[String!]! reads as "the list is always present, and every item is non-null" — the most common and usually-correct choice for a required collection.
Nested objects become their own types
A nested JSON object becomes a separate GraphQL type referenced by field:
{ "id": "u_1", "address": { "city": "London", "zip": "EC1" } }type User {
id: ID!
address: Address
}
type Address {
city: String!
zip: String!
}GraphQL is a *graph*, so this is also where you'd add fields that don't exist in the JSON at all — a posts: [Post!]! connection the client can traverse, which is the whole point of GraphQL over a flat REST JSON response.
Input types for mutations
The type you generate describes output. To *accept* the same shape in a mutation, GraphQL requires a separate `input` type — output types and input types can't be interchanged:
input CreateUserInput {
name: String!
nickname: String
tags: [String!]!
}
type Mutation {
createUser(input: CreateUserInput!): User!
}What JSON can't express
As with any schema generated from a sample, JSON gives you shape but not graph semantics — you add:
- Relationships between types (the edges that make it a graph).
- Correct nullability, which requires knowing your data, not one payload.
- Enums where a string is really a fixed set (
role: "admin" | "member"→ a GraphQLenum). - Custom scalars for dates, emails, URLs.
- Queries and mutations — the operations, which no data sample contains.
Treat generated SDL as a first draft of the types, then design the actual graph. For related targets see JSON to TypeScript and GraphQL vs REST with JSON; to lock the shape for validation, generate a JSON Schema.