Getting a string from a range of possible paths I have a scenario where I need to grab the first occurrence of a string from an object, but only if the match occurs in one of the path's that have been pre-defined.

{ id: 'I60ODI', description: 'some random description' }
{ foo: 'bar', description: { color: 'green', text: 'some description within text' } }


When provided either of the two objects above, I would expect the solution to return either some random description or some description within text, provided that the two possible paths are obj.description and obj.description.text. New paths might also need to be added in the future, so it needs to be easy to add them.

Here is the solution I have implemented so far, but to me it doesn't seem optimal.

// require the ramda library
const R = require('ramda');

// is the provided value a string?
const isString = R.ifElse(R.compose(R.equals('string'), (val) =&gt; typeof val), R.identity, R.always(false));
const addStringCheck = t =&gt; R.compose(isString, t);

// the possible paths to take (subject to scale)
const possiblePaths = [
    R.path(['description']),
    R.path(['description', 'text'])
];
// add the string check to each of the potential paths
const mappedPaths = R.map((x) =&gt; addStringCheck(x), possiblePaths);

// select the first occurrence of a string 
const extractString = R.either(...mappedPaths);

// two test objects
const firstObject = { description: 'some random description' };
const secondObject = { description: { text: 'some description within text' } };
const thirdObject = { foo: 'bar' };

console.log(extractString(firstObject)); // 'some random description'
console.log(extractString(secondObject)); // 'some description within text'
console.log(extractString(thirdObject)); // false


I would really appreciate it if a seasoned functional programmer might provide me with some alternative approaches to implementation. Thanks.
A few points:


your isString function isn't exactly checking if it's a string, as it's also returning false if not. The typeof check can be simplified to R.is(String)
It's probably better to push all the inputs to the edge of the logic, in this case making paths an argument too. Just splits out our logic an our inputs a bit better.
Instead of wrapping each path in R.path, you can make a function which turns all paths into path functions
For applying the list of path functions to the object, we can use R.juxt


Possible refactoring:

const isString = R.is(String)
const makePaths = R.map(R.path)

const extractStringFor = (paths) =&gt;
  R.compose(
    R.defaultTo(false),
    R.find(isString),
    R.juxt(makePaths(paths))
  )

const firstObject = { description: 'some random description' };
const secondObject = { description: { text: 'some description within text' } };
const thirdObject = { foo: 'bar' };

const paths = [['description'], ['description', 'text']]
const extractString = extractStringFor(paths)

extractString(firstObject), // 'some random description'
extractString(secondObject), // 'some description within text'
extractString(thirdObject) // false


caveat: This runs all paths against each object, and then find the match that's a string. That could be optimised though, potentially with R.takeWhile/R.reduceWhile or something.