RSS

Ramda Chops: Safely Accessing Properties

Info

SummaryGet introduced to ramda's prop, propOr, path & pathOr functions.
Shared2018-01-31

Thanks to @joshsloat and @zerkms for their review of this post.

One of the most prevalent causes of bugs I’ve seen in latter-day JavaScript revolves around expectations with regard to data modeling. With the rise of react, redux, et al, many of us store our application state in an object whose keys and hierarchy can easily change, leaving us sometimes with or without values that were in fact expected: for example, undefined is not a function or trying to call .map(...) on a non-mappable data type (such as null or undefined). While there are any number of solutions for this issue that might even include diving into algebraic data types, the ramda library gives us a few helper methods that we can use right away to dig into our data structures and extract values:


Other ramda posts:

prop & propOr

What happens normally if you expect an array, try to access the third item (index position 2), but are actually provided undefined instead of an array?

const arr = undefined
arr[2] // TypeError is thrown

What happens if you try to access the length property on what you think should be an array but ends up being null or undefined?

const arr = null
arr.length // TypeError is thrown

One solution is to do the “value or default” approach to keep the errors at bay:

const arr = undefined
const xs = arr || []
xs[2] // undefined
xs.length // 0

An approach we could take to avoid the errors being thrown would be to use ramda’s prop helper:

import prop from 'ramda/src/prop'

const arr = undefined
prop(2, arr) // undefined
prop('length', arr) // undefined

Ramda’s length function would accomplish a similar goal for prop('length').

But if we want a default to be returned in lieu of our data not being present, we can turn to propOr:

import propOr from 'ramda/src/propOr'

const arr = undefined
propOr({}, 2, arr) // {}
propOr(0, 'length', arr) // 0

If you need to select multiple properties without fear, then the props or pick functions may be for you.

path & pathOr

What if we are working in a deeply nested data structure where multiple keys in our hierarchy may or may not exist? Enter path and pathOr. These work similarly to prop and propOr except that they use an array syntax to dive into data structures and ultimately check for a value, whereas the prop family checks for a property’s presence.

import path from 'ramda/src/path'

const data = {
  courses: {
    abc123: {
      title: 'How To Build a Tiny House',
      dueAt: '2018-01-30'
    }
  }
}

// getCourseTitle :: String -> String | undefined
const getCourseTitle = courseId =>
  path(['courses', courseId, 'title'])

getCourseTitle('abc123')(data) // "How To Build a Tiny House"
getCourseTitle('def456')(data) // undefined

Try this code in the ramda REPL

Or if we’d always like to default to a value, we can use pathOr:

import pathOr from 'ramda/src/path'

const data = {
  courses: {
    abc123: {
      title: 'How To Build a Tiny House',
      dueAt: '2018-01-30'
    }
  }
}

// getCourseTitle :: String -> String
const getCourseTitle = courseId =>
  pathOr('My Course', ['courses', courseId, 'title'])

getCourseTitle('abc123')(data) // "How To Build a Tiny House"
getCourseTitle('def456')(data) // "My Course"

Try this code in the ramda REPL


As I said before, there are many different ways to solve this problem, but I’ve found the propOr and pathOr family of ramda functions to be a great starting point.

Until next time,
Robert