Skip to content

JSON paths and mapping

A path says where a value sits inside a JSON document. CroStream uses paths in three places:

  • an incoming webhook's Variables, to read values from what a sender posts (a JSON body, or a form);
  • a Send a webhook step's Read the answer, to read values from a JSON answer;
  • the same step's JSON fields, where a key says where a value goes in the body you send (see Building JSON).

You rarely have to type one: in an editor you paste a sample and click the value, and the path is written for you. This page is for when you want to edit a path by hand, or understand what a rule does.

The example

Every example on this page reads this body, a merch store order:

{
  "event": "order.paid",
  "order": {
    "id": "ord_8812",
    "customer": { "name": "NightOwl_TV", "country": "CA" },
    "total": 54.50,
    "paid": true,
    "coupon": null,
    "items": [
      { "title": "Logo hoodie", "size": "L", "qty": 1, "price": 42 },
      { "title": "Sticker pack", "qty": 2, "price": 6.25 }
    ],
    "tags": ["merch", "first-order"],
    "notes": { "gift message": "Enjoy!" }
  }
}

Think of it as a tree: order holds customer, items and so on; items is a list of two things; the first has a title. A path walks down that tree.

Path forms

Path Selects Result
event A key at the top. order.paid
order.id Keys, separated by dots, one level down each. ord_8812
order.customer.name As deep as you need. NightOwl_TV
order.items[0].title A list element, counted from 0: [0] is the first. Logo hoodie
order.tags[1] The second element of a list of plain values. first-order
order.items[*].title Every element of a list (see Many values). Logo hoodie, Sticker pack
order.notes["gift message"] A key written in quotes, for keys with spaces, dots or other odd characters. Enjoy!
["order"].id A quoted key anywhere, even first. ord_8812
$ The whole body. Also written . or $.. the whole document (needs type JSON)
$.order.customer.name A leading $ or . is allowed and means nothing. NightOwl_TV
.event Same, in the style of jq. order.paid

Rules:

  • Keys are case-sensitive. Order.id isn't order.id.
  • A bare key can hold any characters except spaces, ., [, ] and quotes. A key that needs one of those goes in ["…"], written like a JSON string (["a\"b"] for a"b). A key that starts with $ and has more characters after it, like $id, is just a key.
  • Indexes run from 0 to 9999, with no leading zeros. There are no negative indexes: to get the last element use [*] with Last.
  • Spaces around the whole path are ignored.
  • A list position is always written in brackets: items.0.title looks for a key named 0 and finds nothing; write items[0].title.

A path with a mistake is shown in red with the position, for example column 14: expected ] after the index.

Forms, and a form with JSON inside

A body that is a form (name=Ann&amount=5) is read as an object with one key per field: name, amount. A field that appears twice becomes a list (tag=a&tag=b is tag[0] and tag[1], or tag[*]).

A field whose value is the text of a JSON object or list is decoded, so you can reach inside it. That is how Ko-fi sends a donation: one form field, data, holding JSON. Its paths start with the field name:

data={"from_name":"VelvetFox","amount":"5.00"}
Path Result
data.from_name VelvetFox
data.amount 5.00

