# json-function: querying JSON arrays with where, select, orderBy and joins

> json-function is a dependency-free TypeScript library for querying arrays of JSON objects in memory, with a chainable API and standalone functions that preserve the item type. Version 2 rewrites the semantics rather than just the API, and the removal of transform closes a prototype pollution hole.

**aykutkardas/json-function** — Query arrays of JSON objects with where, search, select, orderBy, limit, schema and joins. Typed, dependency-free, fast on large arrays.

- Repository: https://github.com/aykutkardas/json-function
- Stars: 745 · Forks: 32
- Language: TypeScript
- License: MIT
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/aykutkardas-json-function

## Filtering JSON that already sits in memory

json-function exists for the case where you have an array of JSON objects and you want to filter, reshape or sort it. The project's own description lists where, search, select, orderBy, limit, schema and joins, and the README states the goals plainly: no dependencies, TypeScript types included, and fast on large arrays.

The audience is developers working in Node.js or the browser who already hold the data client side, typically a response from an API that was fetched once, and who would otherwise write a hand rolled filter or pull in a query layer. There is no server component and no persistence here. The library operates on the array you hand it and hands results back.

The interesting part is not the feature list but the API shape. The same capability is available in two forms, a chainable query object and standalone functions, and the design in 2.0 makes the chain immutable, which changes how partial queries can be saved, shared and reused across a codebase.

## A chain of queries or a call per operation

The chain form builds a query and runs it against data with get:

```js
import JsonFunction from "json-function";

const result = JsonFunction
  .where({ completed: false })
  .select(["title", "completed"])
  .orderBy("title", "DESC")
  .limit(2)
  .get(data);
```

The standalone form calls the same operations directly on an array, which is handy when you only need one behaviour:

```js
import { where, orderBy } from "json-function";

const incomplete = where(data, { completed: false });
const sorted = orderBy(incomplete, "title", "DESC");
```

The chain is immutable by design. Every chain method returns a new query, so a partial query can be stored and reused, and getQuery() and setQuery() save and restore a query. In 2.0 getQuery() returns an ordered list of steps and those steps run in that order, so a saved query is data rather than a closure. Queries saved in the old 1.x object format are still accepted by setQuery() and by get(data, { query }), which eases the upgrade but also means the migration note is worth reading rather than skimming.

## The function list is a small query language

The available operations are wider than filtering. where filters by field values with AND, OR, deep fields and comparison helpers such as wh.gt and wh.in. search finds items whose fields contain a given text. select keeps only some fields, including nested ones. orderBy sorts by a field ascending or descending, and limit takes a number of items from a start index.

Then there are the reshaping operations. schema can rename, nest, join and compute fields. innerJoin and leftJoin join two arrays on a field. toArray turns an object of objects into an array.

Types are part of the contract rather than an afterthought. The standalone functions keep the item type of their input, so where(users, ...) returns User[] and select(users, ["id", "name"]) returns Pick<User, "id" | "name">[]. For a TypeScript codebase that is the main argument for using this over ad hoc array methods, since the result type narrows to the fields you actually selected.

## Version 2 changed semantics, not just signatures

The migration notes are the most useful part of the documentation, because several behaviours in 1.x were traps. Chaining is now immutable, so calling methods one by one without using the return value no longer builds a query:

```js
  // 1.x
  JsonFunction.where({ completed: false });
  JsonFunction.limit(2);
  JsonFunction.get(data);

  // 2.x
  const query = JsonFunction.where({ completed: false }).limit(2);
  query.get(data);
```

innerJoin is now a real inner join. Items without a match are dropped, and an item with several matches appears once per match, so leftJoin is what keeps unmatched items. where with several queries returns each matching item once, in input order. orderBy no longer sorts the input array in place, which matters when the same array is used elsewhere in your program. search matches the key literally with no regular expressions, skips missing fields, and is case sensitive unless you pass caseSensitive: false.

The resetRecord option and the option and data properties are gone.

## transform was removed over a prototype pollution issue

One removal deserves its own attention, because it changes what the library is for. transform and .transform() no longer exist, and the stated reason is that converting key casing is outside what this library queries, and the function was the source of a prototype pollution issue.

The documented path forward is to use a separate package such as camelcase-keys, for example camelcaseKeys(data, { deep: true }), or to rename specific fields with schema(data, { userId: "user_id" }). If your pipeline depended on transform as a normalisation step, this is the part of the upgrade that will break your code, and it is a security-driven break rather than a stylistic one.

The module format also changed. The package is now ES module only and no longer ships a separate CommonJS build. import works everywhere, and require("json-function") keeps working on Node.js 20.19+ and 22.12+, which can load ES modules with require. On older Node.js versions require fails with ERR_REQUIRE_ESM, so any consumer still pinned below those versions has to migrate the import or stay on 1.x.

