Coming from Lodash

Map Lodash functions to @vinicunca/perkakas, with notes where the APIs differ.

Each heading is a Lodash function. The Perkakas equivalent is checked against @vinicunca/perkakas v1.17.1. Names that Remeda documents but Perkakas does not ship are called out. In-page links such as #reverse jump to that Lodash section.

add

Lodash add → add.

assign

Lodash assign → merge.

  • In Lodash the merge is done in-place, on the first argument to the function. In Perkakas a new object is always returned, and none of the input objects are mutated.
  • In Lodash assign can be used to merge any number of objects by accepting a variadic argument list. In Perkakas, if you are merging only 2 objects use merge, but if you need to merge more than 2 objects use mergeAll instead, which takes an Array of objects.
// Lodash
const DATA = { a: 1, b: 2 };
assign(DATA, b);
assign(DATA, b, c, d, e);

// Perkakas
let DATA = { a: 1, b: 2 };
DATA = merge(DATA, b);
DATA = mergeAll([DATA, b, c, d, e]);

camelCase

Lodash camelCase → toCamelCase.

  • Lodash attempts pseudo-linguistic word splitting to handle special characters, which might lead to inaccurate results. Perkakas uses a simpler word splitting approach based on type-fest's definition that should only be used for simple strings like identifiers and internal keys. For linguistic processing where language and locale nuances matter, use the built-in Intl.Segmenter instead.
  • Perkakas treats consecutive uppercase characters differently from Lodash. Use { preserveConsecutiveUppercase: false } as the second parameter to get the same results.
  • Lodash performs normalization on the input before splitting it, including deburr and removing apostrophes. Perkakas's word splitting is simpler and doesn't include these normalizations, so they need to be done manually if required.
  • Lodash allows calling camelCase without any input, or with an undefined input, which isn't supported in Perkakas. Handle these cases before calling the function.

Simple strings

// Lodash
_.camelCase(input);

// Perkakas
toCamelCase(input, { preserveConsecutiveUppercase: false });

Normalized

// Lodash
_.camelCase(input);

