Convert graphQL queries and mutations to easily readable JSON representations.
graphql-query-to-json
=====================
!Statement coverage
!Function coverage
!Line coverage
!Branches badge
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
``sh`
npm install graphql-query-to-jsonor
yarn add graphql-query-to-json
`ts
const {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 |user { name }
| Object Fields | Nested objects | → user: { name: true } |__args
| Arguments | property | user(id: 1) → user: { __args: { id: 1 } } |__aliasFor
| Aliases | Field renaming + | renamed: user → renamed: { __aliasFor: "user" } |$userId
| Variables | Substituted values | → actual variable value |EnumType
| Enums | wrapper | status: ACTIVE → status: { "value": "ACTIVE" } |__on
| Inline Fragments | property | ... on User { name } → __on: { __typeName: "User", name: true } |
`ts
// 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
}
}
}
}
`
`ts
// 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
}
}
}
}
`
`ts
// 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 }
}
}
}
`
`ts
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,
},
},
},
}
`
`ts
// 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
}
}
}
}
}
`
`ts
// 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
}
}
}
`
`ts
// 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
}
}
}
`
`ts
// 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
}
}
}
`
`ts
// 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
}
}
}
`
`ts
// 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
}
}
}
}
}
}
}
}
}
`
`ts
// 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
}
}
}
`
`ts
// 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
}
}
}
}
`
`ts
// 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
}
]
}
}
}
`
`ts
// 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
}
}
}
}
}
}
`
`ts
// 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
}
}
}
}
`
`ts
// 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
}
}
}
`
`ts
// 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:
)
- Named Fragments: Not supported due to multiple definition restriction`ts
// ❌ Named fragments still throw an error
const queryWithFragment = 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: [...] } } }
, @skip are parsed but ignored
- The library focuses on structure extraction rather than directive execution`ts
// ✅ This works but directives are ignored
const queryWithDirective =
// The @include directive won't affect the output structure
`These limitations apply equally to queries, mutations, and subscriptions since they all use the same AST processing logic.
TypeScript Support
Full TypeScript definitions are included:
`ts
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 typed
`Development
$3
`bash
npm run build # Compile TypeScript to JavaScript
npm run watch # Build in watch mode
`$3
`bash
npm test # Run Jest tests
npm run test:coverage # Run tests with coverage
`$3
`bash
npm run lintFull # Run Prettier and ESLint with auto-fix
`Architecture
The library uses a multi-phase approach:
1. Parse: Uses
graphql library to parse query into AST
2. Validate: Ensures all variables are provided
3. Transform: Recursively processes selections, arguments, and aliases
4. Substitute: Replaces variable placeholders with actual valuesKey components:
-
getSelections(): Processes field selections recursively
- getArguments(): Handles all argument types and nesting
- replaceVariables(): Deep variable substitution using lodash.mapValues
- checkEachVariableInQueryIsDefined(): Variable validation with descriptive errorsUse Cases
- 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
Contributing
1. Fork the repository
2. Create a feature branch
3. Make your changes with tests
4. Run
npm run build && npm test`MIT License - see LICENSE file for details