map, filter, and transform data structures using GraphQL and JSONPath.
# install peerDependencies
npm install graphql graphql-anywhere graphql-tag rambda jsonpath
# install package from Github packages
npm install --registry=https://npm.pkg.github.com/specialblend @specialblend/mapqlor add .npmrc:
registry=https://registry.npmjs.org/
@specialblend:registry=https://npm.pkg.github.com/specialblend
and run
npm install @specialblend/mapql graphql graphql-anywhere graphql-tag rambda jsonpathimport gql from "graphql-tag";
import map from "@specialblend/mapql";
const data = {
foo: {
bar: "baz",
baz: "faz"
},
// ...
};
const query = gql`
query ExampleFilter {
foo {
# bar
baz
}
}
`;
const result = map(query, data);mapql [queryFile] [sourceFile]
queryFile.graphql query filesourceFileJSON or javascript file exporting object
# compact JSON output
mapql example/TransformLeases.example.graphql example/data.json > example.result.json
# pretty JSON output
PRETTY=true mapql example/TransformLeases.example.graphql example/data.json > example.result.jsonGraphQL query is written in desired result structure, using GraphQL arguments and directives for remapping paths and transforming result values.
β using example data:
expand example data
{
"reportMeta": {
"generated": {
"date": "12/21/2012",
"user": {
"name": "System Admin",
"email": "[email protected]"
}
}
},
"leases": [
{
"leaseId": 1234,
"residents": [
{
"name": "Alice",
"dob": "1/1/1111",
"email": "[email protected]"
},
{
"name": "Bob",
"dob": "12/12/1212",
"email": "[email protected]"
}
],
"address": {
"street": "1234 Main St.",
"city": "New York City",
"stateCode": "NY",
"zipCode": "11210"
}
},
{
"leaseId": 2345,
"residents": [
{
"name": "John Smith",
"dob": "2/2/2222",
"email": "[email protected]"
},
{
"name": "Jane Doe",
"dob": "11/11/1111",
"email": "[email protected]"
}
],
"address": {
"street": "1333 3rd St.",
"city": "Newark",
"stateCode": "NJ",
"zipCode": "07195"
}
},
{
"leaseId": 4567,
"residents": [
{
"name": "Alice",
"dob": "1/1/1111",
"email": "[email protected]"
},
{
"name": "Bob",
"dob": "12/12/1212",
"email": "[email protected]"
}
],
"address": {
"street": "1333 3rd St.",
"city": "Newark",
"stateCode": "NJ",
"zipCode": "07195"
}
}
]
}β with following query:
query GetAddresses1 {
leases(from: "leases") {
contractNumber(from: "leaseId")
address(from: "address") {
street(from: "street")
city(from: "city")
stateCode(from: "stateCode")
zipCode(from: "zipCode")
}
}
reportInfo {
date(from: "reportMeta.generated.date")
manager(from: "reportMeta.generated.user.name")
exampleVersion @const(of: "v1.2.3.4")
}
}β we get our result:
{
"leases": [
{
"contractNumber": 1234,
"address": {
"street": "1234 Main St.",
"city": "New York City",
"stateCode": "NY",
"zipCode": "11210"
}
},
{
"contractNumber": 2345,
"address": {
"street": "1333 3rd St.",
"city": "Newark",
"stateCode": "NJ",
"zipCode": "07195"
}
},
{
"contractNumber": 4567,
"address": {
"street": "1333 3rd St.",
"city": "Newark",
"stateCode": "NJ",
"zipCode": "07195"
}
}
],
"reportInfo": {
"date": "12/21/2012",
"manager": "System Admin",
"exampleVersion": "v1.2.3.4"
}
}- β
foo(from:$path)will mapfoofield in result to JSONPath$path. - β
foo @const(of: "bar")will mapfoofield in result to fixed value"bar".
β let's see if we can't shorten it a little:
query GetAddresses2 {
leases @map {
contractNumber(from: "leaseId")
address @map {
street @map
city @map
stateCode @map
zipCode @map
}
}
reportInfo {
date(from: "reportMeta.generated.date")
manager(from: "reportMeta.generated.user.name")
exampleVersion @const(of: "v1.2.3.4")
}
}- β
foo @mapis short forfoo(from: "foo").
β let's go little bit shorter.
{
leases @map {
contractNumber: leaseId
address @map {
street
city
stateCode
zipCode
}
}
reportInfo(from: "reportMeta.generated") {
date
manager(from: "user.name")
exampleVersion @const(of: "v1.2.3.4")
}
}- β
querykeyword and name are optional. - β
foo: baris short forfoo(from: "bar")when path is simple field name (not JSONPath). - β
@mapdirective is optional and implicit on leaf nodes.
By default, mapql will return head (first element) of result from JSONPath query. This can be disabled with argument head: false, useful when expecting list of values. Default value is head: true.
example:
const data = {
exampleObj: {
exampleStr: "hello world",
},
destinations: [
{
array_string: "foo",
},
{
array_string: "bar",
},
{
array_string: "baz",
},
],
} query PathHead {
exampleStr
exampleStrHead(head: true)
exampleStrNoHead(head: false)
destinationsHead(from: "destinations[*].array_string", head: true)
destinationsNoHead(from: "destinations[*].array_string", head: false)
destinations(from: "destinations[*].array_string")
}{
"destinations": "foo",
"destinationsHead": "foo",
"destinationsNoHead": [
"foo",
"bar",
"baz"
],
"exampleStr": "hello world",
"exampleStrHead": "hello world",
"exampleStrNoHead": [
"hello world"
]
}β query:
query FilterActiveLeases {
leases(filter: { match: { isActive: true } }) {
isActive
residents @map {
name
email
}
address @map {
street
city
zipCode
}
}
}β result:
expand result
{
"leases": [
{
"isActive": true,
"residents": [
{
"name": "Alice",
"email": "[email protected]"
},
{
"name": "Bob",
"email": "[email protected]"
}
],
"address": {
"street": "1234 Main St.",
"city": "New York City",
"zipCode": "11210"
}
},
{
"isActive": true,
"residents": [
{
"name": "Alice",
"email": "[email protected]"
},
{
"name": "Bob",
"email": "[email protected]"
}
],
"address": {
"street": "1333 3rd St.",
"city": "Newark",
"zipCode": "07195"
}
}
]
}- β
foo(filter: { match: { bar: "baz" } })can be used to filter fields using structured selectors.
β using nested match:
query FilterByStateCode {
leases(filter: { match: { address: { stateCode: "NJ" } } }) {
residents @map {
name
email
}
address @map {
street
city
zipCode
}
}
}β query:
query TransformLeases {
leases @map {
contractNumber: leaseId @String @concat(before: "#")
address @map {
street
streetLine2 @default(to: "N/A")
city
stateCode
zipCode @parseInt
}
}
reportMetaJson: reportMeta @toJson
}β result:
{
"leases": [
{
"contractNumber": "#1234",
"address": {
"street": "1234 Main St.",
"streetLine2": "#789",
"city": "New York City",
"stateCode": "NY",
"zipCode": 11210
}
},
{
"contractNumber": "#2345",
"address": {
"street": "1333 3rd St.",
"streetLine2": "N/A",
"city": "Newark",
"stateCode": "NJ",
"zipCode": 7195
}
},
{
"contractNumber": "#4567",
"address": {
"street": "1333 3rd St.",
"streetLine2": "N/A",
"city": "Newark",
"stateCode": "NJ",
"zipCode": 7195
}
}
],
"reportMetaJson": "{\"generated\":{\"date\":\"12/21/2012\",\"user\":{\"name\":\"System Admin\",\"email\":\"[email protected]\"}}}"
}- β directives are composable and will execute in left-to-right composition order.
β οΈ transformation directives on parent nodes are called before child nodes.
@default(to: any): set default value@parseInt: cast numeric string to integer@parseFloatcast numeric string to float@String: cast value tostring@Boolean: cast value intoboolean@toJson: format value to JSON string@fromJson: parse value from JSON string@concat(before?: string, after?: string): concatenate string. arguments not mutually exclusive.@not: logical not.@of: return singleton array of value.
@substr(from: $index, len: $index): return substring of value.@slice(from: $index, to: $index): slice array.@add(x: number): add to value.@sub(x: number): subtract from value.@mul(x: number): multiply value.@prop(_: string): return property of object.@path(_: string[]): return path of object.@head: return first element of array.@init: return all except last element of array.@tail: return all except first element of array.@last: return last element of array.