// Perkakas + Native
toCamelCase(
  input
    // "Promote" diacritics to be independent characters in the string.
    .normalize('NFD')
    // Remove apostrophes and all independent diacritic characters.
    .replace(/['\u2019\u0300-\u036F]/g, ''),
  { preserveConsecutiveUppercase: false },
);

Missing input

// Lodash
_.camelCase();
_.camelCase(input);

// Perkakas
('');
input !== undefined
  ? toCamelCase(input, { preserveConsecutiveUppercase: false })
  : '';

// Or
toCamelCase(input ?? '', { preserveConsecutiveUppercase: false });

capitalize

Lodash capitalize → capitalize.

Runtime

// Lodash
_.capitalize(input);

// Perkakas
capitalize(toLowerCase(input));

CSS

// Lodash
<div>{_.capitalize(input)}</div>

// CSS
<style>
  .capitalize {
    text-transform: lowercase;
  }

  .capitalize::first-letter {
    text-transform: uppercase;
  }
</style>
<div class="capitalize">{input}</div>

ceil

Lodash ceil → ceil.

chunk

Lodash chunk → chunk.

clamp

Lodash clamp → clamp.

cloneDeep

Lodash cloneDeep → clone.

compact

Lodash compact → filter.

Use isTruthy as the predicate.

// Lodash
compact(DATA);

// Perkakas
filter(DATA, isTruthy);

concat

Lodash concat → concat.

cond

Lodash cond → conditional.

constant

Lodash constant → constant.

countBy

Lodash countBy → countBy.

debounce

Lodash debounce → funnel.

  • debounce can be implemented using the funnel utility. A reference implementation is provided below, and a more expanded version with inline documentation and tests is available in the test file funnel.lodash-debounce.test.ts.
  • A more complete reference implementation that also maintains Lodash's capability to store the callback's return value is available below, and in funnel.lodash-debounce-with-cached-value.test.ts.
  • These implementations can be copied as-is into your project, but might contain redundant parts which are not relevant for your specific use cases. By inlining only the parts you need you can take advantage of capabilities not available in Lodash. To copy the expanded versions from the test files instead, take everything between the REFERENCE START and REFERENCE END markers.

Reference

import { funnel } from '@vinicunca/perkakas';

function debounce<F extends (...args: any) => void>(
  func: F,
  wait = 0,
  {
    leading = false,
    trailing = true,
    maxWait,
  }: {
    readonly leading?: boolean;
    readonly trailing?: boolean;
    readonly maxWait?: number;
  } = {},
) {
  const {
    call,
    isIdle: _isIdle,
    ...rest
  } = funnel(
    () => {
      if (leading || trailing) {
        func();
      }
    },
    {
      minQuietPeriodMs: wait,
      ...(maxWait !== undefined && { maxBurstDurationMs: maxWait }),
      ...(trailing
        ? leading
          ? { triggerAt: 'both' }
          : { triggerAt: 'end' }
        : { triggerAt: 'start' }),
    },
  );
  return Object.assign(call, rest);
}

With call arguments

import { funnel } from '@vinicunca/perkakas';

function debounce<F extends (...args: any) => void>(
  func: F,
  wait = 0,
  {
    leading = false,
    trailing = true,
    maxWait,
  }: {
    readonly leading?: boolean;
    readonly trailing?: boolean;
    readonly maxWait?: number;
  } = {},
) {
  const {
    call,
    isIdle: _isIdle,
    ...rest
  } = funnel(
    (args: Parameters<F>) => {
      if (leading || trailing) {
        func(...args);
      }
    },
    {
      reducer: (_, ...args: Parameters<F>) => args,
      minQuietPeriodMs: wait,
      ...(maxWait !== undefined && { maxBurstDurationMs: maxWait }),
      ...(trailing
        ? leading
          ? { triggerAt: 'both' }
          : { triggerAt: 'end' }
        : { triggerAt: 'start' }),
    },
  );
  return Object.assign(call, rest);
}

With cached value

import { funnel } from '@vinicunca/perkakas';

function debounce<F extends (...args: any) => any>(
  func: F,
  wait = 0,
  {
    leading = false,
    trailing = true,
    maxWait,
  }: {
    readonly leading?: boolean;
    readonly trailing?: boolean;
    readonly maxWait?: number;
  } = {},
) {
  let cachedValue: ReturnType<F> | undefined;

  const { call, flush, cancel } = funnel(
    (args: Parameters<F>) => {
      if (leading || trailing) {
        cachedValue = func(...args) as ReturnType<F>;
      }
    },
    {
      reducer: (_, ...args: Parameters<F>) => args,
      minQuietPeriodMs: wait,
      ...(maxWait !== undefined && { maxBurstDurationMs: maxWait }),
      ...(trailing
        ? leading
          ? { triggerAt: 'both' }
          : { triggerAt: 'end' }
        : { triggerAt: 'start' }),
    },
  );
  return Object.assign(
    (...args: Parameters<F>) => {
      call(...args);
      return cachedValue;
    },
    {
      flush: () => {
        flush();
        return cachedValue;
      },

      cancel,
    },
  );
}

deburr

No direct Perkakas helper. Use the notes below, or native JavaScript.

Not provided by Perkakas.

deburr uses hard-coded heuristics which can lead to inaccurate results:

  • Misses some accented characters (e.g., ḩ, ố, ῷ and й).
  • Erroneously "simplifies" ligatures that don't formally have any diacritics that require "deburring" (e.g., Æ, IJ, Œ, and ʼn).
  • Swaps characters because they look similar although they are semantically unrelated to each other (e.g., Ð, Ø, Þ, ß, ſ).

We strongly recommend using linguistically-aware alternatives instead that are purpose-built for this task:

  • When comparing strings, prefer Intl.Collator with sensitivity: 'base' to compare them while ignoring accents and case.
  • When simplifying strings, prefer normalize("NFD") to decompose characters, then use replace to remove diacritics. This solution is more accurate, but is not a drop-in replacement for deburr because of the differences mentioned above.
  • When querying for a string, prefer purpose built collations and other built-in mechanisms of databases and other systems that offer more robust query normalization.

Comparison

// Lodash
_.deburr('café') === 'cafe';

// Native JS
const collator = new Intl.Collator('en', { sensitivity: 'base' });
collator.compare('café', 'cafe') === 0;

Simplifying

// Lodash
_.deburr(input);

// Native JS
input
  .normalize('NFD')
  .replace(
    /[\u0300-\u036F]/g /* The "Combining Diacritical Marks" Unicode block. */,
    '',
  );

defaultTo

Lodash defaultTo → defaultTo.

  • Number.NaN is not considered nullish and would not result in returning the default value. Use when with the built-in Number.isNaN and a constant for the fallback value.
  • Unlike in Perkakas, Lodash allows the fallback value to be of any type, even those that are incompatible with the data type. It also allows the data type to be non-nullish; for those cases use the built-in Nullish coalescing operator ?? directly, or use when with isNullish and constant.

Nullish

const DATA: number | undefined | null;

// Lodash
_.defaultTo(DATA, 456);

// Perkakas
defaultTo(DATA, 456);

NaN

const DATA = Number.NaN;

// Lodash
_.defaultTo(DATA, 10);

// Perkakas
when(DATA, Number.isNaN, constant(10));

Both

const DATA: number | null | undefined;

// Lodash
_.defaultTo(DATA, 10);

// Perkakas
when(
  DATA,
  (x) => x === undefined || x === null || Number.isNaN(x),
  constant(10),
);

Non-matching fallback

const DATA: string | null | undefined;

// Lodash
_.defaultTo(DATA, 123);

// Perkakas
when(DATA, isNullish, constant(123));

// or natively
DATA ?? 123;

difference

Lodash difference → difference.

  • Perkakas's difference treats the inputs as multisets/bags which respect item duplication, whereas in Lodash all matching values are filtered out. This means Perkakas removes only one copy of each matching item, while Lodash removes all copies.
  • When the input arrays might contain duplicates and you want to filter all values use a composition of filter, isNot, and isIncludedIn to replicate the Lodash semantics.
  • Lodash's difference accepts multiple values arrays as separate arguments and flattens them, while Perkakas's difference takes a single values array. When migrating these arrays need to be flattened or spread into a single array manually.
  • Lodash supports calling difference trivially, with no exclusion array at all which results in a shallow clone of the input array. In Perkakas the exclusion array is required.
  • Lodash accepts null and undefined values for the array (and treats them as an empty array). In Perkakas this nullish value needs to be handled explicitly either by skipping the call to difference, or by coalescing the input to an empty array.

Without duplicate values (unique)

// Lodash
_.difference(uniqueData, uniqueValues);

// Perkakas
difference(uniqueData, uniqueValues);

With duplicate values

// Lodash
_.difference(dataWithDuplicates, uniqueValues);
_.difference(uniqueData, valuesWithDuplicates);
_.difference(dataWithDuplicates, valuesWithDuplicates);

// Perkakas
filter(dataWithDuplicates, isNot(isIncludedIn(uniqueValues)));
filter(uniqueData, isNot(isIncludedIn(valuesWithDuplicates)));
filter(dataWithDuplicates, isNot(isIncludedIn(valuesWithDuplicates)));

// valuesWithDuplicates doesn't need to be deduped when used inside
// `isIncludedIn`, but can be for efficiency if needed via `unique`
filter(dataWithDuplicates, isNot(isIncludedIn(unique(valuesWithDuplicates))));

Multiple exclusion arrays

// Lodash
_.difference(DATA, a, b, c);

// Perkakas
difference(DATA, [...a, ...b, ...c]);

Missing 2nd (exclusions) parameter

// Lodash
_.difference(DATA);

// Perkakas
difference(DATA, []);

// Or directly via Native JS:
[...DATA];

Nullish inputs

// Lodash
_.difference(DATA, values);

// Perkakas
isNonNullish(DATA) ? difference(DATA, values) : [];

// Or
difference(DATA ?? [], values);

differenceBy

Lodash differenceBy → filter.

Not provided by Perkakas.

  • Use a composition based on filter, isNotisIncludedIn, and map to recreate the logic for differenceBy.
  • Additionally, when the iteratee parameter is defined as a property name, use prop as the iteratee function instead.
  • Lodash accepts null and undefined values for the array (and treats them as an empty array). In Perkakas this nullish value needs to be handled explicitly either by skipping the call to differenceBy, or by coalescing the input to an empty array.
  • When the iteratee parameter is not provided to the Lodash differenceBy function (or is provided as undefined) it behaves like a call to difference.

Function iteratee

// Lodash
_.differenceBy(DATA, values, iteratee);

// Perkakas
filter(DATA, piped(iteratee, isNot(isIncludedIn(map(values, iteratee)))));

// the mapped values don't need to be deduped when used inside `isIncludedIn`,
// but could be for efficiency if needed via `unique`
filter(
  DATA,
  piped(iteratee, isNot(isIncludedIn(unique(map(values, iteratee))))),
);

Property iteratee

// Lodash
_.differenceBy(DATA, values, 'x');

// Perkakas
filter(DATA, piped(prop('x'), isNot(isIncludedIn(map(values, prop('x'))))));

// the mapped values don't need to be deduped when used inside `isIncludedIn`,
// but could be for efficiency if needed via `unique`
filter(
  DATA,
  piped(prop('x'), isNot(isIncludedIn(unique(map(values, prop('x')))))),
);

Multiple exclusion arrays

// Lodash
_.differenceBy(DATA, a, b, c, iteratee);

// Perkakas
filter(
  DATA,
  piped(iteratee, isNot(isIncludedIn(map([...a, ...b, ...c], iteratee)))),
);

// the mapped values don't need to be deduped when used inside `isIncludedIn`,
// but could be for efficiency if needed via `unique`
filter(
  DATA,
  piped(
    iteratee,
    isNot(isIncludedIn(unique(map([...a, ...b, ...c], iteratee)))),
  ),
);

Nullish inputs

// Lodash
_.differenceBy(DATA, values, iteratee);

// Perkakas
isNonNullish(DATA)
  ? filter(DATA, piped(iteratee, isNot(isIncludedIn(map(values, iteratee)))))
  : [];

// Or
filter(DATA ?? [], piped(iteratee, isNot(isIncludedIn(map(values, iteratee)))));

Missing iteratee function

// Lodash
_.differenceBy(DATA, values);

// Convert to Lodash's `difference` and then refer to the `difference` migration
// docs
_.difference(DATA, values);

differenceWith

Lodash differenceWith → differenceWith.

  • Lodash's differenceWith accepts a variadic list of values arrays which are then effectively flattened, but Perkakas only accepts a single values array; Multiple arrays need to be spread manually instead.
  • Lodash accepts null and undefined values for the array (and treats them as an empty array). In Perkakas this nullish value needs to be handled explicitly either by skipping the call to differenceWith, or by coalescing the input to an empty array.
  • When the comparator parameter is not provided to the Lodash differenceWith function (or is provided as undefined) it behaves like a call to difference.

Single exclusion array

// Lodash
_.differenceWith(DATA, values, comparator);

// Perkakas
differenceWith(DATA, values, comparator);

Multiple exclusion arrays

// Lodash
_.differenceWith(DATA, a, b, c, comparator);

// Perkakas
differenceWith(DATA, [...a, ...b, ...c], comparator);

Nullish inputs

// Lodash
_.differenceWith(DATA, values, comparator);

// Perkakas
isNonNullish(DATA) ? differenceWith(DATA, values, comparator) : [];

// Or
differenceWith(DATA ?? [], values, comparator);

Missing iteratee function

// Lodash
_.differenceWith(DATA, values);

// Convert to Lodash's `difference` and then refer to the `difference` migration
// docs
_.difference(DATA, values);

divide

Lodash divide → divide.

drop

Lodash drop → drop.

dropRight

Lodash dropRight → dropLast.

dropRightWhile

Lodash dropRightWhile → dropLastWhile.

dropWhile

Lodash dropWhile → dropWhile.

endsWith

Lodash endsWith → endsWith.

  • Lodash supports an optional position parameter to check only part of the string, while Perkakas doesn't support this parameter. To replicate this behavior, use sliceString to truncate the string before checking.
  • Lodash allows the suffix to be undefined (converts to "undefined" string) but this is not supported by Perkakas and needs to be handled explicitly.

Basic usage

// Lodash
_.endsWith(input, suffix);

// Perkakas
endsWith(input, suffix);

// Native
input.endsWith(suffix);

Position parameter

// Lodash
_.endsWith(input, suffix, position);

// Perkakas
endsWith(sliceString(input, 0, position), suffix);

// Native
input.endsWith(suffix, position);

Undefined suffix

// Lodash
_.endsWith(input, suffix);

// Perkakas
suffix !== undefined && endsWith(input, suffix);

escape

No direct Perkakas helper. Use the notes below, or native JavaScript.

Not provided by Perkakas.

escape is not needed in the vast majority of modern web development - all modern frameworks and tools perform this kind of escaping (and more!) automatically wherever it matters. If you are working on a low-level application that writes directly to the DOM, prefer libraries that take a holistic approach to this problem (like DOMPurify) instead of basic string substitutions. In the rare case you must have an escaping function, copy the reference code below as-is:

const ESCAPED: Readonly<Record<string, `&${string};`>> = {
  // https://github.com/lodash/lodash/blob/main/lodash.js#L399-L403
  '&': '&amp;',
  '<': '&lt;',
  '>': '&gt;',
  '"': '&quot;',
  '\'': '&#39;',
};

const CHARS_RE = new RegExp(`[${Object.keys(ESCAPED).join('')}]`, 'g');

function escape(input: string): string {
  return input.replace(CHARS_RE, (char) => ESCAPED[char]!);
}

escapeRegExp

No direct Perkakas helper. Use the notes below, or native JavaScript.

Not provided by Perkakas.

  • Use the native RegExp.escape instead.
  • When unavailable, escapeRegExp could also be replicated using String.prototype.replace to replace matches of /[\\^$.*+?()[\]{}|]/g (which matches on the characters: ^, $, ., *, +, ?, (), {}, , and |) with a backslash-escaped version of the match (using the "\\$&" replacement pattern).
// Lodash
_.escapeRegExp(input);

// Native
RegExp.escape(input);

// Or
input.replace(/[\\^$.*+?()[\]{}|]/g, '\\$&');

every

No direct Perkakas helper. Use the notes below, or native JavaScript.

Not provided by Perkakas.

Use the native JS Array.prototype.every instead.

@see "You don't need Lodash"

// Lodash
every(DATA, predicate);

// Native
DATA.every(predicate);

filter

Lodash filter → filter.

find

Lodash find → find.

findIndex

Lodash findIndex → findIndex.

findLast

Lodash findLast → findLast.

findLastIndex

Lodash findLastIndex → findLastIndex.

flatMap

Lodash flatMap → flatMap.

flatMapDeep

No direct Perkakas helper. Use the notes below, or native JavaScript.

Not provided by Perkakas.

This function could be recreated in Perkakas via a composition of map, and flat with a large, const depth parameter.

// Lodash
flatMapDeep(DATA, mappingFunction);

// Perkakas
flat(map(DATA, mappingFunction), 10 /* depth */);

// Or as a pipe
pipe(DATA, map(mappingFunction), flat(10 /* depth */));

flatMapDepth

No direct Perkakas helper. Use the notes below, or native JavaScript.

Not provided by Perkakas.

This function could be recreated in Perkakas via map, flatMap, or flat (or a combination of them, depending on the parameters used).

// Lodash
flatMapDepth(DATA);

// Perkakas
flat(DATA);
// Lodash
flatMapDepth(DATA, mappingFunction);

// Perkakas
flatMap(DATA, mappingFunction);
// Lodash
flatMapDepth(DATA, mappingFunction, depth);

// Perkakas
flat(map(DATA, mappingFunction), depth);

// Or as a pipe
pipe(DATA, map(mappingFunction), flat(depth));

flatten

Lodash flatten → flat.

Either use with no params, or with a depth of 1.

// Lodash
flatten(DATA);

// Perkakas
flat(DATA);
flat(DATA, 1 /* depth */);

flattenDeep

Lodash flattenDeep → flat.

Unlike flatten in Lodash, the Perkakas flat function is always bound by the depth param. To replicate the Lodash behavior use a high, const value. Infinity and Number.MAX_INTEGER are not consts and would result in inefficient typing.

// Lodash
flattenDeep(DATA);

// Perkakas
flat(DATA, 100); // ✅
flat(DATA, Infinity); // ❌

floor

Lodash floor → floor.

flow

Lodash flow → pipe.

forEach

Lodash forEach → forEach.

  • In Lodash forEach first checks if the data argument is array-like (by looking for a length prop). In Perkakas the function only accepts arrays and calling it with an object would result in a TypeScript error. Use forEachObj instead for those cases.
  • In Lodash forEach stops iterating early when the callback returns false. In Perkakas the callback cannot return a value (it must return void) and this feature isn't available. You can build a pipe that would replicate this logic.

Array

// Lodash
forEach([1, 2, 3], (item) => console.log(item));

// Perkakas
forEach([1, 2, 3], (item) => {
  console.log(item);
});

Object

// Lodash
forEach({ a: 1, b: 2, c: 3 }, (value) => console.log(value));

// Perkakas
forEachObj({ a: 1, b: 2, c: 3 }, (value) => {
  console.log(value);
});

Early exit

// Lodash
forEach([1, 2, 3], (item) => (item === 2 ? false : console.log(item)));

// Perkakas
// ❌ You can't return a value in a forEach callback in Perkakas:
forEach([1, 2, 3], (item) => (item === 2 ? false : console.log(item)));

// ✅ Instead you can rely on lazy evaluation of pipes:
pipe(
  [1, 2, 3],
  takeWhile((item) => item !== 2),
  forEach((item) => {
    console.log(item);
  }),
);

forEachRight

No direct Perkakas helper. Use the notes below, or native JavaScript.

Not provided by Perkakas.

  • This function could be replicated using a native JS for loop.
  • If the order of iteration doesn't matter, you can use forEach for arrays, or forEachObj for objects.
  • The order in which object properties are iterated over is well-defined, but might not be the order you expect (MDN); nevertheless, entries maintains the same order, and could be used to replicate forEachRight.
  • If the native solution doesn't suffice please open an issue at Perkakas's GitHub project so we can learn about your use-case.

Arrays

const DATA = [1, 2, 3];

// Lodash
forEachRight(DATA, (item) => (item === 2 ? false : console.log(item)));

// Native
for (const index = DATA.length - 1; index >= 0; index--) {
  const item = DATA[item]!;
  if (item === 2) {
    break;
  }

  console.log(item);
}

Objects

const DATA = { a: 1, b: 2, c: 3 };

// Lodash
forEachRight(DATA, (value) => (value === 2 ? false : console.log(value)));

// Native
const keys = Object.keys(DATA);
for (const index = keys.length - 1; index >= 0; index--) {
  const value = DATA[key]!;
  if (value === 2) {
    break;
  }

  console.log(value);
}

forOwn

Lodash forOwn → forEachObj.

fromPairs

Lodash fromPairs → fromEntries.

get

Lodash get → prop.

  • In Lodash get takes an array where each element is a property in the path to a deeply-nested property (e.g. ['a', 'b', '0', 'c']). In Perkakas the path is provided directly as a variadic arguments list.
  • In Lodash all property keys are string, even when accessing numbers. In Perkakas the exact type of each prop should be used instead (e.g., number when accessing arrays).
  • Lodash also supports a string that defines the path to the deeply-nested property (e.g., a.b[0].c). In Perkakas this isn't supported directly; instead, use the helper function stringToPath to convert the path string to a path array, which could then be spread into prop.
  • Lodash supports an optional last argument to get which defines a default fallback in cases where the prop being accessed does not exist on the input object. To achieve this wrap your call to prop with a call to defaultTo, or use the native Nullish coalescing operator ?? instead if the fallback is of a different type.

Shallow Access

const DATA = { a: 123 };

// Lodash
_.get(DATA, ['a']);

// Perkakas
prop(DATA, 'a');

Array indices

const DATA = [1, 2, 3];

// Lodash
_.get(DATA, ['0']);

// Perkakas
prop(DATA, 0);

Path Arrays

const DATA = { a: [{ b: { c: 3 } }] };

// Lodash
_.get(DATA, ['a', '0', 'b', 'c']);

// Perkakas
prop(DATA, 'a', 0, 'b', 'c');

Path Strings

const DATA = { a: [{ b: { c: 3 } }] };

// Lodash
_.get(DATA, 'a[0].b.c');

// Perkakas
prop(DATA, ...stringToPath('a[0].b.c'));

Default Fallback

const DATA = { a: [] as Array<{ b: number }> };

// Lodash
_.get(DATA, ['a', '0', 'b'], 'default');

// Perkakas
defaultTo(prop(DATA, 'a', 0, 'b'), 123);
// Or natively
prop(DATA, 'a', 0, 'b') ?? 'default';

groupBy

Lodash groupBy → groupBy.

  • When using groupBy with a callback function use Perkakas's groupBy function.
  • When using groupBy on an array of objects and with a property name use Perkakas's groupByProp instead.
  • When using groupBy on an array of primitives (e.g., string) and with a property name use Perkakas's groupBy composed with prop.

Callback

const DATA = [6.1, 4.2, 6.3];

// Lodash
groupBy(DATA, Math.floor);

// Perkakas
groupBy(DATA, Math.floor);

Property Name (on objects)

const DATA = [
  { type: 'cat', name: 'felix' },
  { type: 'dog', toys: 3 },
];

// Lodash
groupBy(DATA, 'type');

// Perkakas
groupByProp(DATA, 'type');

Property Name (on primitives)

const DATA = ['one', 'two', 'three'];

// Lodash
groupBy(DATA, 'length');

// Perkakas
groupBy(DATA, prop('length'));

has

Lodash has → hasProp.

  • Lodash's has accepts deep paths ("a.b.c" or ["a", "b", "c"]). Perkakas's hasProp only checks a single key.
// Lodash
has(obj, 'a');

// Perkakas
hasProp(obj, 'a');

hasIn

Lodash hasIn → hasProp.

  • Lodash's hasIn uses the in operator which returns true for inherited properties. Perkakas uses Object.hasOwn which ignores them.
  • Lodash's hasIn accepts deep paths ("a.b.c" or ["a", "b", "c"]). Perkakas's hasProp only checks a single key.

Own properties

// Lodash
hasIn(obj, 'a');

// Perkakas
hasProp(obj, 'a');

Inherited properties

// Lodash
hasIn({}, 'toString'); // => true

// Perkakas
hasProp({}, 'toString'); // => false

Lodash head → first.

identity

Lodash identity → identity.

In Perkakas the identity function is a factory that creates an identity function; it needs to be called to be used.

// Lodash
map(identity);

// Perkakas
map(identity());

includes

Lodash includes → isIncludedIn.

  • In Lodash includes also works on strings and objects; In Perkakas only arrays are supported. For objects, use values first. For strings prefer the native JS String.prototype.includes.
  • The 3rd optional parameter fromIndex isn't supported in Perkakas. You can replicate it's usage with drop (for non-negative indices), or takeLast (for non-positive indices).
  • The order of the parameters is flipped in Perkakas, the item is first, and then the array.

Arrays

// Lodash
includes([1, 2, 3], 1);

// Perkakas
isIncludedIn(1, [1, 2, 3]);

Objects

// Lodash
includes({ a: 1, b: 2 }, 1);

// Perkakas
isIncludedIn(1, values({ a: 1, b: 2 }));

Strings

const DATA = 'Hello, World!';

// Lodash
includes(DATA, 'lo');

// Native
DATA.includes('lo');

fromIndex: non-negative

// Lodash
includes([1, 2, 3], 1, 2);

// Perkakas
isIncludedIn(1, drop([1, 2, 3], 2));

fromIndex: non-positive

// Lodash
includes([1, 2, 3], 1, -2);

// Perkakas
isIncludedIn(1, takeLast([1, 2, 3], 2));

intersection

Lodash intersection → intersection.

In Perkakas intersection treats the inputs as multisets/bags which respect item duplication, whereas in Lodash the result is always de-dupped. This only matters for cases where both arrays might have duplicate values. To achieve the same output pass the result to unique.

No duplicates

// Lodash
intersection([2, 1], [2, 3]);

// Perkakas
intersection([2, 1], [2, 3]);

With duplicates

// Lodash
intersection([1, 1], [1, 1]);

// Perkakas
unique(intersection([1, 1], [1, 1]));

intersectionWith

Lodash intersectionWith → intersectionWith.

invert

Lodash invert → invert.

invokeMap

Lodash invokeMap → map.

  • map can be used instead of invokeMap by using a callback functions instead of (string) function names.
  • If you need to invoke functions dynamically via a provided function name, narrow the name first (so you can handle errors explicitly), and then use the name to access the object.

No arguments

// Lodash
invokeMap(
  [
    [5, 1, 7],
    [3, 2, 1],
  ],
  'sort',
);

// Perkakas
map(
  [
    [5, 1, 7],
    [3, 2, 1],
  ],
  (array) => array.sort(),
);

With arguments

// Lodash
invokeMap([123, 456], String.prototype.split, '');

// Perkakas
map([123, 456], (num) => num.toString().split(''));

Dynamic

const DATA = [
  { foo: (x: number) => x + 1, bar: (x: number) => x - 1 },
  { foo: (x: number) => x * 2, bar: (x: number) => x / 2 },
] as const;

// Lodash
invokeMap(DATA, funcName, 3);

// Perkakas
if (funcName === 'foo' || funcName === 'bar') {
  map(DATA, ({ [funcName]: func }) => func(3));
} else {
  // Error!
}

// Or
map(DATA, (obj) => {
  if (funcName === 'foo' || funcName === 'bar') {
    return obj[funcName](3);
  } else {
    // Error
  }
});

isArray

Lodash isArray → isArray.

isBoolean

Lodash isBoolean → isBoolean.

isDate

Lodash isDate → isDate.

isEmpty

Lodash isEmpty → isEmptyish.

isEqual

Lodash isEqual → isDeepEqual.

isError

Lodash isError → isError.

isFunction

Lodash isFunction → isFunction.

isMatch

Lodash isMatch → hasSubObject.

isNil

Lodash isNil → isNullish.

isNumber

Lodash isNumber → isNumber.

isObjectLike

Lodash isObjectLike → isObjectType.

isPlainObject

Lodash isPlainObject → isPlainObject.

isString

Lodash isString → isString.

isSymbol

Lodash isSymbol → isSymbol.

join

Lodash join → join.

kebabCase

Lodash kebabCase → toKebabCase.

  • Lodash attempts pseudo-linguistic word splitting to handle special characters, which might lead to inaccurate results. Perkakas uses a simpler word splitting approach based on type-fest's definition that should only be used for simple strings like identifiers and internal keys. For linguistic processing where language and locale nuances matter, use the built-in Intl.Segmenter instead.
  • Lodash performs normalization on the input before splitting it, including deburr and removing apostrophes. Perkakas's word splitting is simpler and doesn't include these normalizations, so they need to be done manually if required.
  • Lodash allows calling kebabCase without any input, or with an undefined input, which isn't supported in Perkakas. Handle these cases before calling the function.

Simple strings

// Lodash
_.kebabCase(input);

// Perkakas
toKebabCase(input);

Normalized

// Lodash
_.kebabCase(input);

// Perkakas + Native
toKebabCase(
  input
    // "Promote" diacritics to be independent characters in the string.
    .normalize('NFD')
    // Remove apostrophes and all independent diacritic characters.
    .replace(/['\u2019\u0300-\u036F]/g, ''),
);

Missing input

// Lodash
_.kebabCase();
_.kebabCase(input);

// Perkakas
('');
input !== undefined ? toKebabCase(input) : '';

// Or
toKebabCase(input ?? '');

keyBy

Lodash keyBy → indexBy.

keys

Lodash keys → keys.

last

Lodash last → last.

lowerCase

Lodash lowerCase → toTitleCase.

  • Use toLowerCase on the results of toTitleCase, which reformats the string into space-delimited words.
  • Lodash attempts pseudo-linguistic word splitting to handle special characters, which might lead to inaccurate results. Perkakas uses a simpler word splitting approach based on type-fest's definition that should only be used for simple strings like identifiers and internal keys. For linguistic processing where language and locale nuances matter, use the built-in Intl.Segmenter instead.
  • Lodash performs normalization on the input before splitting it, including deburr and removing apostrophes. Perkakas's word splitting is simpler and doesn't include these normalizations, so they need to be done manually if required.
  • Lodash allows calling lowerCase without any input, or with an undefined input, which isn't supported in Perkakas. Handle these cases before calling the function.

Basic usage

// Lodash
_.lowerCase(input);

// Perkakas
toLowerCase(toTitleCase(input));

Normalized

// Lodash
_.lowerCase(input);

// Perkakas + Native
toLowerCase(
  toTitleCase(
    input
      // "Promote" diacritics to be independent characters in the string.
      .normalize('NFD')
      // Remove apostrophes and all independent diacritic characters.
      .replace(/['\u2019\u0300-\u036F]/g, ''),
  ),
);

Missing input

// Lodash
_.lowerCase();
_.lowerCase(input);

// Perkakas
('');
input !== undefined ? toLowerCase(toTitleCase(input)) : '';

// Or
toLowerCase(toTitleCase(input ?? ''));

lowerFirst

Lodash lowerFirst → uncapitalize.

  • For display purposes, prefer using CSS; use ::first-letter to target just the first letter of the word and text-transform: lowercase to lowercase it.
  • Lodash allows calling lowerFirst without any input (or with an undefined input), which results in an empty string "". Perkakas's uncapitalize requires a string input.

Basic usage

// Lodash
_.lowerFirst(input);

// Perkakas
uncapitalize(input);

CSS

// Lodash
<div>{_.lowerFirst(input)}</div>

// CSS
<style>
  .lowerFirst::first-letter {
    text-transform: lowercase;
  }
</style>
<div class="lowerFirst">{input}</div>

Missing input data

// Lodash
_.lowerFirst();
_.lowerFirst(input);

// Perkakas
input !== undefined ? uncapitalize(input) : '';

// Or
uncapitalize(input ?? '');

map

Lodash map → map.

mapKeys

Lodash mapKeys → mapKeys.

mapValues

Lodash mapValues → mapValues.

max

Lodash max → firstBy.

Use identity as the criteria and "desc" as the direction.

// Lodash
max(DATA);

// Perkakas
firstBy(DATA, [identity(), 'desc']);

maxBy

Lodash maxBy → firstBy.

Use "desc" as the direction.

// Lodash
maxBy(DATA, criteria);

// Perkakas
firstBy(DATA, [criteria, 'desc']);

mean

Lodash mean → mean.

meanBy

Lodash meanBy → meanBy.

min

Lodash min → firstBy.

Use identity as the criteria.

// Lodash
min(DATA);

// Perkakas
firstBy(DATA, identity());

minBy

Lodash minBy → firstBy.

multiply

Lodash multiply → multiply.

negate

Lodash negate → isNot.

  • Unlike Lodash's negate, isNot preserves type predicates: negating a type-guard returns a type-guard which excludes the guarded type, so the result of filter stays narrowed.
  • Lodash's negate forwards every argument it receives to the predicate, and invokes it with the same this. isNot only takes a single-parameter predicate and passes just the first argument through; wrap multi-parameter functions with the native logical NOT operator ! instead.
  • Lodash's negate negates the truthiness of any return value, whereas isNot requires a predicate that returns a boolean. Compose with isTruthy to get the Lodash semantics.

Predicates

// Lodash
_.negate(predicate);

// Perkakas
isNot(predicate);

Filtering

// Lodash
_.filter(DATA, _.negate(_.isString));

// Perkakas
filter(DATA, isNot(isString));

Multiple parameters

// Lodash
_.negate(isGreaterThan);

// Native
(a: number, b: number) => !isGreaterThan(a, b);

Non-boolean predicates

// Lodash
_.negate(getLength);

// Perkakas
isNot(piped(getLength, isTruthy));

// Or directly via Native JS:
(data: string) => !getLength(data);

noop

Lodash noop → doNothing.

  • If a return value is needed, use constant with undefined.
  • Otherwise, use doNothing (which returns void).
// Lodash
noop;

// Perkakas
constant(undefined);
doNothing();

nth

No direct Perkakas helper. Use the notes below, or native JavaScript.

Not provided by Perkakas.

  • For index 0 use first.
  • For index -1 use last.
  • For arbitrary non-negative indices use the native JS data[n].
  • Or use Array.prototype.at for any index.
// Lodash
nth(DATA);
nth(DATA, 0);

// Perkakas
first(DATA);

// Lodash
nth(DATA, 1);

// Native
DATA[1];
DATA.at(1);

// Lodash
nth(DATA, -1);

// Perkakas
last(DATA);

// Lodash
nth(DATA, -2);

// Native
DATA.at(-2);

omit

Lodash omit → omit.

omitBy

Lodash omitBy → omitBy.

once

Lodash once → once.

orderBy

Lodash orderBy → sortBy.

Unlike in Lodash, in Perkakas the sort criteria can only be a callback (and not a prop name), it is paired with the order modifier ("asc"/"desc", if needed), and it's provided as a variadic argument and not two array arguments.

const DATA = [
  { user: 'fred', age: 48 },
  { user: 'barney', age: 34 },
  { user: 'fred', age: 40 },
  { user: 'barney', age: 36 },
];

// Lodash
orderBy(DATA, ['user', 'age'], ['asc', 'desc']);

// Perkakas
sortBy(DATA, prop('user'), [prop('age'), 'desc']);

pad

No direct Perkakas helper. Use the notes below, or native JavaScript.

Not provided by Perkakas.

  • If pad is used with whitespace padding (which is also the default) in order to center text within an HTML component, prefer using CSS instead. Use width with a length in ch units, and text-align: center.
  • For other uses pad could be replicated via padStart and padEnd, by computing the amount of effective padding you need on one side.

CSS

// Lodash
<pre>{_.pad(input, 20)}</pre>

// CSS
<div style="text-align:center;width:20ch">{input}</div>

Reference Implementation

function pad(data: string, length: number, padding = ' ') {
  return data.padStart((length + data.length) / 2, padding).padEnd(length, padding);
}

padEnd

No direct Perkakas helper. Use the notes below, or native JavaScript.

Not provided by Perkakas.

  • Use native String.prototype.padEnd instead.
  • If padEnd is used with whitespace padding (which is also the default) in order to position text within an HTML component, prefer using CSS instead. Use width with a length in ch units, and text-align: left.

Basic Usage

// Lodash
_.padEnd(input, n);
_.padEnd(input, n, padding);

// Native
input.padEnd(n);
input.padEnd(n, padding);

CSS

// Lodash
<pre>{_.padEnd(input, 20)}</pre>

// CSS
<div style="text-align:left;width:20ch">{input}</div>

padStart

No direct Perkakas helper. Use the notes below, or native JavaScript.

Not provided by Perkakas.

  • Use native String.prototype.padStart instead.
  • If padStart is used with whitespace padding (which is also the default) in order to position text within an HTML component, prefer using CSS instead. Use width with a length in ch units, and text-align: right.

Basic Usage

// Lodash
_.padStart(input, n);
_.padStart(input, n, padding);

// Native
input.padStart(n);
input.padStart(n, padding);

CSS

// Lodash
<pre>{_.padStart(input, 20)}</pre>

// CSS
<div style="text-align:right;width:20ch">{input}</div>

parseInt

No direct Perkakas helper. Use the notes below, or native JavaScript.

Not provided by Perkakas.

  • Use native Number.parseInt instead.
  • When used as a callback, parseInt protects against the index parameter unintentionally redefining the radix. To avoid this issue, wrap the callback with an arrow function.

Basic usage

// Lodash
_.parseInt(input);
_.parseInt(input, radix);

// Native
Number.parseInt(input);
Number.parseInt(input, radix);

Callback

// Lodash
data.map(_.parseInt);

// Native
data.map((item) => Number.parseInt(item, 10)); // ✅
data.map(Number.parseInt); // ❌

partial

Lodash partial → partialBind.

The placeholder argument _ is not supported. Some cases can be written as partialLastBind; otherwise, use an arrow function.

function greet(greeting, firstName, lastName) {
  return `${greeting} ${firstName} ${lastName}`;
}

// Lodash
_.partial(greet, _, 'john', 'doe');
_.partial(greet, 'hi', _, 'doe');

// Perkakas
partialLastBind(greet, 'john', 'doe');
// (not supported)

// Native
(greeting) => greet(greeting, 'john', 'doe');
(firstName) => greet('hi', firstName, 'doe');

partialRight

Lodash partialRight → partialLastBind.

The placeholder argument _ is not supported. Some cases can be written as partialBind; otherwise, use an arrow function.

function greet(greeting, firstName, lastName) {
  return `${greeting} ${firstName} ${lastName}`;
}

// Lodash
_.partialRight(greet, 'hi', 'john', _);
_.partialRight(greet, 'hi', _, 'doe');

// Perkakas
partialBind(greet, ['hi', 'john']);
// (not supported)

// Native
(lastName) => greet('hi', 'john', lastName);
(firstName) => greet('hi', firstName, 'doe');

partition

Lodash partition → partition.

pick

Lodash pick → pick.

pickBy

Lodash pickBy → pickBy.

pull

No direct Perkakas helper. Use the notes below, or native JavaScript.

Not provided by Perkakas.

CAUTION: pull mutates the input array in-place! All functions in Perkakas are pure and never mutate the input. These mutations might introduce side-effects and implicit dependencies in your codebase that need to be handled before migrating to Perkakas!

  • pull is equivalent to Lodash's difference function. To migrate to Perkakas first migrate calls to Lodash _.difference and then use the migration docs for difference to complete the migration. IMPORTANT: The Perkakas difference function isn't a drop-in replacement for the Lodash _.difference function, we do not recommend migrating directly from pull to Perkakas's difference.
  • pull takes a variadic array of items to remove; difference take an explicit array instead. You will need to wrap your items in an array when migrating.
  • If the mutability of the input array is desired, make sure the variable is assignable (e.g., using let instead of const), and assign back the result of difference back to it. Note that if the input array is part of an object or nested array, you will need to manually reconstruct this outer containing object with the updated array manually.
  • If mutability wasn't desired, and instead the input was cloned (shallowly) before calling pull, that cloning is now redundant.

Exclusion Items

// pull
_.pull(DATA, a, b, c, ...additional);

// difference
_.difference(DATA, [a, b, c, ...additional]);

In-place mutation

// pull
const DATA = ['a', 'b', 'c', 'd'];
_.pull(DATA, ...values);

// difference
let DATA = ['a', 'b', 'c', 'd'];
DATA = _.difference(DATA, values);

Non-mutating usage

// pull
const pulled = _.pull([...DATA], ...values);

// difference
const pulled = _.difference(DATA, values);

Nested inside an object

// pull
const DATA = { a: ['a', 'b', 'c'], b: 'hello, world' };
_.pull(DATA.a, ...values);

// difference
let DATA = { a: ['a', 'b', 'c'], b: 'hello, world' };
DATA = { ...DATA, a: _.difference(DATA.a, values) };

pullAll

No direct Perkakas helper. Use the notes below, or native JavaScript.

Not provided by Perkakas.

CAUTION: pullAll mutates the input array in-place! All functions in Perkakas are pure and never mutate the input. These mutations might introduce side-effects and implicit dependencies in your codebase that need to be handled before migrating to Perkakas!

  • pullAll is equivalent to Lodash's difference function. To migrate to Perkakas first migrate calls to Lodash _.difference and then use the migration docs for difference to complete the migration. IMPORTANT: The Perkakas difference function isn't a drop-in replacement for the Lodash _.difference function, we do not recommend migrating directly from pullAll to Perkakas's difference.
  • If the mutability of the input array is desired, make sure the variable is assignable (e.g., using let instead of const), and assign back the result of difference back to it. Note that if the input array is part of an object or nested array, you will need to manually reconstruct this outer object with the updated array manually.
  • If mutability wasn't desired, and instead the input was cloned (shallowly) before calling pullAll, that cloning is now redundant.

In-place mutation

// pullAll
const DATA = ['a', 'b', 'c', 'd'];
_.pullAll(DATA, values);

// difference
let DATA = ['a', 'b', 'c', 'd'];
DATA = _.difference(DATA, values);

Non-mutating usage

// pullAll
const pulled = _.pullAll([...DATA], values);

// difference
const pulled = _.difference(DATA, values);

Nested inside an object

// pullAll
const DATA = { a: ['a', 'b', 'c'], b: 'hello, world' };
_.pullAll(DATA.a, values);

// difference
let DATA = { a: ['a', 'b', 'c'], b: 'hello, world' };
DATA = { ...DATA, a: _.difference(DATA.a, values) };

pullAllBy

No direct Perkakas helper. Use the notes below, or native JavaScript.

Not provided by Perkakas.

CAUTION: pullAllBy mutates the input array in-place! All functions in Perkakas are pure and never mutate the input. These mutations might introduce side-effects and implicit dependencies in your codebase that need to be handled before migrating to Perkakas!

  • pullAllBy is equivalent to Lodash's differenceBy function. To migrate to Perkakas first migrate calls to Lodash _.differenceBy and then use the migration docs for differenceBy to complete the migration.
  • If the mutability of the input array is desired, make sure the variable is assignable (e.g., using let instead of const), and assign back the result of differenceBy back to it. Note that if the input array is part of an object or nested array, you will need to manually reconstruct this outer object with the updated array manually.
  • If mutability wasn't desired, and instead the input was cloned (shallowly) before calling pullAllBy, that cloning is now redundant.

In-place mutation

// pullAllBy
const DATA = [{ x: 1 }, { x: 2 }, { x: 3 }];
_.pullAllBy(DATA, values, iteratee);

// differenceBy
let DATA = [{ x: 1 }, { x: 2 }, { x: 3 }];
DATA = _.differenceBy(DATA, values, iteratee);

Non-mutating usage

// pullAllBy
const pulled = _.pullAllBy([...DATA], values, iteratee);

// differenceBy
const pulled = _.differenceBy(DATA, values, iteratee);

Nested inside an object

// pullAllBy
const DATA = { items: [{ x: 1 }, { x: 2 }], meta: 'info' };
_.pullAllBy(DATA.items, values, iteratee);

// differenceBy
let DATA = { items: [{ x: 1 }, { x: 2 }], meta: 'info' };
DATA = { ...DATA, items: _.differenceBy(DATA.items, values, iteratee) };

pullAllWith

No direct Perkakas helper. Use the notes below, or native JavaScript.

Not provided by Perkakas.

CAUTION: pullAllWith mutates the input array in-place! All functions in Perkakas are pure and never mutate the input. These mutations might introduce side-effects and implicit dependencies in your codebase that need to be handled before migrating to Perkakas!

  • pullAllWith is equivalent to Lodash's differenceWith function. To migrate to Perkakas first migrate calls to Lodash _.differenceWith and then use the migration docs for differenceWith to complete the migration. IMPORTANT: The Perkakas differenceWith function isn't a drop-in replacement for the Lodash _.differenceWith function, we do not recommend migrating directly from pullAllWith to Perkakas's differenceWith.
  • If the mutability of the input array is desired, make sure the variable is assignable (e.g., using let instead of const), and assign back the result of differenceWith back to it. Note that if the input array is part of an object or nested array, you will need to manually reconstruct this outer object with the updated array manually.
  • If mutability wasn't desired, and instead the input was cloned (shallowly) before calling pullAllWith, that cloning is now redundant.

In-place mutation

// pullAllWith
const DATA = [{ x: 1 }, { x: 2 }, { x: 3 }];
_.pullAllWith(DATA, values, comparator);

// differenceWith
let DATA = [{ x: 1 }, { x: 2 }, { x: 3 }];
DATA = _.differenceWith(DATA, values, comparator);

Non-mutating usage

// pullAllWith
const pulled = _.pullAllWith([...DATA], values, comparator);

// differenceWith
const pulled = _.differenceWith(DATA, values, comparator);

Nested inside an object

// pullAllWith
const DATA = { items: [{ x: 1 }, { x: 2 }], meta: 'info' };
_.pullAllWith(DATA.items, values, comparator);

// differenceWith
let DATA = { items: [{ x: 1 }, { x: 2 }], meta: 'info' };
DATA = { ...DATA, items: _.differenceWith(DATA.items, values, comparator) };

random

Lodash random → randomInteger.

  • When Lodash's random function is called with a non-integer param it returns any number, not just integers (e.g. 2.5)! Perkakas's randomInteger function always returns integers, effectively rounding the parameters to fit the range of possible integers.
  • If you want to generate any number in the range (and not just integers) see the solutions provided in "You don't need Lodash".
  • Lodash's random parameters are optional. In Perkakas all parameters are required.

Two integer params

// Lodash
random(1, 10);

// Perkakas
randomInteger(1, 10);

Single integer param

// Lodash
random(10);

// Perkakas
randomInteger(0, 10);

No params

// Lodash
random();

// Perkakas
randomInteger(0, 1);

Not supported: floating-point numbers

random(1.5, 3.5);
random(1.5);
random(10, true);
random(5, 10, true);
random(true);

range

Lodash range → range.

  • When calling Lodash's range with a single argument, an implicit start of 0 is used. In Perkakas, start is always required.
  • When start > end, Lodash infers a negative step (-1). Perkakas does not infer the step direction — a negative step must be provided explicitly, or the result is an empty array.
  • Lodash special-cases a step of 0 by computing the length of the output as if step were 1, and filling the output with the start value. We throw a RangeError for this case instead.
  • Lodash coerces NaN to 0 (e.g., _.range(NaN, 3) → [0, 1, 2]). Perkakas does not handle NaN inputs and their behavior is unspecified.

Implicit 0 start

// Lodash
_.range(5);
_.range(end);

// Perkakas
range(0, 5);
range(0, end);

With start

// Lodash
_.range(1, 5);
_.range(start, end);

// Perkakas
range(1, 5);
range(start, end);

With step

// Lodash
_.range(1, 20, 5);
_.range(start, end, step);

// Perkakas
range(1, { end: 20, step: 5 });
range(start, { end, step });

Descending range (start > end)

// Lodash
_.range(5, 1);
_.range(start, end);

// Perkakas
range(5, { end: 1, step: -1 });
range(start, { end, step: -1 });

Step of 0

// Lodash
_.range(1, 5, 0);
_.range(start, end, 0);

// Native
Array.from({ length: 4 }).fill(1);
Array.from({ length: Math.abs(end - start) }).fill(start);

rangeRight

No direct Perkakas helper. Use the notes below, or native JavaScript.

Lodash's rangeRight is equivalent to composing _.reverse on _.range. Migrate first to that pattern, and then use the migration guides for those functions to fully migrate to Perkakas.

// Lodash
_.rangeRight(start, end, step);
_.reverse(_.range(start, end, step));

reduce

Lodash reduce → reduce.

reduceRight

Lodash reduceRight → reduce.

Use reverse on the input.

// Lodash
reduceRight(DATA, reducer, accumulator);

// Perkakas
reduce(reverse(DATA), reducer, accumulator);

// Or with a pipe
pipe(DATA, reverse(), reduce(reducer, accumulator));

reject

Lodash reject → filter.

Wrap the callback with isNot.

// Lodash
reject(DATA, predicate);

// Perkakas
filter(DATA, isNot(predicate));

repeat

No direct Perkakas helper. Use the notes below, or native JavaScript.

Not provided by Perkakas.

Use native String.prototype.repeat instead.

// Lodash
_.repeat(input, n);

// Native
input.repeat(n);

replace

No direct Perkakas helper. Use the notes below, or native JavaScript.

Not provided by Perkakas.

  • Use native String.prototype.replace instead.
  • Lodash typing incorrectly allows calling replace with two arguments. These calls return strings as-is, while RegExp inputs are converted to strings.

Regular Usage

// Lodash
_.replace(input, pattern, replacement);

// Native
input.replace(pattern, replacement);

Invalid 2-parameter usage

// Lodash
_.replace(input, replacement);
_.replace(/pattern/, replacement);

// Native
input; // Returns input unchanged
String(/pattern/); // "/pattern/" - RegExp converted to string

reverse

Lodash reverse → reverse.

Lodash mutates the input but in Perkakas a new array is returned instead.

// Lodash
const DATA = [1, 2, 3];
reverse(DATA);

// Perkakas
let DATA = [1, 2, 3];
DATA = reverse(DATA);

round

Lodash round → round.

sample

Lodash sample → sample.

  • The sample function in Perkakas takes a required second parameter of the size of the sample. Use 1.
  • The Perkakas function returns an array; to extract the value either use native destructuring or index access, or use only.
  • For objects, first call values.
  • If you are using sample in an extremely hot path where you need the most efficient implementation possible, prefer a native implementation instead.

Simple

// Lodash
sample(DATA);

// Perkakas
only(sample(DATA, 1 /* size */));

// or with a pipe
pipe(DATA, sample(1 /* size */), only());

// Or with native accessors
sample(DATA, 1 /* size */)[0];
sample(DATA, 1 /* size */).at(0)!;
const [result] = sample(DATA, 1 /* size */);

Native

// Lodash
sample(DATA);

// Native
DATA[Math.floor(Math.random() * DATA.length)]!;
DATA.at(Math.floor(Math.random() * DATA.length))!;

sampleSize

Lodash sampleSize → sample.

  • Unlike Lodash, In Perkakas the sample function returns items in the same order they appear in the input. Use the shuffle function if you also need to randomize their order.
  • The second parameter to sampleSize in Lodash is optional, and defaults to 1 when not provided. In Perkakas this is a required param.
// Lodash
sampleSize(DATA);

// Perkakas
sample(DATA, 1);
// Lodash
sampleSize(DATA, 2);

// Perkakas
// ❌ The result isn't shuffled!
sample(DATA, 2);

// ✅ Add `shuffle` after `sample` (if order is important).
shuffle(sample(DATA, 2));

// Or with a pipe
pipe(DATA, sample(2), shuffle());

set

Lodash set → setPath.

  • In Lodash the set function supports two ways of defining the path parameter: a string representation of the path (similar to XPath: e.g. a.b[0].c), and an array representation of the path (e.g. ['a', 'b', 0, 'c']). In Perkakas only the array representation is accepted. Use the helper function stringToPath to translate string paths to array paths.
  • Unlike the Lodash set function, In Perkakas the provided value must match the type of the prop at that path, the function does not support creating "sparse" objects.
  • For better type-safety, Perkakas offers two additional functions to handle paths of length 1. Use set to update an existing prop in an object (best type-safety and with IDE type-ahead), and addProp to add a new prop to the object (or to override its type)
  • In Lodash set mutates the input object. In Perkakas a new object is returned instead. The input object is never mutated.
let data = { a: 'hello', deep: [{ z: true }] };

// Lodash
set(data, 'a', 'world');
set(data, ['a'], 'foo');

set(data, 'a', 123);
set(data, ['a'], 456);
set(data, 'b', 123);
set(data, ['b'], 456);

set(data, 'deep[0].z', false);
set(data, ['deep', 0, 'z'], false);

// ❌ Not supported:
set(data, 'deep[0].newProp', 123);
set(data, ['deep', 0, 'newProp'], 123);

// Perkakas
data = set(data, 'a', 'world');
data = set(data, 'a', 'foo');

data = addProp(data, 'a', 123);
data = addProp(data, 'a', 456);
data = addProp(data, 'b', 123);
data = addProp(data, 'b', 456);

data = setPath(data, stringToPath('deep[0].z'), false);
data = setPath(data, ['deep', 0, 'z'], false);

shuffle

Lodash shuffle → shuffle.

size

Lodash size → length.

snakeCase

Lodash snakeCase → toSnakeCase.

  • Lodash attempts pseudo-linguistic word splitting to handle special characters, which might lead to inaccurate results. Perkakas uses a simpler word splitting approach based on type-fest's definition that should only be used for simple strings like identifiers and internal keys. For linguistic processing where language and locale nuances matter, use the built-in Intl.Segmenter instead.
  • Lodash performs normalization on the input before splitting it, including deburr and removing apostrophes. Perkakas's word splitting is simpler and doesn't include these normalizations, so they need to be done manually if required.
  • Lodash allows calling snakeCase without any input, or with an undefined input, which isn't supported in Perkakas. Handle these cases before calling the function.

Simple strings

// Lodash
_.snakeCase(input);

// Perkakas
toSnakeCase(input);

Normalized

// Lodash
_.snakeCase(input);

// Perkakas + Native
toSnakeCase(
  input
    // "Promote" diacritics to be independent characters in the string.
    .normalize('NFD')
    // Remove apostrophes and all independent diacritic characters.
    .replace(/['\u2019\u0300-\u036F]/g, ''),
);

Missing input

// Lodash
_.snakeCase();
_.snakeCase(input);

// Perkakas
('');
input !== undefined ? toSnakeCase(input) : '';

// Or
toSnakeCase(input ?? '');

some

No direct Perkakas helper. Use the notes below, or native JavaScript.

Not provided by Perkakas.

Use the native JS Array.prototype.some instead.

@see "You don't need Lodash"

// Lodash
some(DATA, predicate);

// Native
DATA.some(predicate);

sortBy

Lodash sortBy → sortBy.

The sortBy function in Perkakas does not support defining the sort criteria using prop names, only callback functions. Also, the sort criteria is provided as a variadic argument of sort criteria, and not a single array argument.

const DATA = [
  { user: 'fred', age: 48 },
  { user: 'barney', age: 34 },
  { user: 'fred', age: 40 },
  { user: 'barney', age: 36 },
];

// Lodash
sortBy(DATA, [
  function (o) {
    return o.user;
  },
]);
sortBy(DATA, ['user', 'age']);

// Perkakas
sortBy(DATA, prop('user'));
sortBy(DATA, prop('user'), prop('age'));

sortedIndex

Lodash sortedIndex → sortedIndex.

sortedIndexBy

Lodash sortedIndexBy → sortedIndexBy.

sortedLastIndex

Lodash sortedLastIndex → sortedLastIndex.

sortedLastIndexBy

Lodash sortedLastIndexBy → sortedLastIndexBy.

sortedUniq

Lodash sortedUniq → unique.

Not provided by Perkakas.

There is no equivalent function in Perkakas because both unique and sortedUniq run at O(n) time complexity. In space complexity (the amount of memory needed to compute them), unique is O(k) where k is the number of unique values in the array, while sortedUniq is O(1). In most real-life use cases, the two implementations would have near-identical performance. We highly recommend always using unique unless you know that the difference matters.

If you still need this algorithm, use filter with the following predicate:

// Lodash
sortedUniq(DATA);

// Perkakas
unique(DATA);
filter(DATA, (item, index, array) => index === 0 || item !== array[index - 1]);

split

Lodash split → split.

  • Lodash allows calling split without a separator parameter (or setting it to undefined) which returns the input string as a single-element array (the same happens when split is used as a callback). Perkakas needs the input wrapped in an array manually.
  • Lodash accepts null and undefined input values, converting them to empty strings. Perkakas needs these values handled separately.
  • When the separator parameter is "", Lodash uses complex logic to parse compound Unicode characters (like family emojis 👨‍👩‍👧‍👦 or flags with modifiers 🏳️‍🌈) as single graphemes. Perkakas uses native String.prototype.split which splits these into component parts. Use Intl.Segmenter with granularity: "grapheme" for proper Unicode handling. This is rarely needed - simple Unicode characters (like non-Latin alphabets) work fine.

Basic usage

// Lodash
_.split(input, separator);
_.split(input, separator, limit);

// Perkakas
split(input, separator);
split(input, separator, limit);

Without separator

// Lodash
_.split(input);
_.map(data, _.split);

// Native
[input];
map(data, (item) => [item]);

Nullish inputs

// Lodash
_.split(input, separator);
_.split(input, separator, limit);

// Perkakas
input != null ? split(input, separator) : [''];
input != null ? split(input, separator, limit) : [''];

// Or
split(input ?? '', separator);
split(input ?? '', separator, limit);

Unicode grapheme splitting

// Lodash
_.split(input, '');
_.split(input, '', limit);

// Native
[...new Intl.Segmenter('en', { granularity: 'grapheme' }).segment(input)].map(
  ({ segment }) => segment,
);
[...new Intl.Segmenter('en', { granularity: 'grapheme' }).segment(input)]
  .map(({ segment }) => segment)
  .slice(0, limit);

startCase

Lodash startCase → toTitleCase.

  • Lodash attempts pseudo-linguistic word splitting to handle special characters, which might lead to inaccurate results. Perkakas uses a simpler word splitting approach based on type-fest's definition that should only be used for simple strings like identifiers and internal keys. For linguistic processing where language and locale nuances matter, use the built-in Intl.Segmenter instead.
  • Perkakas treats consecutive uppercase characters differently from Lodash. Use { preserveConsecutiveUppercase: false } as the second parameter to get the same results.
  • Lodash performs normalization on the input before splitting it, including deburr and removing apostrophes. Perkakas's word splitting is simpler and doesn't include these normalizations, so they need to be done manually if required.
  • Lodash allows calling startCase without any input, or with an undefined input, which isn't supported in Perkakas. Handle these cases before calling the function.

Simple strings

// Lodash
_.startCase(input);

// Perkakas
toTitleCase(input, { preserveConsecutiveUppercase: false });

Normalized

// Lodash
_.startCase(input);

// Perkakas + Native
toTitleCase(
  input
    // "Promote" diacritics to be independent characters in the string.
    .normalize('NFD')
    // Remove apostrophes and all independent diacritic characters.
    .replace(/['\u2019\u0300-\u036F]/g, ''),
  { preserveConsecutiveUppercase: false },
);

Missing input

// Lodash
_.startCase();
_.startCase(input);

// Perkakas
('');
input !== undefined
  ? toTitleCase(input, { preserveConsecutiveUppercase: false })
  : '';

// Or
toTitleCase(input ?? '', { preserveConsecutiveUppercase: false });

startsWith

Lodash startsWith → startsWith.

  • Lodash supports an optional position parameter to check from a specific index, while Perkakas only checks from the beginning of the string. Compose sliceString with startsWith to replicate this behavior.
  • Lodash allows calling the function without a target parameter (or using undefined for it). These would result in the function always returning true. In Perkakas the target is required and handling undefined should be done before calling startsWith.
  • Lodash also allows calling the function without an input parameter (or using undefined for it). These would result in the function always returning false (unless target is also "" or undefined). This parameter is also required in Perkakas; handle the undefined explicitly.

Basic usage

// Lodash
_.startsWith(input, target);

// Perkakas
startsWith(input, target);

Position parameter

// Lodash
_.startsWith(input, target, position);

// Perkakas
startsWith(sliceString(input, position), target);

Missing parameters

// Lodash
_.startsWith();
_.startsWith(input);

// Native
true;
true;

Undefined target

// Lodash
_.startsWith(input, target);

// Perkakas
target === undefined || startsWith(input, target);

// Or
startsWith(input, target ?? '');

Undefined input

// Lodash
_.startsWith(input, target);

// Perkakas
startsWith(input ?? '', target);

// Or (if you know target is not `undefined` or `""`)
input !== undefined && startsWith(input, target);

subtract

Lodash subtract → subtract.

sum

Lodash sum → sum.

sumBy

Lodash sumBy → sumBy.

take

Lodash take → take.

takeRight

Lodash takeRight → takeLast.

takeRightWhile

Lodash takeRightWhile → takeLastWhile.

takeWhile

Lodash takeWhile → takeWhile.

tap

Lodash tap → tap.

template

No direct Perkakas helper. Use the notes below, or native JavaScript.

Not provided by Perkakas.

Lodash's template is a complex templating engine that compiles templates into executable JavaScript functions. Due to its complexity and security implications, it is outside the scope that Perkakas offers.

  • In most cases it can be substituted by native template literals.
  • In many other cases it is redundant with the templating capabilities of frameworks such as React, Express, Vue, Fastify, and many others.
  • For the most complex cases there are dedicated libraries that offer robust templating features (e.g., Handlebars).

Template Literals

// Lodash
const greet = _.template('Hello <%= firstName %> <%= lastName %>!');

// Template literals
function greet({
  firstName,
  lastName,
}: {
  firstName: string;
  lastName: string;
}) {
  return `Hello ${firstName} ${lastName}!`;
}

HTML

// Lodash
const bold = _.template('<b><%- value %></b>');
bold({ value: '<script>' });

// React
function Bold({ value }: { value: string }) {
  return <b>{value}</b>;
}

<Bold value="<script>" />;

With complex logic

// Lodash
const list = _.template(
  '<% _.forEach(users, function(user) { %><li><%- user %></li><% }); %>',
);
list({ users: ['fred', 'barney'] });

// React
function List({ users }: { users: Array<string> }) {
  return users.map((user) => <li key={user}>{user}</li>);
}

<List users={['fred', 'barney']} />;

throttle

Lodash throttle → funnel.

  • throttle can be implemented using the funnel utility. A reference implementation is provided below, and a more expanded version with inline documentation and tests is available in the test file funnel.lodash-throttle.test.ts.
  • A more complete reference implementation that also maintains Lodash's capability to store the callback's return value is available below, and in funnel.lodash-throttle-with-cached-value.test.ts.
  • These implementations can be copied as-is into your project, but might contain redundant parts which are not relevant for your specific use cases. By inlining only the parts you need you can take advantage of capabilities not available in Lodash. To copy the expanded versions from the test files instead, take everything between the REFERENCE START and REFERENCE END markers.

Reference

import { funnel } from '@vinicunca/perkakas';

function throttle<F extends (...args: any) => void>(
  func: F,
  wait = 0,
  {
    leading = true,
    trailing = true,
  }: { readonly leading?: boolean; readonly trailing?: boolean } = {},
) {
  const {
    call,
    isIdle: _isIdle,
    ...rest
  } = funnel(
    () => {
      if (leading || trailing) {
        func();
      }
    },
    {
      minQuietPeriodMs: wait,
      maxBurstDurationMs: wait,
      ...(trailing
        ? leading
          ? { triggerAt: 'both' }
          : { triggerAt: 'end' }
        : { triggerAt: 'start' }),
    },
  );
  return Object.assign(call, rest);
}

With call arguments

import { funnel } from '@vinicunca/perkakas';

function throttle<F extends (...args: any) => void>(
  func: F,
  wait = 0,
  {
    leading = true,
    trailing = true,
  }: { readonly leading?: boolean; readonly trailing?: boolean } = {},
) {
  const {
    call,
    isIdle: _isIdle,
    ...rest
  } = funnel(
    (args: Parameters<F>) => {
      if (leading || trailing) {
        func(...args);
      }
    },
    {
      reducer: (_, ...args: Parameters<F>) => args,
      minQuietPeriodMs: wait,
      maxBurstDurationMs: wait,
      ...(trailing
        ? leading
          ? { triggerAt: 'both' }
          : { triggerAt: 'end' }
        : { triggerAt: 'start' }),
    },
  );
  return Object.assign(call, rest);
}

With cached value

import { funnel } from '@vinicunca/perkakas';

function throttle<F extends (...args: any) => any>(
  func: F,
  wait = 0,
  {
    leading = true,
    trailing = true,
  }: { readonly leading?: boolean; readonly trailing?: boolean } = {},
) {
  let cachedValue: ReturnType<F> | undefined;

  const { call, flush, cancel } = funnel(
    (args: Parameters<F>) => {
      if (leading || trailing) {
        cachedValue = func(...args) as ReturnType<F>;
      }
    },
    {
      reducer: (_, ...args: Parameters<F>) => args,
      minQuietPeriodMs: wait,
      maxBurstDurationMs: wait,
      ...(trailing
        ? leading
          ? { triggerAt: 'both' }
          : { triggerAt: 'end' }
        : { triggerAt: 'start' }),
    },
  );

  return Object.assign(
    (...args: Parameters<F>) => {
      call(...args);
      return cachedValue;
    },
    {
      flush: () => {
        flush();
        return cachedValue;
      },

      cancel,
    },
  );
}

times

Lodash times → times.

toLower

Lodash toLower → toLowerCase.

  • For display purposes, prefer using CSS text-transform: lowercase.
  • Lodash allows calling toLower without any input, or with an undefined input, which isn't supported in Perkakas. Handle these cases before calling the function.

Basic usage

// Lodash
_.toLower(input);

// Perkakas
toLowerCase(input);

CSS

// Lodash
<div>{_.toLower(input)}</div>

// CSS
<div style="text-transform:lowercase">{input}</div>

Missing input

// Lodash
_.toLower();
_.toLower(input);

// Perkakas
('');
input !== undefined ? toLowerCase(input) : '';

// Or
toLowerCase(input ?? '');

toPairs

Lodash toPairs → entries.

toPath

Lodash toPath → stringToPath.

toUpper

Lodash toUpper → toUpperCase.

  • For display purposes, prefer using CSS text-transform: uppercase.
  • Lodash allows calling toUpper without any input, or with an undefined input, which isn't supported in Perkakas. Handle these cases before calling the function.

Basic usage

// Lodash
_.toUpper(input);

// Perkakas
toUpperCase(input);

CSS

// Lodash
<div>{_.toUpper(input)}</div>

// CSS
<div style="text-transform:uppercase">{input}</div>

Missing input

// Lodash
_.toUpper();
_.toUpper(input);

// Perkakas
('');
input !== undefined ? toUpperCase(input) : '';

// Or
toUpperCase(input ?? '');

transform

Lodash transform → reduce.

trim

No direct Perkakas helper. Use the notes below, or native JavaScript.

Not provided by Perkakas.

  • When the second characters parameter is not provided to Lodash (or when it is undefined), all whitespace characters would be trimmed. This is the same as the native String.prototype.trim.
  • The native trim doesn't support the additional characters parameter that allows changing the trimmed characters. Instead, create a regex that would match characters anchored to either the start or the end of the string (^[${characters}]+|[${characters}]+$) and then use String.prototype.replace to replace them with the empty string ("") and use the g RegExp flag so that it matches both the start and the end sequences. You might need to use RegExp.escape on characters if they might contain special regex characters.
  • Lodash does complex grapheme parsing, but this is usually not needed unless the characters parameter itself contains complex Unicode graphemes (like family emojis 👨‍👩‍👧‍👦 or flags with modifiers 🏳️‍🌈 that you want to trim). In these cases use Intl.Segmenter with granularity: "grapheme" to split the string, then use Perkakas's dropWhile (for left trimming) and dropLastWhile (for right trimming), and rejoin the array back with join.
  • Lodash allows calling trim without any input (or with an undefined input), which results in an empty string "". This requires explicit handling in replacements.

Whitespaces

// Lodash
_.trim(input);

// Native
input.trim();

Callback

// Lodash
_.map(data, _.trim);

// Native
data.map(String.prototype.trim);

Characters

// Lodash
_.trim(input, characters);

// Native
const escaped = RegExp.escape(characters);
input.replace(new RegExp(`^[${escaped}]+|[${escaped}]+$`, 'g'), '');

Graphemes

// Lodash
_.trim(input, characters);

// Perkakas
const segmenter = new Intl.Segmenter('en', { granularity: 'grapheme' });
const inputGraphemes = Array.from(segmenter.segment(input), prop('segment'));
const graphemes = Array.from(segmenter.segment(characters), prop('segment'));
join(
  dropLastWhile(
    dropWhile(inputGraphemes, isIncludedIn(graphemes)),
    isIncludedIn(graphemes),
  ),
  '',
);

Missing input data

// Lodash
_.trim();
_.trim(input);

// Native
('');
input?.trim() ?? '';

trimEnd

No direct Perkakas helper. Use the notes below, or native JavaScript.

Not provided by Perkakas.

  • When the second characters parameter is not provided to Lodash (or when it is undefined), all whitespace characters would be trimmed. This is the same as the native String.prototype.trimEnd.
  • The native trimEnd doesn't support the additional characters parameter that allows changing the trimmed characters. Instead, create a regex that would match characters anchored to the end of the string ([${characters}]+$) and then use String.prototype.replace to replace them with the empty string (""). You might need to use RegExp.escape on characters if they might contain special regex characters.
  • Lodash does complex grapheme parsing, but this is usually not needed unless the characters parameter itself contains complex Unicode graphemes (like family emojis 👨‍👩‍👧‍👦 or flags with modifiers 🏳️‍🌈 that you want to trim). In these cases use Intl.Segmenter with granularity: "grapheme" to split the string, then use Perkakas's dropLastWhile, and rejoin the array back with join.
  • Lodash allows calling trimEnd without any input (or with an undefined input), which results in an empty string "". This requires explicit handling in replacements.

Whitespaces

// Lodash
_.trimEnd(input);

// Native
input.trimEnd();

Callback

// Lodash
_.map(data, _.trimEnd);

// Native
data.map(String.prototype.trimEnd);

Characters

// Lodash
_.trimEnd(input, characters);

// Native
input.replace(new RegExp(`[${RegExp.escape(characters)}]+$`), '');

Graphemes

// Lodash
_.trimEnd(input, characters);

// Perkakas
const segmenter = new Intl.Segmenter('en', { granularity: 'grapheme' });
const inputGraphemes = Array.from(segmenter.segment(input), prop('segment'));
const graphemes = Array.from(segmenter.segment(characters), prop('segment'));
join(dropLastWhile(inputGraphemes, isIncludedIn(graphemes)), '');

Missing input data

// Lodash
_.trimEnd();
_.trimEnd(input);

// Native
('');
input?.trimEnd() ?? '';

trimStart

No direct Perkakas helper. Use the notes below, or native JavaScript.

Not provided by Perkakas.

  • When the second characters parameter is not provided to Lodash (or when it is undefined), all whitespace characters would be trimmed. This is the same as the native String.prototype.trimStart.
  • The native trimStart doesn't support the additional characters parameter that allows changing the trimmed characters. Instead, create a regex that would match characters anchored to the start of the string (^[${characters}]+) and then use String.prototype.replace to replace them with the empty string (""). You might need to use RegExp.escape on characters if they might contain special regex characters.
  • Lodash does complex grapheme parsing, but this is usually not needed unless the characters parameter itself contains complex Unicode graphemes (like family emojis 👨‍👩‍👧‍👦 or flags with modifiers 🏳️‍🌈 that you want to trim). In these cases use Intl.Segmenter with granularity: "grapheme" to split the string, then use Perkakas's dropWhile, and then rejoin the array back with join.
  • Lodash allows calling trimStart without any input (or with an undefined input), which results in an empty string "". This requires explicit handling in replacements.

Whitespaces

// Lodash
_.trimStart(input);

// Native
input.trimStart();

Callback

// Lodash
_.map(data, _.trimStart);

// Native
data.map(String.prototype.trimStart);

Characters

// Lodash
_.trimStart(input, characters);

// Native
input.replace(new RegExp(`^[${RegExp.escape(characters)}]+`), '');

Graphemes

// Lodash
_.trimStart(input, characters);

// Perkakas
const segmenter = new Intl.Segmenter('en', { granularity: 'grapheme' });
const inputGraphemes = Array.from(segmenter.segment(input), prop('segment'));
const graphemes = Array.from(segmenter.segment(characters), prop('segment'));
join(dropWhile(inputGraphemes, isIncludedIn(graphemes)), '');

Missing input data

// Lodash
_.trimStart();
_.trimStart(input);

// Native
('');
input?.trimStart() ?? '';

truncate

Lodash truncate → truncate.

  • Perkakas requires an explicit length parameter, while Lodash defaults to 30 when it isn't provided.
  • Lodash computes the length of the input and omission strings in graphemes, unlike Perkakas that counts Unicode characters. In the vast majority of cases these are identical, but when these strings contain complex Unicode characters (like family emojis 👨‍👩‍👧‍👦 or flags with modifiers 🏳️‍🌈) the input might not be truncated at the same index.
  • The Perkakas function would never return a string longer than length, opting to truncate the omission itself when it's too long. Lodash will never truncate the omission and therefore might return an output that exceeds length.

Default length

// Lodash
_.truncate(input);

// Perkakas
truncate(input, 30);

Custom length

// Lodash
_.truncate(input, { length });

// Perkakas
truncate(input, length);

With options

// Lodash
_.truncate(input, { omission, separator });
_.truncate(input, { length, omission, separator });

// Perkakas
truncate(input, 30, { omission, separator });
truncate(input, length, { omission, separator });

unescape

No direct Perkakas helper. Use the notes below, or native JavaScript.

Not provided by Perkakas.

unescape is rarely needed in modern web development. The main use case is processing HTML documents/snippets. For comprehensive HTML processing, prefer dedicated HTML/XML parsing libraries. In the rare case you must have an unescaping function, copy the reference code below as-is:

const UNESCAPED: Readonly<Record<`&${string};`, string>> = {
  // https://github.com/lodash/lodash/blob/main/lodash.js#L408-L412
  '&amp;': '&',
  '&lt;': '<',
  '&gt;': '>',
  '&quot;': '"',
  '&#39;': '\'',
};

const ENTITIES_RE = new RegExp(`${Object.keys(UNESCAPED).join('|')}`, 'g');

function unescape(input: string): string {
  return input.replace(ENTITIES_RE, (entity) => UNESCAPED[entity]!);
}

uniq

Lodash uniq → unique.

uniqBy

Lodash uniqBy → uniqueBy.

uniqWith

Lodash uniqWith → uniqueWith.

upperCase

Lodash upperCase → toTitleCase.

  • Use toUpperCase on the results of toTitleCase, which reformats the string into space-delimited words.
  • Lodash attempts pseudo-linguistic word splitting to handle special characters, which might lead to inaccurate results. Perkakas uses a simpler word splitting approach based on type-fest's definition that should only be used for simple strings like identifiers and internal keys. For linguistic processing where language and locale nuances matter, use the built-in Intl.Segmenter instead.
  • Lodash performs normalization on the input before splitting it, including deburr and removing apostrophes. Perkakas's word splitting is simpler and doesn't include these normalizations, so they need to be done manually if required.
  • Lodash allows calling upperCase without any input, or with an undefined input, which isn't supported in Perkakas. Handle these cases before calling the function.

Basic usage

// Lodash
_.upperCase(input);

// Perkakas
toUpperCase(toTitleCase(input));

Normalized

// Lodash
_.upperCase(input);

// Perkakas + Native
toUpperCase(
  toTitleCase(
    input
      // "Promote" diacritics to be independent characters in the string.
      .normalize('NFD')
      // Remove apostrophes and all independent diacritic characters.
      .replace(/['\u2019\u0300-\u036F]/g, ''),
  ),
);

Missing input

// Lodash
_.upperCase();
_.upperCase(input);

// Perkakas
('');
input !== undefined ? toUpperCase(toTitleCase(input)) : '';

// Or
toUpperCase(toTitleCase(input ?? ''));

upperFirst

Lodash upperFirst → capitalize.

  • For display purposes, prefer using CSS; use ::first-letter to target just the first letter of the word and text-transform: uppercase to capitalize it.
  • Lodash allows calling upperFirst without any input (or with an undefined input), which results in an empty string "". Perkakas's capitalize requires a string input.

Basic usage

// Lodash
_.upperFirst(input);

// Perkakas
capitalize(input);

CSS

// Lodash
<div>{_.upperFirst(input)}</div>

// CSS
<style>
  .upperFirst::first-letter {
    text-transform: uppercase;
  }
</style>
<div class="upperFirst">{input}</div>

Missing input data

// Lodash
_.upperFirst();
_.upperFirst(input);

// Perkakas
input !== undefined ? capitalize(input) : '';

// Or
capitalize(input ?? '');

values

Lodash values → values.

without

No direct Perkakas helper. Use the notes below, or native JavaScript.

Not provided by Perkakas.

  • without is equivalent to Lodash's difference function. To migrate to Perkakas first migrate calls to Lodash _.difference and then use the migration docs for difference to complete the migration. IMPORTANT: The Perkakas difference function isn't a drop-in replacement for the Lodash _.difference function, we do not recommend migrating directly from without to Perkakas's difference.
  • without takes variadic arguments; difference takes an explicit array. You will need to wrap your items in an array when migrating.
// without
_.without(DATA, a, b, c, ...additional);

// difference
_.difference(DATA, [a, b, c, ...additional]);

words

No direct Perkakas helper. Use the notes below, or native JavaScript.

Not provided by Perkakas.

  • words is often used to convert between different cases of identifiers and keys. Use toCamelCase, toKebabCase, toSnakeCase, or toTitleCase instead.
  • If words is used for simple splitting tasks, it can often be replaced with String.prototype.split using simple regular expressions like /\s+/, /\W+/, /[\p{Z}\p{P}]+/u, or ones tailored specifically for your use-case.
  • Lodash performs a lot of pseudo-linguistic heuristics in order to detect special characters like diacritics, emojis, and complex graphemes. If you need accurate language-aware splitting of words, prefer Intl.Segmenter with granularity: "word".
  • When provided with the optional pattern parameter, words defers the call to String.prototype.match as-is.

Case conversion

// Lodash
_.words(input).map(_.toLowerCase).join('-');
_.words(input).map(_.toLowerCase).join('_');
_.words(input).map(_.capitalize).join(' ');
_.words(input).map(_.toUpperCase).join('-');
_.words(input).map(_.toUpperCase).join('_');

// Perkakas
toKebabCase(input);
toSnakeCase(input);
toTitleCase(input, { preserveConsecutiveUppercase: false });
toUpperCase(toKebabCase(input));
toUpperCase(toSnakeCase(input));

Naive splitting

// Lodash
_.words(input);

// Perkakas
split(input, /\s+/); // spaces
split(input, /\W+/); // spaces, punctuation
split(input, /[\p{Z}\p{P}]+/u); // support for unicode spaces and punctuation.

Proper NLP

// Lodash
_.words(input);

// Native
const segmenter = new Intl.Segmenter('en', { granularity: 'word' });
Array.from(segmenter.segment(input)).map(({ segment }) => segment);

Custom pattern

// Lodash
_.words(input, pattern);

// Native
input.match(pattern);

zip

Lodash zip → zip.

zipObject

No direct Perkakas helper. Use the notes below, or native JavaScript.

Not provided by Perkakas.

Compose zip and fromEntries:

// Lodash
zipObject(keys, values);

// Perkakas
fromEntries(zip(keys, values));

zipWith

Lodash zipWith → zipWith.