Day 9 — The shape of real data
Most flows do not fail on logic. They fail because the data was not the shape you assumed.
Object or array?
Everything you get from a connector is one of two things, and telling them apart is most of the battle.
| Object | Array | |
|---|---|---|
| Looks like | { "Name": "Anita" } | [ {...}, {...} ] |
| Is | One thing with named fields | A list of things |
| Read with | ?['Name'] | item() in a loop, or first() |
| Comes from | One form response, one item | Get items, Get rows, an attachments list |
@outputs('One')?['Name']
@length(outputs('Many'))
@first(outputs('Many'))?['Name']
@last(outputs('Many'))?['Name']
@outputs('Many')?[0]?['Name']
first() instead and keep the flow flat: first(outputs('Get items')?['value'])?['Title'].Nested fields
Real connector output is objects inside objects. You chain the brackets.
{
"trigger": {
"name": "When an item is created",
"type": "automated",
"body": {
"ID": 42,
"Title": "Enrolment request",
"Student": { "Name": "Priya Nair", "Email": "priya@example.com", "Phone": null },
"Course": { "Name": "Tableau", "Fee": 14000, "Trainer": { "Name": "Meera", "Id": 7 } },
"Tags": ["urgent", "weekend"]
}
},
"actions": [
{ "name": "Student name", "type": "Compose", "input": "@triggerBody()?['Student']?['Name']" },
{ "name": "Trainer name", "type": "Compose",
"input": "@triggerBody()?['Course']?['Trainer']?['Name']" },
{ "name": "Missing phone", "type": "Compose",
"input": "@coalesce(triggerBody()?['Student']?['Phone'], 'Not given')" },
{ "name": "Deep and missing", "type": "Compose",
"input": "@triggerBody()?['Course']?['Syllabus']?['Url']" },
{ "name": "First tag", "type": "Compose", "input": "@first(triggerBody()?['Tags'])" },
{ "name": "Summary", "type": "Compose",
"input": "@{triggerBody()?['Student']?['Name']} wants @{triggerBody()?['Course']?['Name']} with @{triggerBody()?['Course']?['Trainer']?['Name']}" }
]
}
When the data arrives as text
An HTTP response, a file, or a column somebody stuffed JSON into gives you a string that happens to look like JSON. json() turns it into something you can read fields from.
{
"trigger": { "type": "manual",
"body": { "Payload": "{\"Name\":\"Rahul\",\"Scores\":[8,6,9]}" } },
"actions": [
{ "name": "As text", "type": "Compose", "input": "@triggerBody()?['Payload']" },
{ "name": "Length of the text", "type": "Compose",
"input": "@length(triggerBody()?['Payload'])" },
{ "name": "Parsed", "type": "Compose", "input": "@json(triggerBody()?['Payload'])" },
{ "name": "Name from it", "type": "Compose",
"input": "@json(triggerBody()?['Payload'])?['Name']" },
{ "name": "Best score", "type": "Compose",
"input": "@max(json(triggerBody()?['Payload'])?['Scores'])" }
]
}
"Length of the text" gives the number of characters, because before json() it really is just a string. That is the tell: if length() returns something far too big, you are looking at text, not data.
Cleaning what people typed
Any field a human filled in will arrive with stray spaces, mixed capitals and inconsistent phone formats.
{
"trigger": { "type": "manual",
"body": {
"Name": " anita SHARMA ",
"Email": " Anita.Sharma@Example.COM ",
"Phone": "+91 98765 43210",
"Region": "north"
} },
"actions": [
{ "name": "Clean name", "type": "Compose",
"input": "@trim(triggerBody()?['Name'])" },
{ "name": "Clean email", "type": "Compose",
"input": "@toLower(trim(triggerBody()?['Email']))" },
{ "name": "Digits only", "type": "Compose",
"input": "@replace(replace(replace(triggerBody()?['Phone'], '+91', ''), ' ', ''), '-', '')" },
{ "name": "Region matched", "type": "Compose",
"input": "@equals(toLower(trim(triggerBody()?['Region'])), 'north')" },
{ "name": "Proper case", "type": "Compose",
"input": "@concat(toUpper(substring(trim(triggerBody()?['Name']), 0, 1)), toLower(substring(trim(triggerBody()?['Name']), 1)))" }
]
}
Multi-select answers
A Forms checkbox question does not give you an array. It gives you one string with semicolons in it, and everybody trips over this once.
{
"trigger": { "type": "manual",
"body": { "Name": "Vikram", "Interests": "Excel;Power BI;SQL" } },
"actions": [
{ "name": "Raw", "type": "Compose", "input": "@triggerBody()?['Interests']" },
{ "name": "Length of the raw text", "type": "Compose",
"input": "@length(triggerBody()?['Interests'])" },
{ "name": "As an array", "type": "Compose",
"input": "@split(triggerBody()?['Interests'], ';')" },
{ "name": "How many chosen", "type": "Compose",
"input": "@length(split(triggerBody()?['Interests'], ';'))" },
{ "name": "Wants Power BI", "type": "Compose",
"input": "@contains(split(triggerBody()?['Interests'], ';'), 'Power BI')" },
{ "name": "Each interest", "type": "ApplyToEach",
"from": "@split(triggerBody()?['Interests'], ';')",
"actions": [
{ "name": "Note", "type": "Compose", "input": "Interested in @{item()}" }
] }
]
}
The raw text is 18 characters. Split on the semicolon and it becomes three items you can loop, count and test.
Try these yourself
- Reach a name three levels deep in a nested object, safely.
- Parse a JSON string out of a field and read two values from it.
- Clean a name and an email that arrive with spaces and mixed capitals.
- Turn a semicolon-separated answer into an array and count the choices.
- Explain how to tell whether a connector gave you an object or an array.
