How to debug nested api responses with a json tree viewer
- Step 1Capture the raw response body — From your terminal, run
curl -s https://api.example.com/v1/users/42 > resp.json, or in browser DevTools open the Network tab, click the request, choose Response, and Save (or copy into a.jsonfile). The viewer needs the full JSON body — not the headers. - Step 2Drop the file onto the viewer — The dropzone accepts
.json,.ndjson,.jsonl, and.txt. For a single API response use a.jsonfile containing one JSON document. The free tier handles files up to 2 MB; most single API responses are far smaller than that. - Step 3Set Max depth if the response is extremely deep — The Max depth field (1–20, default 20) caps how many levels the tree builds children for. Leave it at 20 for normal responses. Lower it to, say, 3 if you only want to inspect the outer envelope of a giant nested payload — deeper containers still appear as
Object(n)/Array(n)nodes, just without expandable children. - Step 4Click View Tree, then read the top-level envelope — The toolbar shows total node count and the deepest level reached. Start at
$and confirm the envelope — is the body underdata, is there anerrorsarray (GraphQL), what doesmeta/paginationlook like? Expand into the branch you care about. - Step 5Search for the field or value you need — Type a key (
status,customerId) or a value (an order ID, an email) into the search box. Matching branches expand automatically; non-matching branches are hidden. Clear the box (the X) to return to the full tree. - Step 6Copy the path and wire it into your code — Hover the target field and click Copy to grab
$.data.user.orders[0].id. Use that path in your client, in a json-path-extractor query, or as the spec for the field you're mapping. Download exports the tree structure as<name>.tree.jsonif you want to share the shape.
What each node shows for an API response
How the tree renders the JSON types you meet in REST and GraphQL bodies. Container child counts come from the node's size; leaf values render as their JSON literal.
| JSON in the response | Tree node type | What you see | Why it matters for debugging |
|---|---|---|---|
{ "user": {...} } | object | Object(n) with n = key count | Confirms the object came back populated vs {} (empty) |
"orders": [ ... ] | array | Array(n) with n = element count | Distinguishes Array(0) (empty list) from a list with results |
"status": "paid" | string | Green literal "paid" | Read the exact returned string, quotes included |
"total": 4200 | number | Cyan literal 4200 | See raw numeric values — note JSON numbers lose precision past 2^53 |
"active": true | boolean | Amber true / false | Spot a flag returned as boolean vs the string "true" |
"deletedAt": null | null | Rose null | Tells null (field present, empty) apart from a missing key |
Path notation the Copy button emits
The viewer builds paths in $ / .key / [index] form — the same syntax json-path-extractor accepts, so a copied path is a ready-made extractor query.
| Where you click | Copied path | Notes |
|---|---|---|
| Root | $ | The whole document |
data object | $.data | Dot precedes object keys |
user inside data | $.data.user | Chained object keys |
First element of orders | $.data.user.orders[0] | Array index in square brackets, zero-based |
id inside that order | $.data.user.orders[0].id | Mix of object keys and array indices |
| A GraphQL error message | $.errors[0].message | GraphQL puts failures in a top-level errors array |
Cookbook
Real nested-response debugging walkthroughs. Tokens and customer data are anonymised; structures mirror common REST and GraphQL shapes.
Find why `orders` is empty in a REST response
ExampleThe client renders no orders. The viewer shows whether the API returned an empty array, a null, or a populated array your code mis-read. The child count on the array node answers it instantly.
Response (resp.json):
{
"data": {
"user": { "id": 42, "orders": [] }
},
"meta": { "requestId": "req_9f3" }
}
In the tree:
$ ............... Object(2)
$.data ......... Object(1)
$.data.user .... Object(2)
$.data.user.orders ... Array(0) ← empty list, not a parse bug
Conclusion: the API returned zero orders for user 42 — the
client is fine. Check the query filters server-side.Copy the exact path for a deeply nested field
ExampleYou need the dotted path to a line-item SKU four levels into the response to map it in your client. Hover and copy instead of counting brackets by hand.
Response branch: $.data.user.orders[0].lineItems[2].sku Click Copy on the sku node → $.data.user.orders[0].lineItems[2].sku Paste straight into json-path-extractor to pull that value out of every record, or into your client's response mapper.
Locate a GraphQL error inside a 200 response
ExampleGraphQL returns HTTP 200 even on field-level failures — the failure lives in a top-level errors array alongside partial data. Search surfaces it without hand-scrolling.
Response:
{
"data": { "user": null },
"errors": [
{ "message": "Not authorized", "path": ["user"],
"extensions": { "code": "FORBIDDEN" } }
]
}
Search: authorized
→ auto-expands $.errors[0] and highlights the branch.
Copy $.errors[0].extensions.code to assert on FORBIDDEN
in your integration test.Inspect only the outer envelope of a huge payload
ExampleA bulk endpoint returns thousands of nested records. You only need to confirm the pagination envelope, not every record. Lower Max depth to keep the tree light.
Set Max depth = 2 before View Tree. $ .............. Object(3) $.data ......... Array(2500) ← shown, children not built $.meta ......... Object(4) $.meta.cursor .. "eyJpZCI6.." $.meta.hasMore . true You see the envelope and the cursor without rendering 2,500 expanded records.
Confirm a field is null vs absent
ExampleYour code throws on user.deletedAt. The tree distinguishes a field that is present-but-null from one that simply isn't in the response.
Response: $.data.user ........ Object(3) $.data.user.id ..... 42 $.data.user.name ... "Ada" $.data.user.deletedAt ... null deletedAt IS present and IS null — your optional-chaining should handle null, not just undefined. If the key were absent entirely, no deletedAt node would appear at all.
Errors and edge cases
Real errors and silent failures sourced from each platform's own documentation. Match the wording to the row, fix what the row says to fix.
Response includes the HTTP headers or a wrapping label
Invalid JSONIf you saved HTTP/1.1 200 OK lines or a Response: label above the body, the file is no longer valid JSON and JSON.parse throws. Save the response body only — in DevTools use the Response tab's raw body, and with curl don't include -i/-v. The viewer trims surrounding whitespace but does not strip non-JSON prefixes.
Trailing comma in a hand-edited response fixture
Parse errorJSON forbids trailing commas ({ "a": 1, }). A fixture you trimmed by hand will fail strict JSON.parse. Remove the trailing comma, or run the file through json-format-fixer first, then view the result.
An NDJSON / log-stream file with one object per line
Single document onlyThe dropzone accepts .ndjson and .jsonl, but the viewer parses the whole file as ONE JSON document. A stream of newline-delimited objects ({...}\n{...}) is not a single valid document and will fail to parse. Wrap the lines in a top-level array ([ {...}, {...} ]) before viewing, or convert the stream first.
Large integer IDs lose precision
By designJSON numbers are IEEE-754 doubles, so an ID beyond 2^53 (e.g. a Twitter-style 64-bit snowflake) is rounded by JSON.parse — 9223372036854775807 becomes 9223372036854776000. APIs that return big IDs as strings avoid this; if yours returns them as numbers, treat the displayed value as approximate and read the ID from a string field where available.
Response deeper than Max depth
PreservedIf the response nests deeper than the Max depth setting, containers at the cap still appear as Object(n) / Array(n) nodes with their child count — they just can't be expanded. Raise Max depth (up to 20) and re-run to drill further. The data isn't dropped, only the child rendering is capped.
Empty response body
Parse errorA 204 No Content or otherwise empty body is not valid JSON — there's nothing to parse, so the viewer reports an error. That's expected: an empty body has no structure to render.
Duplicate keys in the response object
Last winsIf a malformed response contains the same key twice in one object, JSON.parse keeps the last occurrence — the tree shows one node for that key. This is standard JavaScript JSON behaviour, not a viewer choice. If you suspect duplicates, inspect the raw text separately.
File over the 2 MB free limit
Upgrade requiredFree uploads cap at 2 MB. A single REST/GraphQL response over that is rare but happens with embedded base64 or huge result sets. Pro raises the limit to 100 MB. For very large bulk exports, see the large-JSON guide for the structure-only approach.
Unicode escapes and emoji in string values
Supported\u00e9 and emoji decode correctly because the browser parses native JSON — "caf\u00e9" shows as café. Search matches the decoded form, so searching café finds it. No encoding option is needed.
You expected the Download to give you the original response
Tree structure onlyDownload exports the tree representation (keys, types, sizes, values) as <name>.tree.json, not a copy of your original file. To prettify or minify the original response use json-prettifier or json-minifier instead.
Frequently asked questions
Does the response get uploaded anywhere?
No. The viewer parses the file with the browser's native JSON.parse and builds the tree in memory on your machine. Bearer tokens, cookies, and customer data in the response body never leave your browser.
Can I paste a response, or do I need a file?
The tool takes a dropped/selected file (.json, .ndjson, .jsonl, .txt). Save the response body to a file first — curl -s URL > resp.json or DevTools → Network → Response → Save.
What path syntax does the Copy button use?
$ for the root, .key for object keys, and [n] for array indices — for example $.data.user.orders[0].id. It's the same notation json-path-extractor accepts, so a copied path is a ready-to-run query.
How does search work on an API response?
It matches both keys and values, case-insensitively, and auto-expands every branch that contains a match while hiding the rest. Search a key like status or a value like an order ID; clear the box to restore the full tree.
Why does my GraphQL response show data AND errors?
GraphQL returns HTTP 200 even when a field fails, putting failures in a top-level errors array next to partial data. The tree shows both branches — expand $.errors to read message, path, and extensions.code.
Will it validate my response against a schema?
No. It only parses and visualises. For structural validation use json-validator; to generate a JSON Schema from a sample response use json-schema-generator.
Can it handle a response with thousands of records?
Yes, within the file-size limit (2 MB free, 100 MB Pro). For very large payloads, lower the Max depth to inspect the envelope without expanding every record, or use the large-JSON workflow.
What does the node count and max depth in the toolbar mean?
Node count is the total number of nodes (every key and every leaf) in the parsed tree; max depth is the deepest level reached during the build. Both are quick sanity checks on how big and how nested the response really is.
Why did a big ID change when I viewed it?
JSON numbers are doubles, so integers above 2^53 are rounded on parse. If your API returns 64-bit IDs as numbers they may display imprecisely — read them from a string field if the API provides one.
My response file won't parse. What's wrong?
Usually saved HTTP headers, a trailing comma, an empty body, or an NDJSON stream of multiple objects. Save the body only, remove trailing commas (or run json-format-fixer), and wrap newline-delimited objects in an array before viewing.
Can I extract a value into a flat row after I find its path?
Yes. Copy the path here, then use json-path-extractor to pull that value, or json-flattener to flatten the whole response into dotted-key rows.
Is this tool free?
It runs in your browser and is usable on the free tier up to a 2 MB file. Pro removes the file-size limit (100 MB) and adds bulk and saved-history features.
Privacy first
Conversion runs locally in your browser. No file is uploaded — only metadata counters are saved for signed-in dashboard stats.