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.idisn'torder.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"]fora"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.titlelooks for a key named0and finds nothing; writeitems[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:
| 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[*].titlematches 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=Annfollowed byuser.name=Annfails 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.
Related¶
- Webhooks: the incoming and outgoing editors.
- Variables: the
|jsonfilter for hand-written JSON. - Events and Actions.