JSON Inspect

Guide & code review

Start locally

Open index.html directly in a modern desktop browser, then choose or drop a JSON file. Keep all files in this folder together. There is no server, build step, installation, CDN dependency, telemetry, or data upload.

The table is the default view. Nested JSON shows the same filtered records with expandable objects and arrays. Click a record number in the table to inspect the original record. JSON, JSONL and NDJSON files are supported; JSON Lines is selected by its filename extension.

For objects containing arrays, use Records to select a collection. The app prefers a sessions array, otherwise the first discovered array. “Whole document” treats an object as one record. Collection discovery follows object properties up to eight levels and stops at arrays; it does not automatically combine separate arrays.

The app searches and sorts the entire selected collection, while displaying 50, 100 or 250 rows per page, or ALL matching records on a single page. ALL disables page navigation and follows the current filter. It uses virtual scrolling: the full result set is available through the scrollbar, while only a small window of rows is transferred to the interface and rendered. The footer shows the currently visible range. The default remains 100 rows. Original nested JSON is fetched only when you inspect or expand a record. Nested ALL also uses virtual scrolling, accounting for expanded record heights and restoring expansion state when you scroll back. This bounds rendering and transfer work for large session dumps.

Named column layouts

Arrange headers by dragging and choose visible fields with Columns, then open Layouts…, enter a name and save. Layouts remember the order of all columns and which ones are visible. Select a saved layout beside Columns to restore it. Filters and sorting are unaffected.

Changes show as (modified) until you explicitly update the saved layout. In Layouts, select New layout to save another name, or select an existing layout to update, rename or delete it. Updating captures the current columns. Deleting leaves the current view unchanged. Custom layout stops automatic reuse without changing the current columns.

The last selected layout is reused on newly opened files. Missing fields are skipped and newly discovered fields are appended but hidden; use Columns to show them. The stored definition stays intact unless you explicitly update it, so fields return when you open a matching file again.

Layouts are stored in this browser, with a session-only warning if storage is unavailable. Local-file storage depends on the browser and app location: export a backup before moving the folder or clearing browser data. Export JSON or layouts.js to back up or share layouts; these files contain names and field selections, not record data. Import merges by name, replacing matching definitions, and does not automatically apply them.

The browser cannot silently overwrite a local JavaScript file. To distribute shared defaults, export layouts.js and manually replace the copy beside index.html. Existing browser-saved layouts take precedence; import the file to update them. Imported files must be a JSON array or a JSON array assigned to window.COLUMN_LAYOUTS; additional executable JavaScript is rejected.

Search examples

In Expression mode, start typing a field name to see matching fields from the loaded collection, including hidden columns. Field suggestions have a ▦ icon. After a field, operator suggestions (ƒ) show descriptions and examples: CONTAINS, =, !=, IN, ~, !~, >, >=, <, and <=. AND/OR suggestions help join complete conditions.

Click a suggestion, or use ↑/↓ and Enter; Tab inserts the first suggestion if none is highlighted. Escape dismisses the list, and Ctrl + Space reopens it. Enter without a highlighted suggestion applies the query. Suggestions only edit the draft, never run it. Plain text, Regex and jq subset modes retain their existing input behavior.

Ordinary field names need no quotes: vdom CONTAINS "T" and flows.in.source_ip IN 10.0.0.0/8. Generated filters and suggestions use the same format. Backticks are only needed for special or reserved names, such as `["a.b"]` for a literal dotted key. Existing backtick-wrapped fields remain supported. String values use quotation marks.

Select Plain text for ordinary case-insensitive substring matching. Punctuation is literal: 10.1. does not behave like a regex. Searches inspect all fields, including hidden columns. Press Enter or Apply search to run the search.

Select Expression for the examples below. AND binds more tightly than OR; NOT binds most tightly. Use parentheses to make your intent explicit. Boolean operators are case-insensitive. Quoted strings support escaped quotes and Unicode escapes.

PurposeExpression
Field equalityflows.in.destination_port = 443
Network + alternativesflows.in.source_ip IN 10.0.0.0/8 AND (flows.in.destination_port = 53 OR application ~ /dns/i)
Any field in a subnet10.1.0.0/16 OR 2001:db8::/32
Literal phrase across values"junos-syslog" AND NOT "invalid"
Substring in a fieldpolicy.name CONTAINS "TIPSNET" or policy.name:TIPSNET
Regex in a fieldapplication ~ /^junos-(dns|https)/i
Negative regexapplication !~ /dns/i
Numeric comparisonduration >= 3600 AND flows.in.bytes > 1000000
Compare fieldsflows.in.source_ip != .flows.out.destination_ip
Present or nullexists(source_nat_pool) AND source_nat_pool != null
Scalar array records`$` = 42

