Object join technics.
This project is maintained by lopatnov
A TypeScript library providing SQL-like join operations for JavaScript objects. Merge two objects using one of 9 composable join types, with support for deep merging of nested objects.
npm install @lopatnov/join
Browser:
<script src="https://lopatnov.github.io/join/dist/join.umd.min.js"></script>
import { join, JoinTypes } from "@lopatnov/join";
const { join, JoinTypes } = require("@lopatnov/join");

The library uses a 4-bit flag system to describe which parts of two objects appear in the result:
| Bit | Name | Meaning |
|---|---|---|
| 3 | left |
Include properties unique to the left object |
| 2 | innerLeft |
Include shared properties, keeping left values |
| 1 | innerRight |
Include shared properties, using right values |
| 0 | right |
Include properties unique to the right object |
| Join Type | Bits | Description |
|---|---|---|
none |
0000 |
Empty result — no properties from either object |
left |
1000 |
Only properties unique to the left object |
right |
0001 |
Only properties unique to the right object |
innerLeft |
0100 |
Shared properties only, keeping left values |
innerRight |
0010 |
Shared properties only, replacing with right values |
innerJoin |
0110 |
Shared properties, deep-merged (left + right values combined) |
leftJoin |
1110 |
Left-unique + shared properties (deep-merged) |
rightJoin |
0111 |
Shared properties (deep-merged) + right-unique |
fullJoin |
1111 |
All properties from both objects, shared ones deep-merged |
expand |
1011 |
Left-unique + right values for shared + right-unique (default) |
Custom join types can be composed with bitwise OR:
const customJoin = join(JoinTypes.left | JoinTypes.innerLeft | JoinTypes.right);
function join(
joinType?: JoinTypes
): <TContext>(
context: TContext
) => <TJoinObject>(joinObject: TJoinObject) => TContext & TJoinObject;
join uses a curried three-step pattern:
| Step | Call | Description |
|---|---|---|
| 1 | join(JoinTypes.xxx) |
Set the join type; returns a context-setter function |
| 2 | contextSetter(leftObject) |
Set the left object; returns a joiner function |
| 3 | joiner(rightObject) |
Set the right object; returns the merged result |
JoinTypes.expand is the default join type when join() is called with no arguments.
Returns only the properties unique to the right object:
const rightJoin = join(JoinTypes.right);
const contextJoinBy = rightJoin({
sample1: "One",
sample2: "Two",
sample3: "Three"
});
const result = contextJoinBy({
sample2: "Dos",
sample3: "Tres",
sample4: "Quatro"
});
console.log(result); // { sample4: "Quatro" }
Returns only the properties unique to the left object:
const leftJoin = join(JoinTypes.left);
const contextJoinBy = leftJoin({
sample1: "One",
sample2: "Two",
sample3: "Three"
});
const result = contextJoinBy({
sample2: "Dos",
sample3: "Tres",
sample4: "Quatro"
});
console.log(result); // { sample1: "One" }
Returns left-unique + shared (from left) + right-unique properties:
const customJoin = join(JoinTypes.left | JoinTypes.innerLeft | JoinTypes.right);
const contextJoinBy = customJoin({
sample1: "One",
sample2: "Two",
sample3: "Three"
});
const result = contextJoinBy({
sample2: "Dos",
sample3: "Tres",
sample4: "Quatro"
});
console.log(result); // { sample1: "One", sample2: "Two", sample3: "Three", sample4: "Quatro" }
Returns shared properties with their values deep-merged:
const result = join(JoinTypes.innerJoin)({
sample1: "One",
sample2: "Two",
sample3: { smile: "cheese" }
})({
sample2: "Dos",
sample3: { sorrir: "queijo" },
sample4: "Quatro"
});
console.log(result);
// { sample2: "Dos", sample3: { smile: "cheese", sorrir: "queijo" } }
Contributions are welcome! Please read CONTRIBUTING.md before opening a pull request.
Apache-2.0 © 2020–2026 Oleksandr Lopatnov · LinkedIn