How to extract json fields across ndjson records
- Step 1Convert NDJSON to a JSON array — Wrap the records in
[ ... ]and add a comma between each object. From a shell you can do this with jq:jq -s '.' export.ndjson > export.json, or add the brackets and commas by hand for a small batch. - Step 2Drop or paste the array — Provide the wrapped batch as one document. The top-level value is now an array, so paths begin at
$[*]or$[?(...)]. - Step 3Write an extraction expression — Use
$[*].userIdto pull one field from every record, or$[*].error.codefor a nested field. Records missing the field simply contribute no node, so the result can be shorter than the record count. - Step 4Or write a filter to subset records — Use
$[?(@.status == 'failed')]to keep whole records that match. Filters take one operator — there is no&&/||, so apply one condition per run. - Step 5Window large batches with a slice — Use
$[0:100]to preview the first 100, or$[::-1]to reverse (handy for time-ordered exports). Combine, e.g.$[::-1][0:10], for the most recent records. - Step 6Copy or download the result — Copy to the clipboard or Download a
.extracted.json. To turn the array into a spreadsheet, pass it to a CSV converter; to count value frequencies, do that step in jq or a spreadsheet.
NDJSON inputs and how to make them queryable
The tool parses one JSON document. This is how each shape behaves and what to do.
| Input | Parses as-is? | Action |
|---|---|---|
Raw NDJSON / .jsonl (one object per line) | No — parse error | Wrap in [ ... ] with commas, e.g. via jq -s '.' |
JSON array of records [ {...}, {...} ] | Yes | Query with $[*].field or $[?(@.k == v)] |
| A single JSON object | Yes | Object paths only ($.field); array paths won't match it |
| NDJSON with a blank trailing line | No (when unwrapped) | Trailing newline alone is fine once wrapped; just ensure no stray comma |
| Array with a trailing comma | No — invalid JSON | Remove the trailing comma before the closing ] |
Batch extraction and filtering patterns
Patterns assume the export is wrapped as a top-level array. One predicate per filter.
| Goal | JSONPath | Returns |
|---|---|---|
| One field from every record | $[*].userId | Flat array of all user ids |
| Keep failed records | $[?(@.status == 'failed')] | Full record objects where status is failed |
| Nested field across records | $[*].address.city | Each record's city (skips records lacking it) |
| First 100 records | $[0:100] | A window for sampling |
| Reverse a time-ordered export | $[::-1] | Records newest-first |
| Records that have an error block | $[?(@.error)] | Records where the error key is present |
Cookbook
Real export fragments wrapped as JSON arrays, with the path that extracts or filters. [matches: N] is the count the tool reports.
Wrap an NDJSON export, then pull every user id
ExampleThe raw export is NDJSON and won't parse. Wrapping in [ ... ] makes it one array; $[*].userId projects the field from every record into a flat list.
Raw (won't parse):
{ "userId": 1, "status": "ok" }
{ "userId": 2, "status": "failed" }
Wrapped:
[ { "userId": 1, "status": "ok" },
{ "userId": 2, "status": "failed" } ]
Path: $[*].userId
Output [matches: 2]: [ 1, 2 ]Keep only the failed records
ExampleA single-operator filter returns whole records, giving you a clean failed-only subset you can download for replay or investigation.
Path: $[?(@.status == 'failed')]
Input (wrapped):
[ { "userId": 1, "status": "ok" },
{ "userId": 2, "status": "failed" } ]
Output [matches: 1]:
[ { "userId": 2, "status": "failed" } ]Project a nested field across records
ExampleRecords often carry nested objects. $[*].address.city pulls the city from each — records without an address simply contribute nothing.
Path: $[*].address.city
Input (wrapped):
[ { "id": 1, "address": { "city": "Oslo" } },
{ "id": 2, "address": { "city": "Kyiv" } } ]
Output [matches: 2]:
[ "Oslo", "Kyiv" ]Window the most recent 10 records
ExampleFor a time-ordered export, reverse with a slice and take the head. Slices compose, so [::-1][0:10] gives the latest ten.
Path: $[::-1][0:10]
Input (wrapped, 5000 records, oldest first)
Output [matches: 10]:
[ {newest}, ... , {10th newest} ]Filter records that have an error block
ExampleAn existence filter keeps records where the error key is present, so you can pull the error-bearing subset without knowing the exact error values.
Path: $[?(@.error)]
Input (wrapped):
[ { "id": 1 },
{ "id": 2, "error": { "code": "E_TIMEOUT" } } ]
Output [matches: 1]:
[ { "id": 2, "error": { "code": "E_TIMEOUT" } } ]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.
Dropping a `.ndjson` / `.jsonl` file directly
Parse errorThe dropzone accepts the extension, but the extractor still runs JSON.parse on the whole file. A genuine multi-record NDJSON file throws Unexpected non-whitespace character after JSON. Wrap the records in a JSON array first (e.g. jq -s '.').
Compound filter across records
0 matches$[?(@.status == 'failed' && @.retries > 3)] returns nothing — &&/|| aren't supported. Filter on the stronger condition, then narrow the rest after download.
Field absent in some records
By design$[*].address.city only yields a node where the path resolves, so the result is shorter than the record count when some records lack the field. Filter with $[?(@.address)] first if you need alignment.
Negative index on the array
0 matches$[-1] is unsupported for index access. Use a reverse slice: $[::-1][0] for the last record, or $[-1:] which works because slices accept negatives.
Existence filter keeps null/empty fields
Matched$[?(@.email)] matches a record where email: null or email: "", because it tests presence. Add != null or != '' to exclude them.
Numeric filter on string-typed numbers
By designExports sometimes store numbers as strings ("42"). Comparison operators coerce, so $[?(@.count > 10)] still works, but == 42 won't match "42". Mind the types.
Export exceeds the free size limit
BlockedFree tier caps input at 2 MB. Wrapped large exports can exceed that and are blocked with an upgrade prompt; Pro raises the limit to 100 MB. Otherwise split the file and process chunks.
Wanting counts or aggregations of values
Not supportedThe tool extracts and filters; it does not group, count, or aggregate. Extract the values, then tally them with jq's group_by or a spreadsheet pivot.
Browser slows on a very large wrapped array
ExpectedEverything runs in-memory in the tab. Multi-megabyte arrays with heavy recursive ($..) queries can be slow. Slice to a window ($[0:1000]) or process the file in chunks.
Frequently asked questions
Why does my NDJSON file fail to parse?
NDJSON is many top-level JSON objects, not one document, and JSON.parse rejects that. Wrap the records in a JSON array ([ {...}, {...} ]) — jq -s '.' file.ndjson does it in one command.
The dropzone accepts `.jsonl` — why doesn't a `.jsonl` file work?
The accepted-types list includes the extension, but the extractor parses the file as a single JSON document. A true multi-record .jsonl still needs wrapping in an array first.
How do I keep only records matching a condition?
Use a single-predicate filter on the wrapped array, e.g. $[?(@.status == 'failed')], which returns the full matching records. Only one operator per filter is supported.
Can I filter on two conditions at once?
No — &&/|| aren't supported and return 0 matches. Filter on one condition, download, then apply the second condition with another pass or in jq.
Can it count how many times each value occurs?
No aggregation or counting is built in. Extract the values (e.g. $[*].status), then count them with jq's group_by or a spreadsheet. The tool only extracts and filters.
How do I get the last (or newest) record?
Index [-1] isn't supported. Use a reverse slice: $[::-1][0] for the last record, or $[-1:] to get it as a one-element array; slices accept negative indices.
How big a file can I process?
Up to 2 MB on the free tier and 100 MB on Pro, measured on the wrapped JSON. For larger exports, split into chunks and process each, or slice to the window you need.
Is the export data uploaded?
No. Parsing and JSONPath evaluation run entirely in your browser; export records and any PII are never sent to JAD Apps servers.
My extraction returned fewer values than records — is that a bug?
No. A field path only produces a node where it resolves, so records missing the field are skipped. Pre-filter with $[?(@.field)] to count only records that have it.
Can I use this instead of jq for ad-hoc extraction?
For straightforward field extraction and single-condition filtering across a wrapped array, yes. For multi-condition logic, aggregation, or reshaping, jq is the better fit.
How do I turn the filtered records into a CSV?
Extract the records to an array, then convert with json-to-csv. If records are deeply nested, flatten them first with json-flattener.
Can it generate sample NDJSON test data?
Not this tool. Use json-mock-generator to produce mock records (it emits a fixed record shape), then wrap them as an array if you want to query them here.
Privacy first
Conversion runs locally in your browser. No file is uploaded — only metadata counters are saved for signed-in dashboard stats.