Equality is case-sensitive and type-strict: 53 differs from "53". Numeric ordering operators accept numeric strings but ignore absent, null, empty and nonnumeric values. null, false, empty strings and missing fields remain distinct. The SRX value "Null" is a string, not JSON null. exists(field) checks presence, including null values.

A leading dot on a value means a field reference. Quote strings beginning with a dot, strings containing spaces, parentheses, colons or operators, and literal words such as “AND”. Wrap unusual field names in backticks. Literal dotted keys use bracket notation: for a key literally named a.b, use `["a.b"]` = 7; the nested field stays a.b. The Columns panel shows canonical field names.

CIDR matching supports IPv4 and IPv6, including IPv4-mapped IPv6 addresses. Families remain separate; addresses must be standalone strings, without ports or zone identifiers. Host bits in a CIDR are masked. Arrays remain JSON values in table cells; CIDR tests do not recursively descend into array elements. Select a relevant record collection or use nested view for those arrays.

Regex mode takes a bare JavaScript pattern, case-insensitive. In expressions use /pattern/flags with optional i, m, s, u. Expensive regular expressions run in the worker. Unload / cancel stops the worker immediately; a 30-second query limit also stops it. Reopen the file after cancellation.

Saved filters: add or replace

Open Saved filters to see each definition and its expression preview. + Add combines it with the current search using AND, preserving OR groups. Replace all replaces the whole search, including column-generated conditions. Both actions include any search-bar edits and apply immediately, updating the bar, column inputs, chips and results. Layouts and sorting are unchanged. An invalid candidate leaves the existing search intact. Escape or a click outside closes the menu.

Undo filter changes

Undo filter restores the previous search after Add, Replace all, Clear all, removing a chip, or applying a manually edited or column-generated filter. It also undoes mode changes and Try an expression. Up to 50 changes are kept for this session; opening another file or unloading clears this history. Sorting and column layouts are not part of filter history.

Undo restores the previous applied results and search-bar draft separately. If Replace all discarded an invalid or unapplied draft, Undo brings back that text without running it. Use Revert edits to discard the restored draft, or edit and apply it. Undo is available once the current operation finishes. Ordinary typing still uses the browser's text-editing undo.

One complete filter

The expression bar contains the complete filter: column inputs, right-click include/exclude actions and saved filters all insert visible conditions. There are no additional hidden column or preset predicates. Adding a condition combines it with the existing expression using AND and preserves any OR groups with parentheses. Adding saved filters deduplicates identical top-level AND conditions; it does not split OR or NOT groups. You can copy the bar to reproduce the whole filter or use Save current filter to create a reusable preset.

Column inputs are literal, case-insensitive “contains” filters. Typing updates the expression immediately; press Enter or Apply search to update results. Editing a top-level contains condition in the bar updates its column input. OR/NOT groups remain in the expression because independent column inputs cannot represent them faithfully. Multiple contains conditions on one column must be edited in the bar; its column input is disabled until only one remains.

Plain text, Regex and jq subset searches automatically convert to an equivalent expression when you add a field condition or saved filter. Switching modes also preserves meaning. Switching to Plain text or Regex is blocked if those modes cannot represent the current filter; keep Expression mode or clear the filter first.

Unapplied edits explicitly marks a draft. Results, exports and the removable chips still describe the last successfully applied filter. Revert edits restores it. Sorting and paging do not apply draft text. Chips are disabled while there are pending edits so clicking one cannot silently overwrite your draft. Invalid expressions keep the previous results. Clear all clears the complete query.

Applied chips represent independent top-level AND conditions. A whole OR/NOT group stays together; removing it removes that group. Adding a saved filter inserts a snapshot of its definition; it does not remain as a separate active preset. Editing or deleting a saved definition does not change conditions already inserted in the bar.

Drag a column title onto the left or right half of another heading to place it before or after that column. You can also focus a title and use Alt + Left/Right. Order is retained while filtering, sorting, paging and hiding/showing columns, and CSV export uses that order. Opening a new file or selecting a new collection restores its initial order. Use × beside a column title to hide that field. Hiding a field keeps any filters on it active and visible in the expression bar. Restore hidden fields with Columns or the gear in the top-left table header; both open the same visibility selector.

Click a column heading to sort all matched records. Right-click a heading to download its unique values. Ascending sorts group types consistently: missing values, null, booleans, numbers, strings, then arrays/objects. Numbers use numeric order; strings use natural text order (for example, "2" before "10"). Numeric strings remain strings. Descending reverses this order; ties keep original record order. Record numbers identify positions in the original collection.

Edit filters inside the app

