Writing a Zero-Dependency JSONPath Evaluator and TypeScript Interface Inference Engine
Implementing RFC 9535-style JSONPath filter queries and polymorphic TypeScript interface inference from heterogeneous JSON arrays without using eval().

When working with complex REST responses or webhook payloads, developers typically reach for three separate utilities: a query evaluator (like jsonpath-plus) to slice nested arrays, a structural diff tool to compare staging vs. production responses, and a code generator to build TypeScript interfaces and JSON Schemas.
In this deep dive, we will look at how to implement RFC 9535-style JSONPath evaluation and polymorphic TypeScript interface inference in under 300 lines of pure, zero-dependency TypeScript.
1. Tokenizing JSONPath Expressions (.., [start:end], and [?(@.field > X)])
A practical JSONPath evaluator needs to resolve five selector types against an arbitrary parsed JsonValue tree:
- Child property & wildcard selectors:
$.store.book[*].author - Recursive descent (
..):$..price(collects matching keys at any nesting depth) - Negative index & slice selectors:
$.items[-1]or$.items[0:5] - Filter expressions (
[?(@.key op value)]): Supporting==,!=,<,<=,>,>=, and truthy existence checks[?(@.deprecated)].
By parsing the path into discrete segment tokens (child, wildcard, slice, filter, plus a boolean recursive flag when preceded by ..), the evaluator walks the current candidate set cleanly:
// Evaluating a slice token [start:end] with negative index support:
const length = currentArray.length;
const start = rawStart < 0 ? Math.max(length + rawStart, 0) : Math.min(rawStart, length);
const end = rawEnd === null
? length
: rawEnd < 0
? Math.max(length + rawEnd, 0)
: Math.min(rawEnd, length);
For filter predicates like $.orders[?(@.total >= 100)], the parser extracts the left-hand property path (total), the comparison operator (>=), and the right-hand literal (100), evaluating each array item safely without ever calling eval() or new Function() (which would violate Content Security Policy script-src rules).
2. Inferring Clean TypeScript Interfaces from Heterogeneous JSON Arrays
Generating a TypeScript interface from a single flat JSON object is trivial. The real challenge is heterogeneous arrays of objects, where some elements omit optional fields or contain union types (string | null):
[
{ "id": 1, "name": "Alice", "MiddleName": null, "tags": ["admin"] },
{ "id": 2, "name": "Bob", "verified": true, "tags": [] }
]
If an inference engine only inspects array[0], it misses verified: boolean on array[1] and types MiddleName as null forever.
Instead, a proper structural inference pass iterates across all object elements in an array:
- Key Frequency Counting: It counts how many objects in the array contain each key $k$. If
count(k) < objectElements.length, the property is marked optional (verified?: boolean). - Type Union Merging: For each key $k$, it collects the inferred TypeScript types across all occurrences, deduplicates them into a union (
string | null), and recursively emits named sub-interfaces (RootItem,RootItemProfile) in bottom-up order so every referenced interface is declared before use.
We ship both this zero-eval JSONPath engine and the TypeScript / Draft-07 JSON Schema generator inside the KivaroTools Client-Side JSON Workbench, where everything executes locally in browser memory with zero server calls.
About the author
- Founder & Lead Software Architect @ KivaroTools | BS Computer Science | Full-Stack & Systems Engineer (TypeScript, Next.js, Python, Java, C++, WebAssembly) | Privacy-First Web & Financial Engines
Comments
Join the discussionNo comments yet. Be the first to comment.
KivaroTools: Browser Engineering & Client-Side Architecture
A technical series exploring the engineering behind KivaroTools, including browser-based file processing, JavaScript and TypeScript architecture, memory management, application security, data parsing, performance optimization, and financial computation. Each article shares practical implementation insights, engineering decisions, testing approaches, and lessons learned while building reliable web utilities.