Text that only looks like JSON, such as a value {not json, stays plain text. A GET without a body is read as a form made from the query of its address, so ?scene=intro gives the path scene.

Types

As (the type) decides how the value is written into the variable.

Type Takes Gives
Text (the default) Any single value: text, number or yes/no. The text; a number as written; true or false. An object or list fails: use JSON.
Number A number, or text that is a number (" 5.00 "). The number's text, exactly as written: 54.50 stays 54.50, never rounded. Text that isn't a number fails.
Yes / no A yes/no value, 0 or 1, or text such as true, false, yes, no, on, off, y, n, t, f (any case). true or false, always.
JSON Anything: an object, a list, a value. Compact JSON text. Keys of an object come out in alphabetical order.
Path Type Result Why
order.total Number 54.50 Kept as written.
order.total Text 54.50 A number as text is the same.
order.paid Yes / no true
order.customer JSON {"country":"CA","name":"NightOwl_TV"} Alphabetical keys.
order.items JSON [{"price":42,"qty":1,"size":"L","title":"Logo hoodie"},{"price":6.25,"qty":2,"title":"Sticker pack"}] The whole list.
order.customer Text (the default) Problem: expected a string, got an object (use type json).
order.id Number (the default) Problem: expected a number, got text "ord_8812".
order.total Yes / no (the default) Problem: expected a bool, got a number.

Missing values and defaults

A value that isn't there gives the row's If missing value, which is empty when you set none:

Path If missing Result Reported as a problem?
order.missing (none) (empty) Yes: not found.
order.missing n/a n/a No: a default says the value is optional.
order.coupon (null) (none) (empty) Yes: is null.
order.coupon (null) none none No.
order.items[1].size (the second item has no size) one size one size No.
order.items[5].title (no sixth item) (none) (empty) Yes: not found.

A value of the wrong kind (a Number row on text) also gives the default, and is always reported. Problems are shown in the editor and in the delivery log; they never stop the call. A rule with a path that can't be read, or a name used twice (the first row wins), is a problem too.

Many values

A path with [*] matches several values: every element of a list, or every member of an object (in alphabetical key order). Many values says how they become one variable. It appears in a row's More options only when the path has [*].

Path Many values Result
order.items[*].title Join (the default) Logo hoodie, Sticker pack
order.items[*].title Join, with + Logo hoodie + Sticker pack
order.items[*].title Count 2
order.items[*].title First Logo hoodie
order.items[*].title Last Sticker pack
order.items[*].title JSON list ["Logo hoodie","Sticker pack"]
order.items[*].price, Number Join 42, 6.25
order.tags[*] Join merch, first-order
order.items[*].size Join L (the second item has none: it is skipped)
order.customer[*] Join CA, NightOwl_TV (an object's values)
order.items[*] JSON list the list of items as JSON
order.items[*].nope Count 0

Details:

  • Join writes each match with the row's type and puts the separator between them (, unless you change it).
  • Matches that are null, or that lack the rest of the path, are skipped.
  • With no matches left, the result is the If missing value; Count gives 0, JSON list gives [].
  • If the list itself is missing, it's treated like any missing value. If the path before [*] isn't a list or an object, it is a problem.
  • Clicking a value under an each item node in the tree makes a [*] row with Join; clicking a whole object or list there makes it JSON list.
  • A path may have more than one [*]: orders[*].items[*].title matches every title of every order.

Building JSON

The JSON fields of a Send a webhook step use the same path syntax in the opposite direction: Key says where the Value goes in the body you build.

Key Type Value Builds
event Text raid {"event":"raid"}
user.name Text {user} {"user":{"name":"Ann \"AJ\" Lee"}}: nesting is made for you, and quotes are escaped.
viewers Number {viewers} {"viewers":42}: the value must be a number.
live Yes / no yes {"live":true}: true, false, 1, 0, yes, no, on, off.
note Null {"note":null}
tags[] Text twitch {"tags":["twitch"]}: [] adds a new element to the list.
tags[] Text raid …a second tags[] field adds another: ["twitch","raid"].
items[1] Text second {"items":[null,"second"]}: an element by position; skipped ones are null.
meta JSON {"v": 2} {"meta":{"v":2}}: JSON text is put in as it is (and compacted).
$ JSON {"a": [1, 2]} The whole body is that JSON.

Together, the first rows build one document, in the order of the fields: {"event":"raid","user":{"name":"Ann \"AJ\" Lee"},"viewers":42,"live":true,"note":null}.

Rules:

  • [*] isn't allowed in a key: it selects existing values. Use [0], [1] or [].
  • Two fields can't set the same key, and a key can't be both a value and a container. user = Ann followed by user.name = Ann fails with user is already a value set by field 1; it cannot also contain key "name", shown on the field.
  • A Number value that isn't a number (twelve) fails: "twelve" is not a number. A JSON value must be valid JSON.
  • With no fields, the body is {}.
  • Variables are filled into values before the type is applied, which is why a number field with {amount} needs {amount} to hold a number when the macro runs.