Mastering Parse JSON in Power Automate: Schemas, Null Handling, and Arrays
In this series: Power Automate gotchas
Parse JSON looks like a convenience action, but in serious flows it is a contract boundary. It turns an unknown payload into typed dynamic content and makes later actions easier to read. It also fails loudly when the schema lies, which is exactly why you should learn to shape the schema instead of deleting the action.
Generate the schema, then edit it
Start from a real sample. The Generate from sample button is useful because it captures nesting, arrays, and primitive types quickly. But it assumes the sample is the whole truth. Real integrations send nulls, omit optional fields, and occasionally return an array with zero items.
Treat the generated schema as a draft. Review every required property, every nullable field, and every array item. If the schema came from a perfect happy-path response, it is almost certainly too strict for production.
Collect more than one sample. Ask for a normal payload, a payload with missing optional values, and a payload with an empty array. If the source is an HTTP action, save representative bodies from run history before finalizing the schema. One perfect example makes the designer happy; several realistic examples make the flow durable.
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"displayName": {
"type": "string"
},
"creditLimit": {
"type": "number"
}
},
"required": [
"id",
"displayName"
]
}
Nullable fields need type unions
Fix nulls at the schema level. The classic error says the action expected a string but got null. That is not a Power Automate mystery; it means the schema allowed only string while the payload sent null. If null is valid, use a type union.
{
"type": "object",
"properties": {
"email": {
"type": [
"string",
"null"
]
},
"phone": {
"type": [
"string",
"null"
]
},
"lastLoginOn": {
"type": [
"string",
"null"
]
}
}
}
Do not make everything nullable by habit. If a field is required for the process to work, keep it strict and fail early. Nullable schemas are for values that the upstream system legitimately may not know.
Required and optional are separate decisions
Type says what a value may be. Required says whether the property must exist. A field can be optional but, when present, must be a string. A field can be required and still allow null if the upstream contract always includes the property but sometimes has no value.
| Symptom | Likely cause | Fix |
|---|---|---|
Invalid type. Expected String but got Null | Property allows only string | Add null to the type array |
Required property is missing | Payload omitted a required field | Remove it from required or fix upstream |
| Dynamic content missing for array child | Schema item shape is incomplete | Define the array items object |
| Later action gets blank value | Property exists but is null | Use coalesce when reading it |
| Parse succeeds but loop fails | Array is sometimes empty | Guard with empty before indexing |
Arrays need an item contract
Define the shape inside items. For arrays, Power Automate needs to know what each element looks like. A vague array schema may parse, but it gives weak dynamic content and pushes risk into later expressions.
{
"type": "object",
"properties": {
"orders": {
"type": "array",
"items": {
"type": "object",
"properties": {
"orderId": {
"type": "string"
},
"amount": {
"type": "number"
},
"currency": {
"type": [
"string",
"null"
]
}
},
"required": [
"orderId",
"amount"
]
}
}
}
}
Loop over the parsed array, not the raw body. Once Parse JSON succeeds, use its typed output as the source for Apply to each. That keeps later references stable and makes schema changes visible in one place.
Decide what an empty array means. Sometimes it is a valid no-work response. Sometimes it means the upstream query was wrong. Add an explicit branch before the loop so the run history says skipped, no records, or failed validation. That is much better than a flow that quietly completes without doing the business work.
Nested access still needs safe expressions
Dynamic content is not a null shield. If a nested object can be missing, direct dynamic content may still fail in a later action. Use safe navigation in expressions when reading optional paths, and use coalesce for defaults.
body('Parse_JSON')?['customer']?['email']
coalesce(body('Parse_JSON')?['customer']?['email'], 'unknown@example.com')
first(body('Parse_JSON')?['orders'])?['orderId']
Guard array indexing. first is clean only when the array has at least one element. If an empty array is valid, check with empty before reading the first item or route to a no-data branch.
Sometimes json and body are enough
Use Parse JSON when the contract matters. If an HTTP action returns a payload used across many later steps, Parse JSON pays for itself with readable fields and early validation. If you only need one property from a simple object, body with safe navigation can be enough.
Use json for string payloads. Some triggers and connectors hand you JSON as text. The json expression converts it into an object so you can read properties without adding a Parse JSON action. That is useful for small transformations, but less useful when a shared schema would document the contract.
Log the raw payload when the contract is new. During early rollout, capture the response body in a secure logging destination or controlled audit table when parsing fails. Do not log secrets or personal data casually. The point is to see the real shape that broke the schema, fix the contract, and then reduce logging once the integration stabilizes.
Parse JSON is not just a designer helper. It is where you decide what your flow believes about incoming data. Generate the schema, edit it for nulls and optional fields, define array items properly, and your downstream actions will stop carrying the cost of a vague contract.
Keep reading
Filter Rows: The Lookup GUID Is _parentcustomerid_value
Filter rows is Dataverse OData. Lookups filter on _logicalname_value, not the name. Filter array runs after the download, and @equals does not belong in that box.
Get a Row by ID: Missing Fails the Run, Empty List Rows Does Not
Get a row by ID fails the run when the GUID is missing. List rows with zero matches succeeds. Look up the business key and branch on empty.
When an HTTP Request Is Received: The Callback URL Is a Secret
The HTTP trigger URL carries a SAS signature. Anyone with it can start the flow. Restrict who can call, always Respond, and never treat that URL as public.
Power Automate Do Until: The Loop That Succeeds After Giving Up
A Do until that hits count or timeout still succeeds. Put Get inside the loop, add a Delay, and test the condition after the loop exits.
Power Automate Trigger Conditions: The Run That Never Appears
A trigger condition that is false does not start a run and does not leave run history. How the expression differs from a Condition action, and how to see the skipped checks.
Power Platform September 2026 Update: The App-Builder Skill, Agent Plugins, and the Refreshed Model-Driven UI
What matters in Microsoft's September 2026 Power Platform feature update: the model app-builder skill goes GA, the canvas authoring agent plugin goes GA, generative pages embed in forms and reach connectors, model-driven header refresh is GA with display density in preview, and Power Automate gets server-side search.
Newsletter
New posts, straight to your inbox
One email when a new post goes up. Double opt-in, unsubscribe anytime. Not a daily digest.
Comments
- No comments yet. Be the first.