Open Edit filters, select an existing preset or New filter, choose its syntax, edit, then save. New presets can use expression, plain text, regex, or the legacy jq subset. Saving validates syntax before updating a preset. Choose it from Add filter to insert it into your current expression; the menu then resets for the next addition.

Import supports a JSON array or the original window.FILTER_PRESETS = […]; assignment with a JSON array. Both old {"name":"…","filter":"…"} and new {"name":"…","mode":"expression","query":"…"} entries work. JavaScript is not executed. Leading/trailing comments around the assignment are allowed, but the array itself must use strict JSON syntax, with quoted keys and double-quoted strings; arbitrary JavaScript object literals are rejected. Import merges presets; matching names are replaced. Unsupported syntax rejects the entire import.

Presets and theme are stored in browser storage when available. Local-file storage behavior depends on the browser and file location; export a backup for reliable portability. The app never silently overwrites local files. Export filters.js downloads a new file; replace this version's filters.js manually to distribute defaults. Browser-saved presets take precedence over shipped defaults. Exported v2 presets are intended for v2.

How the existing “jq” actually works

The original app.js implements compileJqFilter() and tokenize() in JavaScript. It parses a narrow grammar beginning .[] | select(...) into predicate functions over flattened rows. It does not invoke jq, compile jq to WebAssembly, or load a jq library.

V2 retains this compatibility syntax for your existing SNAT and DNAT presets, and labels it jq subset. It supports comparisons, field references, and/or and nested select expressions. It is not a complete jq implementation: transformations, arbitrary pipelines, array iteration, jq functions and jq's full stream/truth semantics are not implemented. Equality is type-strict in v2, whereas the old parser used JavaScript's coercing equality. Both supplied NAT filters have matching results on both sample files.

Export and data fidelity

Export JSON downloads all filtered records in current sort order, preserving original nesting and JSON types. It exports the selected record collection as an array, not its parent wrapper. A download is a snapshot of the results when requested; changing a filter while it is being prepared does not change that download. Export CSV uses all filtered rows and only visible columns. Values starting with spreadsheet formula characters receive a protective apostrophe; use JSON for faithful data interchange.

Parsing uses JavaScript JSON.parse. Numbers beyond JavaScript's safe integer range (±9,007,199,254,740,991), duplicate object keys and original whitespace cannot be preserved faithfully. The viewer now displays a persistent warning when it finds unsafe integers. Numeric overflow (for example 1e400) is rejected rather than silently exported as null. For exact very-large identifiers, export them as strings at the source. The provided session IDs fit in the safe range.

Data is held in worker memory, including original and flattened records. The 103 MiB sample was tested, but this is not a streaming or unlimited-size database. Very large files may exceed browser memory. Documents deeper than 256 levels are rejected. Expressions are limited to 16,384 characters, 1,024 tokens and 64 parenthesis levels to prevent the editor from exhausting the JavaScript call stack. Virtual scrolling compresses offscreen spacers for very tall result sets, while visible rows retain their normal height. Loading and export have a 90-second timeout. No file contents are stored in browser storage.

Hardening review · 24 October 2026

This review covered the current JSON Inspect source, including parsing, worker requests, virtual scrolling, exports, imports and the editor. Confirmed findings were fixed and given regression coverage. It is not a guarantee of bug-free operation.

Priority / findingCorrection and evidence
High: absent fields could resolve to inherited JavaScript properties.Field access now checks ownership. Regression tests include structured-cloned rows and names such as toString, constructor and __proto__.
High: a completed older operation could clear a newer operation’s busy state and timeout.Responses carry operation/request IDs; old-worker callbacks are ignored. Tests deliberately delay exports and queries, replace workers and reverse source replies.
High: sufficiently tall virtual collections exceeded browser element-height limits, making their final records unreachable.Only offscreen spacers are compressed. A 900,000-record test reaches the last record and the middle while keeping DOM and transfer windows bounded.
Medium: leading-dot decimals were interpreted as field references, unknown string escapes silently lost backslashes, and exists() accepted constants.Numeric recognition and field access are now distinct; malformed escapes and exists() arguments are rejected. Query complexity limits prevent unbounded parser recursion.
Medium: unsafe JSON numbers could lose precision; overflow could export as null.Unsafe integers receive a persistent warning; non-finite parsed numbers are rejected. Exact large identifiers must still be supplied as strings.
Medium: rebuilding overlapping nested windows discarded live record nodes and could refetch expanded records.Overlapping records are retained. Returning expanded records reserve their measured height while loading; a browser test checks node retention.
Medium: CSV formula protection did not cover whitespace before formula characters.Leading whitespace/control characters are checked before protecting formula-like cells. JSON export remains the fidelity-preserving format for supported JSON values.
Medium: the shipped filters.js could not be re-imported as documented.Shipped defaults now use strict JSON syntax; safe assignment/comment parsing accepts them and rejects additional executable code.

