Coming from jq#

If you reach for jq to pick apart JSON, a lot of Bark will feel familiar. jq taught many of us to treat data as something you pipe through a chain of small transformations — and that is exactly what Bark’s > link operator does.

This page isn’t about replacing jq. jq is excellent at what it does, and a one-line filter in a shell is hard to beat. The point is that jq is a handy lens: if its pipe-and-filter model already clicks for you, Bark’s will too. The difference is that the same > chain can also make the request and write the result — so the mental model you use for a quick filter scales up to a whole program, without gluing jq together with curl and a shell script.

The shared idea: pipe data forward#

Both read left to right, each step receiving the previous step’s value:

# jq
echo "$json" | jq '.users | map(.name)'
// Bark — the same shape, with the link operator.
// The transform is just a function, named once and reused.
fn name(user map) { user > get("name") > return() }(string)

users > array.map(name)

Where jq writes the transform inline (.name), Bark lets you pull it into a named function and pipe it in by name. That keeps each step in a chain short and readable — the examples below lean on that.

Familiar operations#

A quick map from common jq filters to their Bark equivalents. Bark’s JSON helpers live in the map and json modules.

Pull a nested value, with a fallback#

jq '.user.name // "unknown"'
data > map.get_or_path("unknown", "user", "name")

map.get_or_path walks maps by string key and arrays by index, returning the default the moment any step is missing — the same intent as jq’s //.

Collect a field from anywhere in the tree#

jq '[.. | .name?]'
data > map.descend("name")

Take just the values of an object#

jq '[.[]]'
data > map.extract()

Map and filter over an array#

jq 'map(.price)'
jq 'map(select(.active))'
fn price(item map)   { item > get("price") > return() }(int)
fn active?(item map) { item > get("active") > return() }(bool)

items > array.map(price)
items > array.filter(active?)

Merge two objects (deep)#

jq -s '.[0] * .[1]'
{"theme": "dark", "lang": "en"} > defaults
{"lang": "fr"} > prefs
defaults > map.deep_merge(prefs) > println()   // {theme: dark, lang: fr}

Drop a field at a path#

jq 'del(.user.token)'
data > map.del_path("user", "token")

Rename every key#

jq 'with_entries(.key |= ascii_upcase)'
data > map.map_keys(str.upper)

A built-in like str.upper can be passed straight in when it needs no extra arguments. When the transform takes arguments — get("price"), eq?("id") — wrap it in a named function, as the array examples above do.

Keep only some keys#

jq '{id, name}'
fn keep?(key string) {
  ["id", "name"] > includes?(key) > return()
}(bool)

data > map.filter_keys(keep?)

Read JSON Lines (NDJSON)#

jq -c . events.ndjson
text > json.parse_lines() > (err error, events array) {
  events > len() > println()
}()

Beyond filtering: fetch and save#

A jq filter transforms whatever is handed to it on stdin. A Bark program can be the whole pipeline: make the API call, shape the response with the same jq-like steps, and persist the result — all in one place.

// Fetch users from an API, keep the active ones, project name + email,
// and write a CSV. No curl, no shell glue.

// The shaping steps are named functions — the pipeline below reads as
// "filter active, project a summary" rather than a wall of inline closures.
fn active?(user map) {
  user > get("active") > return()
}(bool)

fn summary(user map) {
  user > get("name") > name
  user > get("email") > email
  {} > set("name", name) > set("email", email) > return()
}(map)

"https://api.example.com/users" > http.get() > (err error, resp map) {
  err > present?() > return?()                       // bail on a network error

  resp > get("body") > json.parse() > (perr error, users array) {
    perr > present?() > return?()                     // bail on bad JSON

    users > array.filter(active?) > array.map(summary) > rows

    rows > csv.encode() > csv
    "active_users.csv" > file.write(csv) > _

    rows > len() > n
    println("wrote {0} rows to active_users.csv", n)
  }()
}()

The same data flows left to right the entire way — from the HTTP response, through the jq-style shaping, out to a file. That’s the part jq leaves to the surrounding script, and the part Bark folds into the program itself.

See also#