receta
Object

Migration Guide

Step-by-step guide to migrating from Lodash or vanilla JavaScript object operations to Receta's Object module.

Why Migrate?

Before (Lodash):

  • Manual immutability with cloneDeep
  • No type safety
  • Not composable in pipelines
  • No Result/Option integration

After (Receta):

  • Immutable by default
  • Full TypeScript support
  • Pipeline-friendly
  • Integrated error handling

Migration Steps

Step 1: Replace Deep Get/Set

// ❌ Lodash
import _ from 'lodash'

const value = _.get(obj, 'deeply.nested.value', 'default')
_.set(_.cloneDeep(obj), 'deeply.nested.value', 'new')

// ✅ Receta
import { getPath, setPath } from '@ontologyio/receta/object'
import { unwrapOr } from '@ontologyio/receta/option'

const value = pipe(
  obj,
  getPath<string>(['deeply', 'nested', 'value']),
  unwrapOr('default')
)
const updated = setPath(['deeply', 'nested', 'value'], 'new', obj)

Step 2: Replace Pick/Omit with Mask

// ❌ Lodash
const safe = _.pick(user, ['id', 'email'])

// ✅ Receta (security-focused)
const safe = mask(user, ['id', 'email'])

Step 3: Replace mapKeys/mapValues

// ❌ Lodash
_.mapKeys(obj, (value, key) => key.toUpperCase())
_.mapValues(obj, (value) => value * 2)

// ✅ Receta (mapKeys passes the key first)
mapKeys(obj, (key, value) => key.toUpperCase())
mapValues(obj, (value) => value * 2)

Step 4: Replace Deep Merge

// ❌ Lodash
_.merge(_.cloneDeep(obj1), obj2, obj3)

// ✅ Receta
deepMerge([obj1, obj2, obj3])

Step 5: Add Validation

import { flatMap, fromResult } from '@ontologyio/receta/option'

// ❌ Lodash (no validation)
const config = _.get(data, 'config', {})
// Hope it's the right shape!

// ✅ Receta (with validation)
const config = pipe(
  data,
  getPath(['config']),
  flatMap((c) => fromResult(validateShape(c, configSchema))),
  unwrapOr(defaultConfig)
)

Common Patterns

Pattern: Nested Updates

// ❌ Before
const renameUser = (state: State) => {
  const updated = _.cloneDeep(state)
  _.set(updated, 'user.profile.name', 'Alice')
  return updated
}

// ✅ After
const renameUser = (state: State) =>
  setPath(['user', 'profile', 'name'], 'Alice', state)

Pattern: Safe Access

import { tap } from '@ontologyio/receta/option'
// ❌ Before
const email = _.get(user, 'profile.contact.email')
if (email) {
  sendEmail(email)
}

// ✅ After
pipe(
  user,
  getPath<string>(['profile', 'contact', 'email']),
  tap(sendEmail)
)

Pattern: Transformation Pipeline

// ❌ Before
let result = _.cloneDeep(apiResponse)
result = _.mapKeys(result, (v, k) => _.camelCase(k))
result = _.omit(result, ['_internal', '_cache'])
result = _.mapValues(result, (v) => typeof v === 'string' ? v.trim() : v)

// ✅ After
const result = pipe(
  apiResponse,
  filterKeys((key) => !key.startsWith('_')),
  transformKeys('camelCase', { deep: false }),
  mapValues((v) => typeof v === 'string' ? v.trim() : v)
)

Migration Checklist

  • Replace _.get → getPath + unwrapOr
  • Replace _.set → setPath (remove cloneDeep)
  • Replace _.pick → mask (when security-focused)
  • Replace _.mapKeys → mapKeys
  • Replace _.mapValues → mapValues
  • Replace _.merge → deepMerge (remove cloneDeep)
  • Add validation with validateShape
  • Add type annotations
  • Convert to pipelines where appropriate

Performance Notes

  • No cloneDeep needed: only the objects along a path are copied
  • Tree-shakeable = smaller bundles
  • Type-safe = fewer runtime errors

Next Steps

On this page