Mapping JSON scalars to GraphQL types
GraphQL is statically typed, so every JSON value has to land on a concrete scalar. The generator reads the literal value, not just the key: a number with no decimal point becomes Int, a number with a decimal becomes Float, quoted text becomes String, and true/false becomes Boolean. JSON has no date type, so timestamps stay String until you define a custom scalar.
// JSON input
{
"id": 1,
"title": "Intro to GraphQL",
"rating": 4.5,
"published": true,
"publishedAt": "2026-06-17T09:00:00Z"
}
// Generated SDL
type Article {
id: Int!
title: String!
rating: Float!
published: Boolean!
publishedAt: String!
}Nested objects become their own types
GraphQL has no inline anonymous object type. Each nested JSON object is therefore lifted into a separate named type (PascalCased from the field name), emitted above the type that references it, and linked by field. A two-level object produces two type declarations.
// JSON input
{
"title": "Intro to GraphQL",
"author": {
"name": "Ada Lovelace",
"verified": true
}
}
// Generated SDL — Author is defined first, then referenced
type Author {
name: String!
verified: Boolean!
}
type Article {
title: String!
author: Author!
}Arrays map to GraphQL list types
A JSON array becomes a list written in bracket notation. The element type is inferred from the first item: an array of strings is [String!]!, and an array of objects generates a named element type first. The default [Type!]! means the list cannot be null and no element can be null — loosen it to [Type] if either can be.
// JSON input
{
"title": "Intro to GraphQL",
"tags": ["graphql", "schema", "sdl"],
"comments": [
{ "body": "Great post", "likes": 12 }
]
}
// Generated SDL
type Comment {
body: String!
likes: Int!
}
type Article {
title: String!
tags: [String!]!
comments: [Comment!]!
}Nullable vs non-null (the ! marker)
A field is rendered non-null (with !) whenever the sample value is present and not null; a JSON null makes the field nullable. Since the tool sees only one sample, treat ! as a starting guess — if a field can be null in production but happened to be filled in your example, drop the !. Non-null is a hard contract: a ! field that resolves to null errors the whole response.
// JSON input — bio is null, deletedAt is null
{
"name": "Ada Lovelace",
"bio": null,
"deletedAt": null
}
// Generated SDL — null fields lose the !
type User {
name: String!
bio: String
deletedAt: String
}Refining ids and enums by hand
Two things cannot be inferred from a single sample. A numeric id maps to Int! by literal type, but GraphQL convention is the ID scalar, which serializes as a string and stays consistent across REST, databases, and caches. And a field like "status": "ACTIVE" is just a string to the generator — only you know the full value set, so promote it to an enum after generating.
// Generated (literal inference)
type User {
id: Int!
status: String!
}
// Refined by hand — ID scalar + enum
enum UserStatus {
ACTIVE
INACTIVE
PENDING
}
type User {
id: ID!
status: UserStatus!
}The root Query type and using the schema
A schema is invalid without a root Query type — it is where every query begins. The generator appends a type Query block returning your top-level type so the SDL loads in Apollo Server, graphql-js, or Pothos without a "Query root type must be provided" error. You still write the resolver yourself.
// Appended automatically
type Query {
article: Article!
}
// Paste straight into Apollo Server as typeDefs
import { ApolloServer } from '@apollo/server'
const server = new ApolloServer({ typeDefs, resolvers })For the full workflow — query structure, graphql-request, graphql-codegen, and JSON Schema mapping — read the JSON to GraphQL guide. If you only need plain types for a REST API, use JSON to TypeScript instead.