react-native-fast-json
v0.1.0
Published
Fast JSON parsing for large files in React Native, powered by simdjson (C++ SIMD acceleration) via Nitro Modules
Maintainers
Readme
react-native-fast-json
JSON.parse turns the whole file into nested JavaScript objects in the same memory as your app code. That is fine for small JSON; for very large files (on the order of tens to hundreds of MB), it often means:
- CPU stays pegged high while parsing finishes
- The screen freezes or stutters
- Memory use jumps; in bad cases the app is killed for using too much memory
This library parses in native code with simdjson and exposes a JsonView through Nitro Modules—you read the document lazily from native memory instead of inflating everything into one giant JavaScript tree up front.
Rough illustration for a ~250 MB file (device and JSON shape will change this):
| | JSON.parse | parseFile + root JsonView |
| --- | --- | --- |
| Parse / load | Often many seconds on a phone (every object is allocated in JS) | Native read + parse often ~100 ms for the same ~250 MB—near-instant at UI scale; exact time depends on disk and CPU |
| CPU | Long spikes (often >100% in system monitors) | Calmer during native load/parse |
| Memory (ballpark) | ~1.2 GB in a heavy case | ~400 MB (the file still has to live somewhere) |
These numbers are illustrative only—measure your own file on real hardware before you budget.
When to use it
- Large JSON (for example ~100–200 MB+: catalogs, offline bundles, big dumps) where
JSON.parsefeels too slow, freezes the UI, or uses too much memory. - Mostly read-only, targeted access: a few keys, paths, or scalars.
Prefer normal JSON.parse for small responses and whenever you need the full tree as plain JS objects anyway.
Requirements
- React Native with the setup expected by Nitro (see Nitro docs).
react-native-nitro-modulesas a dependency (peer).
Installation
yarn add react-native-fast-json react-native-nitro-modules
# or
npm install react-native-fast-json react-native-nitro-modulesThen install iOS pods from your app root:
cd ios && pod installRebuild the native app after adding the dependency.
Usage
import { fastJson, type JsonView } from 'react-native-fast-json';
// From a JSON string (loads into native memory; not cached by path)
const root = await fastJson.parseString(jsonString);
if (!root) return;
//Preferred - From a file path (native reads the file; result is cached per path)
const fromFile = await fastJson.parseFile('/path/to/data.json');
// Drop cached parse for that path when you are done (see Memory below)
fastJson.release('/path/to/data.json');JsonView
Navigate and read values without building a full JS tree up front:
| Method / property | Purpose |
|-------------------|--------|
| getValue(key) | Single object key (not a path). Returns JsonView \| null. |
| keys() | Keys for objects / arrays (as applicable). |
| at(index) | Array element by index. |
| atPath('$.a.b.c') | Simple dotted path from $ (no [index] in path). |
| atPathWithWildcard('$.items[*].id') | Wildcard / index segments; returns string[] \| null. |
| type, length | Value kind and length (objects/arrays). |
| asString(), asNumber(), asBoolean() | Scalar coercion. |
| asObject() | Materialize object/array into an AnyMap (expensive for large values). |
| rawJson() | Raw JSON slice as string (can be expensive for large values). |
Example:
const meta = root.getValue('metadata');
const version = meta?.getValue('version')?.asString();Memory and caching
How big is “big”?
Native memory for a parse is dominated by the root buffer, which is roughly the JSON byte size plus simdjson padding (think on the order of the file size, not “a few MB overhead”).
| Rough file size | What to expect |
|-----------------|------------------|
| Under ~10 MB | Usually fine to keep a root around for a screen session if you need repeated lazy access. |
| ~10–50 MB | Still workable; treat the root as a large native allocation and avoid keeping multiple overlapping parses. |
| ~50–200 MB | High impact on device RAM and OOM risk if you stack parses or retain roots in global state. Plan release(path) and avoid rawJson / asObject on the whole document. |
| 200 MB+ | Same as above, but stricter: short-lived root, extract what you need, then drop handles immediately (see below). |
These are ballparks—actual pressure depends on device, OS, and what else your app keeps in JavaScript memory versus native (C++) memory.
Prefer a short-lived root: read what you need, then discard
Do not keep the root JsonView in React state, context, or a singleton for the whole app lifetime unless you truly need random access to that document for a long time.
A safer pattern for large files:
parseFile(orparseString) once when you need the data.- In the same synchronous stretch (or one small async function), walk the tree and copy primitives, small objects, or IDs into plain JavaScript values you actually store in state.
- Clear your
JsonViewreferences and callrelease(path)for file parses so the native cache and buffer can go away.
async function loadConfig(path: string) {
const root = await fastJson.parseFile(path);
try {
const version = root?.getValue('metadata')?.getValue('version')?.asString();
const batchSize = root
?.atPath('$.metadata.configuration.export_settings.batch_size')
?.asNumber();
return { version, batchSize }; // plain JS — safe to keep
} finally {
fastJson.release(path);
}
}That way you pay the large native buffer only while you extract fields—not for the entire time the user has the app open.
If you do need the root for a while (e.g. a deep drill-down UI over the same file), keep one root per path, avoid overlapping second parses of the same huge file, and still release when the user leaves the flow.
Other rules of thumb
- Root
JsonViewholds the full parsed buffer in native memory (file size + padding forparseFile, string size forparseString). parseFile(path)caches the native view by path string. RepeatedparseFilewith the same path returns the same cached root untilrelease(path)removes it.parseStringdoes not use that path cache; each call allocates a new native view for that string (until the JS side drops theJsonView).getValue/at/ paths may create child views that copy JSON slices into their own buffers. Holding many large subtrees can add up.rawJson()andasObject()on very large values can allocate large extra memory (strings / maps). Use sparingly on big documents.
If you only need a subtree in JavaScript, you can still hold just that subtree—but while parseFile keeps the root in the path cache, the full file buffer stays in native memory until you release that path (and nothing else retains the view).
API summary
| Method | Description |
|--------|-------------|
| parseString(str) | Parse JSON from a string. |
| parseFile(path) | Load and parse JSON from a filesystem path; cached by path. |
| release(path) | Remove cached parse for path (file cache only). |
Contributing
License
MIT
Made with create-react-native-library