## Install, Node version and a first query

The package is published to npm and installs like any other dependency:

```bash
npm install json-function
```

or with pnpm:

```bash
pnpm add json-function
```

The runtime requirements are declared in package.json, and they are not trivial:

```json
{
  "name": "json-function",
  "version": "2.0.0",
  "type": "module",
  "engines": {
    "node": "^20.19.0 || >=22.12.0"
  },
}
```

The README says the package requires Node.js 20.19 or later and that require("json-function") also works on these versions. The published files are the dist directory only, the module is marked as having no side effects, and the package manager pinned for the repository is pnpm@12.8.1.

A first use is the chain from the README. Filter with where, narrow the shape with select, sort with orderBy, cap the result with limit, then pass the array to get. Each stage returns a new query, so the same filtered set can be reused and passed through setQuery() later.

## When a database query would be the better tool

The honest boundary of this library is that it filters what is already in memory. It does not fetch, paginate, index or persist. If your data sits behind a paginated API and you only want the first page of matching records, a query executed where the data lives will do less work than downloading everything and filtering in Node.js.

The alternative approach is therefore a different architecture rather than a different library: push the filter, the sort and the join into the database with SQL, where the engine can use indexes and avoid transferring rows you will discard. json-function wins in the opposite case, when the array is small enough to hold, when it came from a fetch you already need, when it lives in the browser, or when the shape of the result matters to your types more than query planning does.

A second limitation is scope. The library is a query surface over plain objects, not a data pipeline. It does not validate against a schema, it does not transform key casing any more, and search has no regular expressions, so a fuzzy or pattern matching search is out of scope by design.

## Release history, licensing and the upgrade path

The project is under active development. Two releases share the same date, v2.0.0 and v1.8.40, both published on 2026-10-03, and the last push to the repository was on 2026-10-03 as well. Before that, v1.8.35 dates from 2021-05-23, so the gap between the older 1.8.x line and the current work is long, and the 2.0 release notes are effectively the upgrade manual for anyone on an old version.

The license is MIT, so the code can be used in commercial and closed source products, subject to keeping the license text and copyright notice in place. That is a factual reading of the license grant rather than legal advice, and reading the LICENSE file in the repository is still the right step before you ship it.

The repository layout shows the working method: a src directory, a test directory, a bench directory, per operation documentation under docs, and a CHANGELOG.md. The package.json scripts give you the same checks the author uses, with vitest run --typecheck for the test suite, tsc --noEmit for typechecking, a build script, and a bench script that builds and then runs bench/run.js. Running the typecheck and test scripts against your own project is the cheapest way to find out whether a 2.0 upgrade breaks you.

## Conclusion

Adopt json-function when the data already lives in memory as an array of objects, when you want a typed result without adding a query engine to the bundle, and when you are on Node.js 20.19 or later. Do not reach for it to filter data that is still behind a paginated API, and read the 2.0 migration notes before upgrading a 1.x codebase, because chaining, innerJoin, where, orderBy, search and transform all changed behaviour. Before you commit, run the typecheck and test scripts from package.json against your own types, and confirm your build tooling can load an ES module package.

## FAQ

### How do I install json-function and which Node.js versions are supported?

Run npm install json-function or pnpm add json-function. The package requires Node.js 20.19 or later, and the engines field in package.json allows ^20.19.0 || >=22.12.0.

### Does json-function work with require in CommonJS projects?

Yes, but only on Node.js 20.19+ and 22.12+, which can load ES modules with require. The package no longer ships a separate CommonJS build, and on older Node.js versions require fails with ERR_REQUIRE_ESM.

### What changed in json-function 2.0?

Chaining became immutable, innerJoin became a real inner join, where with several queries returns each matching item once in input order, orderBy no longer sorts the input in place, and search no longer uses regular expressions. The transform function and the resetRecord option were removed.

### Why was transform removed from json-function?

The stated reason is that converting key casing is outside what the library queries, and the function was the source of a prototype pollution issue. The documentation points to a separate casing package or to renaming specific fields with schema.

### Does json-function keep TypeScript types on the result?

Yes. The standalone functions are generic and keep the item type of their input, so where(users, ...) returns User[] and select(users, ["id", "name"]) returns Pick<User, "id" | "name">[].

## Sources

- [aykutkardas/json-function on GitHub](https://github.com/aykutkardas/json-function)
- [Issues](https://github.com/aykutkardas/json-function/issues)
- [License: MIT](https://github.com/aykutkardas/json-function/blob/main/LICENSE)
- [README](https://github.com/aykutkardas/json-function/blob/main/README.md)
- [Releases](https://github.com/aykutkardas/json-function/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/aykutkardas-json-function