Run node tests/run-tests.cjs from this folder for the dependency-free core tests. To also run browser checks, use node tests/run-tests.cjs --browser with a compatible Node, an externally installed Playwright and Chromium; PLAYWRIGHT_MODULE and CHROMIUM_PATH can point to those installations. These are development tools, not app dependencies.

Validation is on offline Chromium via file://. Firefox, Safari, other operating systems and enterprise browser policies remain unverified. The app still parses files in memory, uses a limited jq-compatible grammar rather than full jq, and requires strings for exact large integers. Regular expressions run in the cancellable worker, with a timeout; they are not guaranteed to finish quickly.

Review of the original version

FindingEffect and v2 change
index.html loads Tabulator JavaScript and theme CSS from unpkg.Opening locally still requires external assets. V2 uses only bundled, dependency-free files.
app.js retains flattened rows only and changes null to an empty string.Original nesting and null distinctions are lost. V2 retains raw records and builds a separate flattened projection.
Global, header and saved filters execute in the main thread; header regexes are compiled per comparison.Large scans and expensive regexes can block interaction. V2 compiles query predicates once and scans/sorts/exports in a cancellable worker.
The active Blob worker reads and parses the entire file.The separate streaming parser-worker.js is not used by the current app. V2 explicitly documents full-file memory usage and renders a bounded page or virtual window of results.
Only top-level arrays and objects with a sessions array are accepted.V2 adds selectable nested collections, whole objects, primitive records and JSON Lines.
Flattening concatenates keys with dots into a normal object.Literal dotted keys can collide with nested paths, and special property names are unsafe to assign normally. V2 uses canonical bracket notation and null-prototype maps.
Storage access is unguarded; worker Blob URLs are not revoked.Storage denial can interrupt startup, and repeated loads leak URL handles. V2 guards storage and revokes worker/download URLs.
The unused streaming parser does not enforce complete array grammar.Missing closing brackets, missing separators and trailing content are not reliably rejected. V2 uses JSON.parse for validation instead of carrying forward this parser.

Suggested next improvements

  1. Field summaries and facets: counts by policy, application, source network, and byte totals would make SRX investigation faster without losing generic JSON support.
  2. Saved workspaces: combine column layouts with sort order and filters in a single exportable workspace.
  3. Explicit schema adapters: normalize native Junos output and other wrapped formats into records while keeping the original document available.
  4. Very large file support: incremental parsing and an indexed local data store, guided by the actual file-size ceiling needed at work.
  5. Full jq only if needed: a separately bundled engine would add transformation support, but also size, maintenance and local-file loading complexity. The current expression language covers record filtering without that dependency.

Code organization

All runtime files are ordinary local scripts, loaded in the order listed in index.html. Keep them together when copying the application. state.js holds shared state and small helpers; worker-client.js coordinates loading, exports and worker replies; filter-controller.js handles draft/applied queries and Undo; table-view.js handles tables, nested views, virtual scrolling and column controls; app.js wires the remaining controls and starts the application. core.js contains the shared expression engine and comparison rules. worker.js performs data processing. Layouts and autocomplete have their own scripts.

Verification

The core test suite covers boolean precedence, types, comparisons, escaping, field references, IPv4/IPv6 CIDRs, malformed expressions, legacy presets, unusual keys and both supplied files. Run node tests/run-tests.cjs from this folder. Add --browser with Playwright and Chromium configured to run the full offline browser suite.

Chromium was tested by opening file://…/index.html with browser networking disabled: both sample imports, the filter editor, searches, nested inspection, JSON export, pagination, generic collection selection, invalid JSON and JSON Lines. No HTTP requests or page errors were observed. Other desktop browsers and organization-specific policies have not been verified.

Reference counts: tips-is.json: 70 records, 8 SNAT, 0 DNAT. vr5-flows.json: 112,100 records, 5,975 SNAT, 10,581 DNAT; 70,992 records match source 10.0.0.0/8 and destination port 53.

The browser test scripts include tests/browser-tests.cjs, tests/filter-tests.cjs and tests/virtual-tests.cjs. The virtual-scroll test uses the 112,100-record file and checks bounded DOM size and worker messages, scrollbar jumps, lazy record inspection, filtering/sorting, column changes, pagination and variable-height nested records. The latter covers unified column filters, OR groups, repeated fields, mode conversion, presets, chips, pending edits and worker response races. It needs an externally installed Playwright and Chromium for development verification only; the app itself has no runtime dependencies.