GraphQL filtering using jq and JSONPath
Table of contents
What is jq?
jq is a lightweight and flexible command-line JSON processor. It extracts and manipulates data from JSON documents with simple and powerful filters, letting you transform JSON data in various ways.
Why use jq?
- Filtering: quickly filter, slice, and extract specific parts of JSON.
- Transformation: modify JSON structures, useful for cleaning, formatting, or transforming JSON data.
- Simplicity: a simple syntax lets you do complex manipulations easily.
Why jq is used with GraphQL
GraphQL responses are returned in JSON format. Since GraphQL allows flexible queries, the structure of the returned data can vary significantly, making it necessary to manipulate the response for specific use cases. This is where jq comes in: after querying a GraphQL endpoint, you might need to isolate or transform the returned JSON data for processing, and jq makes this easy, letting you select only the fields you need from the complex JSON response.
What is JSONPath?
JSONPath is a query language for JSON, similar to XPath for XML. It extracts specific parts of a JSON document based on a path expression, and is often used when interacting with JSON data in various programming environments.
Why use JSONPath?
- Simplifies JSON querying: easy navigation and extraction from nested JSON structures using path expressions.
- Integration with APIs: many tools and libraries support JSONPath for working with JSON-based APIs.
- Efficient data extraction: extract only the data you need, saving time and resources.
Why JSONPath is used with GraphQL
GraphQL responses are JSON-based and can be deeply nested with numerous fields. JSONPath provides a simple, readable way to extract specific data from this structure, making it an excellent tool for working with GraphQL results.
Example usage
In real-world scenarios, especially with GraphQL APIs, tools like jq and JSONPath help you interact with responses more efficiently. Zendro’s GraphiQL integrates these filtering techniques directly into its interface.
Filtering in Zendro
Zendro’s GraphiQL interface provides a flexible and interactive way to query the data, applying filters directly to your queries to refine the response. It lets you request only the fields you need, and presents the data in JSON format.
In the results, you can see the JSON output, which can be further processed with jq or JSONPath for specific tasks, such as:
- Filtering specific fields: extract only the fields that matter to you from the GraphQL response.
- Nested data extraction: deal with nested data structures, and query deeper levels of the data tree with additional tools like
jqorJSONPath.
Once you have a query, at the top of the interface there are several buttons. Clicking Filter opens a section at the bottom of the screen where you can select jq or JSONPath, write your filter, and see the results.

The filters you use are applied to the query results. You can reproduce the results by accessing the Zendro-BrAPI data warehouse.
Filtering with jq
For example, if we want the names of the studies in the trial example, calling only 2 (limit:2), we expect 2 names:

Explanation
.trials[]:.trialsaccesses thetrialsproperty of the root object. The[]selects all elements of that array — injq,[]iterates over an array to extract all its elements..studiesFilter[]: same idea — selects thestudiesFilterproperty inside each element oftrials, again using[]to access each element withinstudiesFilter..studyName: finally, accesses thestudyNameproperty inside eachstudiesFilterobject.
Or, to get only the variable name and its value, ignoring null values:
[.trials[].studiesFilter[].observationsFilter[] | select(.value != null) | { name: .observationVariable.observationVariableName, value: .value }]
Explanation
.trials[]: accesses thetrialsproperty of the root object, selecting all elements of thetrialsarray..studiesFilter[]: within eachtrialselement, accessesstudiesFilterand iterates over its elements..observationsFilter[]: within eachstudiesFilterelement, accessesobservationsFilterand iterates over its elements.select(.value != null): filters the elements ofobservationsFilter, keeping only those wherevalueis not null.{ name: .observationVariable.observationVariableName, value: .value }: creates a new object for each filtered element, extractingobservationVariableNamefromobservationVariableandvaluefrom the current object, renamednameandvalue.
Result:
{
"data": [
{
"name": "fresh root yield|CO_334:0000013",
"value": 5
},
{
"name": "germination count|CO_334:0000166",
"value": 93.3
},
{
"name": "harvest index variable|CO_334:0000015",
"value": 0.2
},
{
"name": "initial plant vigor assessment 1-5|CO_334:0000220",
"value": 4
},
{
"name": "plant height measurement in cm|CO_334:0000018",
"value": 240
},
{
"name": "plant stands harvested counting|CO_334:0000010",
"value": 9
},
{
"name": "rotten root percentage|CO_334:0000229",
"value": 0
},
{
"name": "selected variety boolean 0&1|CO_334:0000232",
"value": 0
},
{
"name": "fresh root yield|CO_334:0000013",
"value": 9
},
{
"name": "germination count|CO_334:0000166",
"value": 60
},
{
"name": "harvest index variable|CO_334:0000015",
"value": 0.37
},
{
"name": "initial plant vigor assessment 1-5|CO_334:0000220",
"value": 3
},
{
"name": "plant stands harvested counting|CO_334:0000010",
"value": 9
},
{
"name": "selected variety boolean 0&1|CO_334:0000232",
"value": 0
}
]
}
Filtering with JSONPath
Now, if we want the IDs of the studies in the trial example, calling only 2 (limit:2), we expect 2 IDs:

Explanation
JSONPath’s syntax is very similar to file paths or regular expressions. It navigates through a JSON object or array and extracts specific elements.
$: the dollar sign represents the root object — the starting point of the query..trials[*]: accesses thetrialsproperty of the root object and selects all elements of that array. The asterisk[*]selects all elements..studiesFilter[*]: selects thestudiesFilterproperty within eachtrialsobject, again selecting all elements..studyDbId: finally, after traversing all elements ofstudiesFilter, selects thestudyDbIdproperty within each.
Key differences from jq
JSONPath does not support creating new objects like jq does with { name: .observationVariable.observationVariableName, value: .value }. To get both name and value in a combined result, you’d typically need separate queries and combine the results programmatically.
$.trials[*].studiesFilter[*].observationsFilter[?(@.value != null)].observationVariable.observationVariableName
$.trials[*].studiesFilter[*].observationsFilter[?(@.value != null)].value
Explanation
$.trials[*]: accesses thetrialsproperty of the root object and selects all elements.$.studiesFilter[*]: within eachtrialselement, selects thestudiesFilterproperty and iterates over it.$.observationsFilter[?(@.value != null)]: within eachstudiesFilterelement, selectsobservationsFilterand filters elements wherevalueis not null (similar toselect(.value != null)injq)..observationVariable.observationVariableName: selects theobservationVariableNameproperty insideobservationVariable..value: selects thevalueproperty within the filteredobservationsFilterelements.
By combining GraphQL with tools like jq and JSONPath, you can efficiently retrieve and manipulate data, making your API interactions even more powerful.