---
title: "Migrate legacy JSONEval"
description: "How to migrate from the legacy TypeScript JSONEval engine to the new Rust-based JSONEval RS implementations across all bindings."
---

# How to migrate from legacy JSONEval

This guide outlines how to update your React Native, Node.js, or browser codebase from the legacy TypeScript `@qsi/json-eval` engine to the new unified Rust bindings.

The `@json-eval-rs` suite separates platform bindings differently to maximize speed. Web wrappers handle WebAssembly instantiation asynchronously, while React Native uses synchronous native setup via JSI. However, both expose the same asynchronous JavaScript API.

---

## Part 1. Instantiation and configuration

The legacy package initialized everything directly in the constructor using positional arguments. The new API groups these neatly inside an options object.

### Legacy initialization (`@qsi/json-eval`)
```typescript
// Legacy: Positional arguments required
import { JSONEval } from "@qsi/json-eval";
const evaluator = new JSONEval(schema, context, autoEvaluate, data, customFunctions, tzOffset);
```

### New web initialization (`@json-eval-rs/bundler`, `@json-eval-rs/vanilla`, `@json-eval-rs/node`)
Web applications use wrappers that load the WebAssembly bindings. Because WASM requires asynchronous setup, you control this process manually.

```typescript
// Import explicitly from your platform's wrapper
import { JSONEval } from "@json-eval-rs/bundler"; // Vite, Webpack, Next.js, Rollup
// import { JSONEval } from "@json-eval-rs/node"; // Backend & SSR

const evaluator = new JSONEval({
  schema: schema,
  context: context, // optional
  data: data        // optional
});

// WASM requires initialization.
// If you call `await evaluator.evaluate()`, `init()` runs automatically.
// Calling it manually is good practice for pre-loading:
await evaluator.init(); 
```

### New React Native initialization (`@json-eval-rs/react-native`)
React Native uses JSI (JavaScript Interface) to load instances directly from memory, which removes the need for asynchronous `.init()` checks.

```typescript
import { JSONEval } from "@json-eval-rs/react-native";

// Synchronous constructor automatically loading the module via JSI.
const evaluator = new JSONEval({
  schema: schema,
  context: context,
  data: data
});

// Ready to evaluate immediately. No `.init()` required.
```

---

## Part 2. Execution and returned values

The legacy `evaluate()` mutated internal properties implicitly, making state tracking a headache in UI frameworks. The new packages strictly separate state mutation from variable extraction. This cuts down the parsing overhead across the JavaScript bridge.

### Full evaluation (`evaluate` vs `evaluateOnly`)

Think about what result data you actually need when dispatching an evaluation:

- **`evaluateOnly({ data, context, paths })`**: Mutates the form state internally and returns `void`. This is much faster because it avoids converting the large evaluated schema tree back into Javascript. Use this if you only plan to extract targeted variables reactively later.
- **`evaluate({ data, context, paths })`**: Evaluates the data and also returns the fully generated JavaScript `Object` matching your schema tree.

### Reactive dependent evaluation (`evaluateDependents`)

The legacy platform often fired cascading updates broadly or required you to handle manual `calledBy` arrays to track dependencies. The new engine simply diffs and returns the specific nodes that changed.

```typescript
// Resolve only the nodes affected by this path:
const dependentDiffArray = await evaluator.evaluateDependents({
  changedPaths: ["properties.patientData.value"]
});

// Returns the specific fields modified by the interaction cascade:
// e.g. [{ $ref: "transitiveField.value", value: 10, transitive: true, clear: false }]
```

---

## Part 3. Schema and subset retrieval

The legacy codebase forced you to fetch the entire root tree (`getEvaluatedSchema()`) unconditionally, leaving you to rely on manual client-side loops or the expensive `_jpath` helper to find what you needed. 

The unified API offers fetch methods that target precise bytes directly from Rust memory without serializing the parent properties.

