# Writing a Zero-Dependency JSONPath Evaluator and TypeScript Interface Inference Engine

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:

1. **Child property & wildcard selectors:** `$.store.book[*].author`
2. **Recursive descent (**`..`**):** `$..price` (collects matching keys at any nesting depth)
3. **Negative index & slice selectors:** `$.items[-1]` or `$.items[0:5]`
4. **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:

```typescript
// 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`):

```json
[
  { "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:

1. **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`).
2. **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](https://kivarotools.com/developer/json-formatter), where everything executes locally in browser memory with zero server calls.
