A utility package that allows you to convert GraphQL queries to SPARQL or SPARQL algebra, using a JSON-LD context. Such queries are also known as GraphQL-LD queries.
Supported JSON-LD context features:
- Key-value mapping between shorthands and URIs.
@type
: Sets the RDF datatype.@language
: Sets the RDF language.@id
: Identifies the IRI of a term.@reverse
Reverses the direction of a property.
Looking for GraphQL-LD?
$ npm install [-g] graphql-to-sparql
The graphql-to-sparql
converts GraphQL queries to SPARQL.
$ graphql-to-sparql '{ "hero": "https://example.org/hero", "name": "https://example.org/name" }' '{ hero { name } }'
$ graphql-to-sparql my-context.jsonld my-query.graphql
The programmatic API can be invoked as follows:
const Converter = require('graphql-to-sparql').Converter;
const algebra = await new Converter().graphqlToSparqlAlgebra('{ hero { name } }', {
"hero": "https://example.org/hero",
"name": "https://example.org/name",
"friends": "https://example.org/friends"
});
The resulting object is SPARQL algebra.
Below you can find a couple of examples of how this library converts GraphQL queries to SPARQL. These examples are based on the GraphQL documentation.
GraphQL trees are converted to SPARQL graphs by chaining triple patterns.
Context:
{
"me": "https://example.org/me",
"name": "https://example.org/name"
}
GraphQL:
{
me {
name
}
}
SPARQL:
SELECT ?me_name WHERE {
_:b1 <https://example.org/me> ?me.
?me <https://example.org/name> ?me_name.
}
Nodes can be nested to any depth, and just produce more triple patterns.
Context:
{
"hero": "https://example.org/hero",
"name": "https://example.org/name",
"friends": "https://example.org/friends"
}
GraphQL:
{
hero {
name
# Queries can have comments!
friends {
name
}
}
}
SPARQL:
SELECT ?hero_name ?hero_friends_name WHERE {
_:b1 <https://example.org/hero> ?hero.
?hero <https://example.org/name> ?hero_name.
?hero <https://example.org/friends> ?hero_friends.
?hero_friends <https://example.org/name> ?hero_friends_name.
}
GraphQL allows arguments to be passed to nodes, which are converted to triple objects in SPARQL.
Context:
{
"human": "https://example.org/human",
"id": "https://example.org/id",
"name": "https://example.org/name",
"height": "https://example.org/height"
}
GraphQL:
{
human(id: "1000") {
name
height
}
}
SPARQL:
SELECT ?human_name ?human_height WHERE {
_:b1 <https://example.org/human> ?human.
?human <https://example.org/id> "1000".
?human <https://example.org/name> ?human_name.
?human <https://example.org/height> ?human_height.
}
In some cases, you may have clashing variable names in your GraphQL query. For these situations, aliases can be used to make your rename variables.
Context:
{
"hero": "https://example.org/hero",
"name": "https://example.org/name",
"episode": "https://example.org/episode",
"EMPIRE": "https://example.org/types/Empire",
"JEDI": "https://example.org/types/Jedi"
}
GraphQL:
{
empireHero: hero(episode: EMPIRE) {
name
}
jediHero: hero(episode: JEDI) {
name
}
}
SPARQL:
SELECT ?empireHero_name ?jediHero_name WHERE {
_:b1 <https://example.org/hero> ?empireHero.
?empireHero <https://example.org/episode> <https://example.org/types/Empire>.
?empireHero <https://example.org/name> ?empireHero_name.
_:b1 <https://example.org/hero> ?jediHero.
?jediHero <https://example.org/episode> <https://example.org/types/Jedi>.
?jediHero <https://example.org/name> ?jediHero_name.
}
GraphQL fragments can be used to abstract certain parts of your query tree to reuse them in different places.
GraphQL always applies fragments on certain types, which are translated to RDF https://www.w3.org/1999/02/22-rdf-syntax-ns#type
predicates.
Context:
{
"hero": "https://example.org/hero",
"name": "https://example.org/name",
"appearsIn": "https://example.org/appearsIn",
"friends": "https://example.org/friends",
"episode": "https://example.org/episode",
"EMPIRE": "https://example.org/types/Empire",
"JEDI": "https://example.org/types/Jedi"
}
GraphQL:
{
leftComparison: hero(episode: EMPIRE) {
...comparisonFields
}
rightComparison: hero(episode: JEDI) {
...comparisonFields
}
}
fragment comparisonFields on Character {
name
appearsIn
friends {
name
}
}
SPARQL:
SELECT ?leftComparison_name ?leftComparison_appearsIn ?leftComparison_friends_name ?rightComparison_name ?rightComparison_appearsIn ?rightComparison_friends_name WHERE {
_:b1 <https://example.org/hero> ?leftComparison.
?leftComparison <https://example.org/episode> <https://example.org/types/Empire>.
OPTIONAL {
?leftComparison <https://www.w3.org/1999/02/22-rdf-syntax-ns#type> undefined:Character.
?leftComparison <https://example.org/name> ?leftComparison_name.
?leftComparison <https://example.org/appearsIn> ?leftComparison_appearsIn.
?leftComparison <https://example.org/friends> ?leftComparison_friends.
?leftComparison_friends <https://example.org/name> ?leftComparison_friends_name.
}
_:b1 <https://example.org/hero> ?rightComparison.
?rightComparison <https://example.org/episode> <https://example.org/types/Jedi>.
OPTIONAL {
?rightComparison <https://www.w3.org/1999/02/22-rdf-syntax-ns#type> undefined:Character.
?rightComparison <https://example.org/name> ?rightComparison_name.
?rightComparison <https://example.org/appearsIn> ?rightComparison_appearsIn.
?rightComparison <https://example.org/friends> ?rightComparison_friends.
?rightComparison_friends <https://example.org/name> ?rightComparison_friends_name.
}
}
Defining variables is only supported via the programmatic API (IVariablesDictionary
) at the time of writing.
Variables can be defined to make queries parameterizable, so that the source query does not have to be changed for every single case.
Context:
{
"hero": "https://example.org/hero",
"name": "https://example.org/name",
"friends": "https://example.org/friends",
"episode": "https://example.org/episode",
"JEDI": "https://example.org/types/Jedi"
}
GraphQL:
query HeroNameAndFriends($episode: Episode = "JEDI") {
hero(episode: $episode) {
name
friends {
name
}
}
}
SPARQL:
SELECT ?hero_name ?hero_friends_name WHERE {
_:b1 <https://example.org/hero> ?hero.
?hero <https://example.org/episode> <https://example.org/types/Jedi>.
?hero <https://example.org/name> ?hero_name.
?hero <https://example.org/friends> ?hero_friends.
?hero_friends <https://example.org/name> ?hero_friends_name.
}
Defining variables is only supported via the programmatic API (IVariablesDictionary
) at the time of writing.
Based on the definition of variables, query behaviour can change using GraphQL directives,
such as @include(if: Boolean)
and @skip(if: Boolean)
.
Context:
{
"hero": "https://example.org/hero",
"name": "https://example.org/name",
"friends": "https://example.org/friends",
"episode": "https://example.org/episode",
"JEDI": "https://example.org/types/Jedi"
}
GraphQL:
query Hero($episode: Episode, $withFriends: Boolean! = true) {
hero(episode: $episode) {
name
friends @include(if: $withFriends) {
name
}
}
}
SPARQL:
SELECT ?hero_name ?hero_friends_name WHERE {
_:b1 <https://example.org/hero> ?hero.
?hero <https://example.org/episode> <https://www.w3.org/1999/02/22-rdf-syntax-ns#nil>.
?hero <https://example.org/name> ?hero_name.
?hero <https://example.org/friends> ?hero_friends.
?hero_friends <https://example.org/name> ?hero_friends_name.
}
Similar to regular fragments, inline fragments can be used to scope a block to a certain type.
Context:
{
"hero": "https://example.org/hero",
"name": "https://example.org/name",
"primaryFunction": "https://example.org/primaryFunction",
"height": "https://example.org/height",
"Droid": "https://example.org/types/Droid",
"Human": "https://example.org/types/Human"
}
GraphQL:
query HeroForEpisode {
hero {
name
... on Droid {
primaryFunction
}
... on Human {
height
}
}
}
SPARQL:
SELECT ?hero_name ?hero_primaryFunction ?hero_height WHERE {
_:b1 <https://example.org/hero> ?hero.
?hero <https://example.org/name> ?hero_name.
OPTIONAL {
?hero <https://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://example.org/types/Droid>.
?hero <https://example.org/primaryFunction> ?hero_primaryFunction.
}
OPTIONAL {
?hero <https://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://example.org/types/Human>.
?hero <https://example.org/height> ?hero_height.
}
}
Some meta fields, such as __typename
can be used to bind to the type of a node.
Context:
{
"search": "https://example.org/search",
"text": "https://example.org/text",
"name": "https://example.org/name",
"Droid": "https://example.org/types/Droid",
"Human": "https://example.org/types/Human"
}
GraphQL:
{
search(text: "an") {
__typename
... on Human {
name
}
... on Droid {
name
}
... on Starship {
name
}
}
}
SPARQL:
SELECT ?search___typename ?search_name ?search_name ?search_name WHERE {
_:b1 <https://example.org/search> ?search.
?search <https://example.org/text> "an".
?search <https://www.w3.org/1999/02/22-rdf-syntax-ns#type> ?search___typename.
OPTIONAL {
?search <https://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://example.org/types/Human>.
?search <https://example.org/name> ?search_name.
}
OPTIONAL {
?search <https://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://example.org/types/Droid>.
?search <https://example.org/name> ?search_name.
}
OPTIONAL {
?search <https://www.w3.org/1999/02/22-rdf-syntax-ns#type> undefined:Starship.
?search <https://example.org/name> ?search_name.
}
}
The magical arguments first
and offset
can be used to respectively set the limit and offset of query results.
Furthermore, the magical totalCount
field will bind to the total number of matches, irrespective of the first
and offset
fields.
Context:
{
"hero": "https://example.org/hero",
"name": "https://example.org/name",
"friends": "https://example.org/friends"
}
GraphQL:
{
hero {
name
friends(first:2 offset:10) {
totalCount
name
}
}
}
SPARQL:
SELECT ?hero_name ?hero_friends_name ?hero_friends_totalCount WHERE {
_:b1 <https://example.org/hero> ?hero.
?hero <https://example.org/name> ?hero_name.
{
SELECT * WHERE {
{
SELECT * WHERE {
?hero <https://example.org/friends> ?hero_friends.
?hero_friends <https://example.org/name> ?hero_friends_name.
}
}
{ SELECT (COUNT(?hero_friends) AS ?hero_friends_totalCount) WHERE { ?hero <https://example.org/friends> ?hero_friends. } }
}
OFFSET 10
LIMIT 2
}
}
While this is not a default feature of GraphQL,
this library allows you to select by certain values of properties.
This is done using the _
argument, which takes a value.
GraphQL:
{
name(_:"Han Solo")
description
}
Using id
fields, the id (or subject) of entities can be queried or defined.
Context:
{
"hero": "https://example.org/hero",
"HAN_SOLO": "https://example.org/HanSolo",
"name": "https://example.org/name",
"friend": "https://example.org/friend"
}
GraphQL:
{
hero {
id
name
}
}
SPARQL:
SELECT ?hero_name ?hero_id WHERE {
_:b1 <https://example.org/hero> ?hero_id.
?hero_id <https://example.org/name> ?hero_name.
}
GraphQL:
{
hero(_:HAN_SOLO) {
name
}
}
SPARQL:
SELECT ?hero_name WHERE {
_:b1 <https://example.org/hero> <https://example.org/HanSolo>.
<https://example.org/HanSolo> <https://example.org/name> ?hero_name.
}
GraphQL:
{
friend(_:HAN_SOLO)
name
}
SPARQL:
SELECT ?name WHERE {
?b1 <https://example.org/friend> <https://example.org/HanSolo>;
<https://example.org/name> ?name.
}
Using graph
fields, the named graph can be queried or defined.
When graph
is used as a field,
this will have as side-effect that fields inside the node can be selected from any graph,
instead of only the default graph.
Context:
{
"hero": "https://example.org/hero",
"EMPIRE": "https://example.org/EMPIRE",
"name": "https://example.org/name",
"friend": "https://example.org/friend"
}
GraphQL:
{
hero {
graph
name
}
}
SPARQL:
SELECT ?hero_name ?hero_id WHERE {
_:b1 <https://example.org/hero> ?hero.
GRAPH ?graph {
?hero <https://example.org/name> ?hero_name.
}
}
GraphQL:
{
hero {
graph(_:EMPIRE)
name
friend
}
}
SPARQL:
SELECT ?hero_name WHERE {
_:b1 <https://example.org/hero> ?hero.
GRAPH <https://example.org/EMPIRE> {
?hero <https://example.org/name> ?hero_name;
<https://example.org/friend> ?hero_friend.
}
}
GraphQL:
{
hero(graph:EMPIRE) {
name
friend
}
}
SPARQL:
SELECT ?hero_name WHERE {
GRAPH <https://example.org/EMPIRE> {
_:b1 <https://example.org/hero> ?hero.
?hero <https://example.org/name> ?hero_name.
<https://example.org/friend> ?hero_friend.
}
}
GraphQL:
{
friend(graph:EMPIRE)
name
}
SPARQL:
SELECT ?name WHERE {
GRAPH <https://example.org/EMPIRE> {
?b1 <https://example.org/friend> ?friend;
}
?b1 <https://example.org/name> ?name.
}
By default, all fields that are defined in the query are required to have results. If any of the fields do not produce results, the full query results set will be empty.
However, in some cases you may not be certain that a field will have results.
In those cases, you may want to mark a field as optional, using the @optional
directive.
Context:
{
"hero": "https://example.org/hero",
"name": "https://example.org/name",
"friend": "https://example.org/friend"
}
GraphQL:
query {
hero {
name @optional
friend @optional
}
}
SPARQL:
SELECT ?hero_name ?hero_friend WHERE {
?b1 <https://example.org/hero> ?hero.
OPTIONAL { ?hero <https://example.org/name> ?hero_name. }
OPTIONAL { ?hero <https://example.org/friend> ?hero_friend. }
}
If you want to reverse the relationship between
a parent a child node,
you can use the @reverse
context option for a field.
Context:
{
"hero": "https://example.org/hero",
"name": "https://example.org/name",
"friend": { "@reverse": "https://example.org/friend" }
}
GraphQL:
query {
hero {
friend {
name
}
}
}
SPARQL:
SELECT ?hero_friend_name WHERE {
?b1 <https://example.org/hero> ?hero.
?hero_friend <https://example.org/friend> ?hero;
<https://example.org/name> ?hero_friend_name.
}
If multiple fields are applicable for retrieving a value,
then you can define all of them via the alt
argument.
Context:
{
"hero": "https://example.org/hero",
"name": "https://example.org/name",
"firstName": "https://example.org/firstName"
}
GraphQL:
{
hero {
name(alt: firstName)
}
}
If you want to define more than one alternative, you can define them in a list like this: name(alt: [firstName, nickName])
SPARQL:
SELECT ?hero_name WHERE {
?b1 <https://example.org/hero> ?hero.
?hero (<https://example.org/name>|<https://example.org/firstName>) ?hero_name.
}
Using a tool such as SPARQL-Results+JSON to tree,
results from converted queries can be compacted.
By default, all values will be considered plural, and values will always be emitted in an array.
With the singularizeVariables
option, you can specify which values should be singularized and not wrapped in an array anymore.
To simplify this process, graphql-to-sparql allows @single
or @plural
directives to be added inside the queries to indicate which fields should be singularized and which ones should remain plural.
If no directive is present, everything will remain plural.
For this, graphql-to-sparql allows an singularizeVariables
object to be passed via options,
which can then be used by other tools for compaction.
For example:
import {Converter as TreeConverter} from "sparqljson-to-tree";
const singularizeVariables = {};
const algebra = await new Converter().graphqlToSparqlAlgebra('{ hero { name } }', {
"hero": "https://example.org/hero",
"name": "https://example.org/name",
"friends": "https://example.org/friends"
}, { singularizeVariables });
const response = { ... }; // Passed to some query engine like Comunica
const jsonResult = new TreeConverter().sparqlJsonResultsToTree(response, { singularizeVariables });
Available directives:
Directive | Meaning |
---|---|
@single |
This field will be singular. |
@plural |
This field will be plural. |
@single(scope: all) |
This field and all child fields will be singular. |
@plural(scope: all) |
This field and all child fields will be plural. |
By default, all fields all plural. So there is an implicit @plural(scope: all)
directive on the query root.
Query:
{
hero {
name @single
}
}
Example output:
[
{
"hero": [
{
"name": "Alice"
},
{
"name": "Bob"
}
]
}
]
Query:
query @single(scope: all) {
hero {
name
}
}
Example output:
{
"hero": {
"name": "Alice"
}
}
Query:
query @single(scope: all) {
hero @plural {
name
}
}
Example output:
{
"hero": [
{
"name": "Alice"
}
]
}
This software is written by Ruben Taelman.
This code is released under the MIT license.