| New API method  | Replaces legacy | Description |
|-----------------|-----------------|-------------|
| `getEvaluatedSchema({ skipLayout: false })` | `getEvaluatedSchema()` | Returns the full parsed JS Object tree. |
| `getEvaluatedSchemaWithoutParams({ ... })` | `getSchema()` | Returns raw data values without internal runtime parameter metadata. |
| `getEvaluatedSchemaByPath({ path })` | `_jpath(obj, path)` | Fetches the result of an isolated path, saving Javascript bridge bandwidth. |
| `getEvaluatedSchemaByPaths({ paths, format })` | (Looping `_jpath`) | Native bulk path extraction. The `format` maps output recursively (`0`), as a flat object (`1`), or as an array sequence (`2`). |
| `getSchemaByPath({ path })` (or `.get()`) | `_jpath(obj, path)` | Extracts the schema definition without evaluating functional logics. |

---

## Part 4. Data validation

The validation engine now operates independently instead of silently alongside evaluation. It drops the older `paths` array filters and simply returns a mapped object keyed by the failing paths.

### Validation routine (`validate`)

```typescript
// Legacy:
// const { hasError, error } = evaluator.validate(data, context, paths);

// New unified return:
const result = await evaluator.validate({
  data: data,
  context: context
});

if (result.has_error || result.hasError) { 
  // Error dictionary is keyed natively by path making it easy to wire into UI states.
  console.log(result.error["properties.name.value"].message); 
}
```

---

## Part 5. Advanced extensions

The native Rust core unlocks features that weren't feasible in the older engine:

- **Isolated static compilation (`evaluateLogic`)**: Extracts and evaluates a standalone JSON Logic tree without instantiating a full schema instance:
```typescript
JSONEval.evaluateLogic({ "==": [1, 1] }); // true
```
- **Active thread interruption (`cancel()`)**: You can intercept and stop long-running evaluations on massive datasets using `await evaluator.cancel()`.

---

## Part 6. Fully managed subforms

The legacy package didn't handle subforms out of the box. If you wanted to evaluate logic inside a nested schema array, you had to manually instantiate a brand new detached `JSONEval` instance for the subform data.

### Legacy configuration
```typescript
// Legacy: Peeling the array template and booting a detached class instance.
const subformTemplate = parentSchema.properties.riders.items;
const detachedJse = new JSONEval(subformTemplate, mainContext, false, subformData);

detachedJse.evaluate(subformData, mainContext);
```

### Modern subform operations
The new engine resolves subforms internally using path identifiers. The core instance tracks the array document natively so you never split the tree structure in memory.

By pointing an `activeSubformPath` to the exact node (e.g. index `0` of the `riders` array), you can restrict evaluations exclusively to that boundary:

```typescript
// Modern: Scoping execution natively.
const activeSubformPath = "#/properties/riders/0";

await evaluator.evaluateSubform(activeSubformPath, formPayload, mainContext);

const diffs = await evaluator.evaluateDependentsSubform(
  activeSubformPath, 
  ["properties.benefit.value"], // Changed fields in the subform scope
  formPayload, 
  mainContext, 
  true
);

const validationData = await evaluator.validateSubform(activeSubformPath, formPayload, mainContext);

// Subform definitions are fetched without pulling the parent schema tree overhead.
const nodeSchema = await evaluator.getEvaluatedSchemaSubform(activeSubformPath, true);
const childData = await evaluator.getSchemaValueSubform(activeSubformPath);
```

Using these subform endpoints (`evaluateSubform`, `validateSubform`, `evaluateDependentsSubform`, etc.) ensures complex array iterations compute independently fast.

---

## Part 7. Tips for performance

When migrating, don't blindly port over older legacy loops. The Rust API gives you tools to fetch data much more efficiently:

- **Bypass parameter bulk**: Use `getEvaluatedSchemaWithoutParams` instead of `getEvaluatedSchema` for fetching the full tree. Stripping out the backend `$params` object cuts down the serialization payload traveling across the Javascript bridge.
- **Query only what you need**: It used to be common to fetch the full object schema just to inspect if a single conditional variable had toggled. Swap those out for `getEvaluatedSchemaByPath` or `getEvaluatedSchemaByPaths`. It prevents random memory spikes and limits serialization exactly to the outputs you actually requested.
