[QROQ] Query Language
lib.js
import { parse, evaluate } from "groq-js" export let groq = async ([query]) => { let tree = parse(query) return async dataset => { let result = await evaluate(tree, { dataset }) return await result.get() } }
Projections in GROQ enable you to restructure your filtered results into any form you want. You can create custom new fields based on the current values in scope and prepare your data exactly the way you want to be displayed to users.
Create a basic query which return all the data:
index.js
import fs from "fs-extra" import { groq } from "./lib.js" let people = [ { name: 'wan', age: 23 }, { name: 'tian', age: 27 } ] let query = await groq` * ` let result = await query(people) console.log(result) // [ { name: 'wan', age: 23 }, { name: 'tian', age: 27 } ]
Add a filter for the query:
let query = await groq` *[name=='tian'] ` let result = await query(people) console.log(result) // [ { name: 'tian', age: 27 } ]
If we just want the name prop:
let query = await groq` *[name=='tian'].name ` let result = await query(people) console.log(result) // [ 'tian' ]
You can restructure the return data:
let query = await groq` *[name=='tian'] {"newName": name + "new", "newAge": age + 5} ` // [ { newName: 'tiannew', newAge: 32 } ]
GROQ's select
function works kind of like a JavaScript switch statement. You give it some conditions and a default and it will assign the first condition that passes to the new field you've created.
Using select function for condition, select function works as `switch` function
let people = [ { name: 'wan', age: 23 }, { name: 'tian', age: 27 },{ name: 'yue', age: 31 } ] let query = await groq` *[] { "fristName": name, "group": select( age < 30 => "young", "old" ) } ` /** * [ { fristName: 'wan', group: 'young' }, { fristName: 'tian', group: 'young' }, { fristName: 'yue', group: 'old' } ] */
So what code does is
if age < 30, assign group value as young
Default value as old
You can combine mutli groups:
let query = await groq` *[] { "firstName": name, "group": select( age < 20 => "too young", age < 30 => "young", "Super" ) } { "description": firstName + " is " + group } ` /** * [ { description: 'wan is too young' }, { description: 'tian is young' }, { description: 'yue is Super' } ] */
As you can see, this will reconstructure the data.
The in
keyboard in GROQ allows you to compare properties from your current JSON object scope against an Array of values to control which values should be returned. This becomes very important when matching against ids, types, and other fields that might exist on two separate JSON objects.
let query = await groq` *["Poison" in type] `
It finds all the pokemon which is Poison type.
let query = await groq` *[egg in ["5 km"]] `
It finds all the pokeon in 5 km egg.
let query = await groq` *[egg in ["5 km", "2 km"]] `
Find both 2 km and 5km.
When working with large datasets, or when you simply want the first couple of results, it's essential to understand how to slice an Array using GROQ. The syntax is simple and can be used on any Array from your JSON, it's mainly a matter of knowing where you want your start index and end index of the Array of results you want to return.
Let's say we want to only get first 5 results:
let query = await groq` *[egg in ["5 km", "2 km"]] [0..5] {"hatch": name + " hatches in " + egg} .hatch `
It is index based, so from index 0 to index 5, you will get 6 results.
If you just want 5 result, instead of 6:
let query = await groq` *[egg in ["5 km", "2 km"]] [0...5] {"hatch": name + " hatches in " + egg} .hatch `
GROQ provides a useful order
function for sorting the results of any Array in your JSON. Pass in the name of the property that you want to sort on and then the desc
or asc
to determine the order and you'll get your data back just how you want it.
Using order(prop) for asc sorting:
let query = await groq` *[egg in ["5 km", "2 km"]] [0..10] {id, name} | order(name) `
For desc:
let query = await groq` *[egg in ["5 km", "2 km"]] [0..10] {id, name} | order(name desc) `
Let's say, you only return names, you need to use '@' sign to return the current scope:
let query = await groq` *[egg in ["5 km", "2 km"]] [0..10].name | order(@ desc) `
Notice that following two has the same effect:
let query = await groq` *[] | order(@.name) ` let query = await groq` *[] | order(name) `
GROQ enables you to join together arrays of JSON objects by looping through arrays inside of projections and using the in
keyword to match items based on values. This lesson uses Pokemon to demonstrate matching a base Pokemon with the Pokemon's evolutions from a Pokedex dataset.
let query = await groq` *[] [0..5] { name, num, "evolutions": *[num in ^.next_evolution[].num]{name, num} } `
In the parent query: we create a new prop "evolutions" which contains current pokemon's 'next_evolution'.
^: is referingn to parnet query:
{
name,
num,
..
}
We can also use GROQ's in
keyword in our filters and negate it using the !()
syntax. This allows us to exclude items that might be included later on in joins before we even get to the projection stage of our query. This example demonstrates filtering out all of the evolutions of Pokemon since they will be included by joining them into the base Pokemon objects.
let query = await groq` *[!(num in *[].next_evolution[].num)] [0..5] { name, num, "evolutions": *[num in ^.next_evolution[].num]{name, num} } `
GROQ enables you to join together data in whatever format you want. Based on the dataset, sometimes the query needs to include a subquery where you must track which scope the query is running to be able to compare values against values in the parent scope. The parent scope can be accessed through the ^
so you know which scope the value belongs to.
let query = await groq` *[] [0..3] { name, type, "matchup": *[ count( weaknesses[@ in ^.^.type] ) > 0 ] [0...3] {name, type, weaknesses, "message": ^.name + " vs. " + name} } `