A TypeScript library that converts GraphQL query and mutation strings into structured JSON objects. This library serves as the reverse of json-to-graphql-query, enabling programmatic manipulation and analysis of GraphQL queries.
- ✅ Full GraphQL Support: Queries, mutations and subscriptions
- ✅ Variable Handling: Complete variable substitution with validation
- ✅ Arguments: All argument types (strings, integers, floats, objects, arrays, enums)
- ✅ Aliases: Field aliasing with metadata preservation
- ✅ Inline Fragments: Complete support for conditional type-based field selection
- ✅ Type Safety: Full TypeScript support with comprehensive type definitions
- ✅ Error Handling: Descriptive error messages for malformed queries and missing variables
- ✅ Framework Agnostic: Works with any JavaScript/TypeScript environment
npm install graphql-query-to-json
# or
yarn add graphql-query-to-jsonconst {graphQlQueryToJson} = require("graphql-query-to-json")
const query = `
query GetThisStuff($name: String, $lastName: String) {
viewer {
personal(criteria: {
name: $name,
lastName: $lastName
}) {
name
address
}
}
}
`
const result = graphQlQueryToJson(query, {
variables: {
name: "PETER",
lastName: "SCHMIDT",
},
})
console.log(result)
// Output:
{
query: {
viewer: {
personal: {
__args: {
criteria: {
name: "PETER",
lastName: "SCHMIDT",
},
},
name: true,
address: true,
},
},
},
}The library follows predictable transformation patterns:
| GraphQL Element | JSON Representation | Example |
|---|---|---|
| Scalar Fields | true |
name → name: true |
| Object Fields | Nested objects | user { name } → user: { name: true } |
| Arguments | __args property |
user(id: 1) → user: { __args: { id: 1 } } |
| Aliases | Field renaming + __aliasFor |
renamed: user → renamed: { __aliasFor: "user" } |
| Variables | Substituted values | $userId → actual variable value |
| Enums | EnumType wrapper |
status: ACTIVE → status: { "value": "ACTIVE" } |
| Inline Fragments | __on property |
... on User { name } → __on: { __typeName: "User", name: true } |
// Single and multiple fields
const query = `
query {
viewer {
name
email
}
user {
profile {
avatar
bio
}
}
}
`
const result = graphQlQueryToJson(query)
// Output:
{
query: {
viewer: {
name: true,
email: true
},
user: {
profile: {
avatar: true,
bio: true
}
}
}
}// Query with variables
const query = `
query GetUser($userId: ID!, $includeProfile: Boolean!) {
user(id: $userId) {
name
email
profile @include(if: $includeProfile) {
bio
avatar
}
}
}
`
const result = graphQlQueryToJson(query, {
variables: {
userId: "123",
includeProfile: true
}
})
// Output:
{
query: {
user: {
__args: { id: "123" },
name: true,
email: true,
profile: {
bio: true,
avatar: true
}
}
}
}// Scalar fields that accept arguments
const query = `
query {
userCount(filter: "active")
totalRevenue(currency: "USD", year: 2024)
averageRating(precision: 2)
}
`
const result = graphQlQueryToJson(query)
// Output:
{
query: {
userCount: {
__args: { filter: "active" }
},
totalRevenue: {
__args: { currency: "USD", year: 2024 }
},
averageRating: {
__args: { precision: 2 }
}
}
}const mutation = `
mutation {
getPersonalStuff(name: "PETER") {
personal {
name
address
}
other {
parents
}
}
}
`
const result = graphQlQueryToJson(mutation)
// Output:
{
mutation: {
getPersonalStuff: {
__args: {
name: "PETER",
},
personal: {
name: true,
address: true,
},
other: {
parents: true,
},
},
},
}// Mutation with nested object arguments
const mutation = `
mutation CreateUser($input: UserInput!) {
createUser(input: $input) {
id
name
profile {
email
settings {
theme
notifications
}
}
}
}
`
const result = graphQlQueryToJson(mutation, {
variables: {
input: {
name: "John Doe",
email: "john@example.com",
}
}
})
// Output:
{
mutation: {
createUser: {
__args: {
input: {
name: "John Doe",
email: "john@example.com"
}
},
id: true,
name: true,
profile: {
email: true,
settings: {
theme: true,
notifications: true
}
}
}
}
}// Multiple aliases for the same field
const query = `
query {
currentUser: user(id: 1) {
name
email
}
adminUser: user(id: 2) {
name
permissions
}
guestUser: user(id: 3) {
name
status
}
}
`
const result = graphQlQueryToJson(query)
// Output:
{
query: {
currentUser: {
__aliasFor: "user",
__args: { id: 1 },
name: true,
email: true
},
adminUser: {
__aliasFor: "user",
__args: { id: 2 },
name: true,
permissions: true
},
guestUser: {
__aliasFor: "user",
__args: { id: 3 },
name: true,
status: true
}
}
}// Enums in arguments
const query = `
query {
posts(status: PUBLISHED, orderBy: CREATED_DESC) {
title
content
}
users(role: ADMIN, sortBy: NAME_ASC) {
name
email
}
}
`
const result = graphQlQueryToJson(query)
// Output (enums are wrapped in EnumType objects):
{
query: {
posts: {
__args: {
status: { "value": "PUBLISHED" },
orderBy: { "value": "CREATED_DESC" }
},
title: true,
content: true
},
users: {
__args: {
role: { "value": "ADMIN" },
sortBy: { "value": "NAME_ASC" }
},
name: true,
email: true
}
}
}// Lists and arrays as arguments
const mutation = `
mutation {
updateUser(
id: "123",
tags: ["developer", "typescript", "graphql"],
permissions: [READ, WRITE, ADMIN]
) {
id
tags
permissions
}
}
`
const result = graphQlQueryToJson(mutation)
// Output:
{
mutation: {
updateUser: {
__args: {
id: "123",
tags: ["developer", "typescript", "graphql"],
permissions: [
"READ",
"WRITE",
"ADMIN"
]
},
id: true,
tags: true,
permissions: true
}
}
}// Empty strings, objects, and arrays
const mutation = `
mutation {
createRecord(input: {
name: "",
metadata: {},
tags: [],
count: 0,
isActive: false
}) {
id
status
}
}
`
const result = graphQlQueryToJson(mutation)
// Output:
{
mutation: {
createRecord: {
__args: {
input: {
name: "",
metadata: {},
tags: [],
count: 0,
isActive: false
}
},
id: true,
status: true
}
}
}// Complex nested structures
const query = `
query {
organization {
teams {
members {
user {
profile {
personalInfo {
address {
street
city
country
}
}
}
}
}
}
}
}
`
const result = graphQlQueryToJson(query)
// Output:
{
query: {
organization: {
teams: {
members: {
user: {
profile: {
personalInfo: {
address: {
street: true,
city: true,
country: true
}
}
}
}
}
}
}
}
}// Float arguments and numeric types
const query = `
query GetProducts {
products(
minRating: 4.5,
maxPrice: 99.99,
discount: -10.5,
threshold: 0.001,
scientific: 2.5e3
) {
name
rating
price
}
analytics(
coordinates: {
lat: 40.7128,
lng: -74.006
},
mixed: [1, 2.5, 3, 4.75]
) {
data
}
}
`
const result = graphQlQueryToJson(query)
// Output:
{
query: {
products: {
__args: {
minRating: 4.5, // ✅ Float as number
maxPrice: 99.99, // ✅ Float as number
discount: -10.5, // ✅ Negative float
threshold: 0.001, // ✅ Small decimal
scientific: 2500 // ✅ Scientific notation (2.5e3)
},
name: true,
rating: true,
price: true
},
analytics: {
__args: {
coordinates: {
lat: 40.7128, // ✅ Nested floats
lng: -74.006
},
mixed: ["1", "2.5", "3", "4.75"] // Arrays preserve strings
},
data: true
}
}
}// Single inline fragment
const query = `
query {
posts {
title
... on TextPost {
content
wordCount
}
}
}
`
const result = graphQlQueryToJson(query)
// Output:
{
query: {
posts: {
title: true,
__on: {
__typeName: "TextPost",
content: true,
wordCount: true
}
}
}
}// Multiple inline fragments
const query = `
query {
media {
... on TextPost {
content
author {
name
}
}
... on ImagePost {
imageUrl
altText
}
... on VideoPost {
videoUrl
duration
}
}
}
`
const result = graphQlQueryToJson(query)
// Output:
{
query: {
media: {
__on: [
{
__typeName: "TextPost",
content: true,
author: {
name: true
}
},
{
__typeName: "ImagePost",
imageUrl: true,
altText: true
},
{
__typeName: "VideoPost",
videoUrl: true,
duration: true
}
]
}
}
}// Inline fragments with arguments and variables
const query = `
query GetPosts($limit: Int!) {
posts {
title
... on TextPost {
comments(limit: $limit) {
text
author {
name
}
}
}
}
}
`
const result = graphQlQueryToJson(query, {
variables: { limit: 5 }
})
// Output:
{
query: {
posts: {
title: true,
__on: {
__typeName: "TextPost",
comments: {
__args: { limit: 5 },
text: true,
author: {
name: true
}
}
}
}
}
}// Basic subscription
const subscription = `
subscription {
messageAdded {
id
content
user {
name
email
}
}
}
`
const result = graphQlQueryToJson(subscription)
// Output:
{
subscription: {
messageAdded: {
id: true,
content: true,
user: {
name: true,
email: true
}
}
}
}// Subscription with variables and arguments
const subscription = `
subscription MessageSubscription($userId: ID!, $channel: String!) {
messageAdded(userId: $userId, channel: $channel) {
id
content
timestamp
}
}
`
const result = graphQlQueryToJson(subscription, {
variables: {
userId: "123",
channel: "general"
}
})
// Output:
{
subscription: {
messageAdded: {
__args: {
userId: "123",
channel: "general"
},
id: true,
content: true,
timestamp: true
}
}
}// Subscription with aliases and enums
const subscription = `
subscription {
latestMessage: messageAdded(channel: PUBLIC) {
id
content
}
}
`
const result = graphQlQueryToJson(subscription)
// Output:
{
subscription: {
latestMessage: {
__aliasFor: "messageAdded",
__args: {
channel: { "value": "PUBLIC" }
},
id: true,
content: true
}
}
}Parameters:
query(string): The GraphQL query, mutation, or subscription stringoptions(object, optional):variables(object): Variables referenced in the query
Returns: JSON object representation of the GraphQL query
Throws:
- Error if query syntax is invalid
- Error if variables are referenced but not provided
- Error if query contains multiple operations
While the library supports the core GraphQL features, there are some limitations:
- Inline Fragments: ✅ Fully Supported (e.g.,
... on TypeName) - Named Fragments: Not supported due to multiple definition restriction
// ❌ Named fragments still throw an error
const queryWithFragment = `
query {
user {
...UserFields
}
}
fragment UserFields on User {
id
name
}
`
// Throws: "The parsed query has more than one set of definitions"
// ✅ Inline fragments work perfectly
const queryWithInlineFragment = `
query {
search {
... on User {
name
}
... on Post {
title
}
}
}
`
// Output: { query: { search: { __on: [...] } } }- Directive Handling: Directives like
@include,@skipare parsed but ignored - The library focuses on structure extraction rather than directive execution
// ✅ This works but directives are ignored
const queryWithDirective = `
query($includeEmail: Boolean!) {
user {
name
email @include(if: $includeEmail)
}
}
`
// The @include directive won't affect the output structureThese limitations apply equally to queries, mutations, and subscriptions since they all use the same AST processing logic.
Full TypeScript definitions are included:
import { graphQlQueryToJson } from 'graphql-query-to-json'
interface Variables {
userId: string
limit: number
}
const variables: Variables = {
userId: "123",
limit: 10
}
const result = graphQlQueryToJson(query, { variables })
// Result is properly typednpm run build # Compile TypeScript to JavaScript
npm run watch # Build in watch modenpm test # Run Jest tests
npm run test:coverage # Run tests with coveragenpm run lintFull # Run Prettier and ESLint with auto-fixThe library uses a multi-phase approach:
- Parse: Uses
graphqllibrary to parse query into AST - Validate: Ensures all variables are provided
- Transform: Recursively processes selections, arguments, and aliases
- Substitute: Replaces variable placeholders with actual values
Key components:
getSelections(): Processes field selections recursivelygetArguments(): Handles all argument types and nestingreplaceVariables(): Deep variable substitution using lodash.mapValuescheckEachVariableInQueryIsDefined(): Variable validation with descriptive errors
- Query Analysis: Programmatically analyse GraphQL query structure
- Query Transformation: Convert between query formats
- Testing: Validate query structures in tests
- Documentation Generation: Extract field usage patterns
- Caching Keys: Generate cache keys from query structure
- Fork the repository
- Create a feature branch
- Make your changes with tests
- Run
npm run build && npm test - Submit a pull request
MIT License - see LICENSE file for details