I would choose a version like this:
// helper functions
const mapObject = (fn) => (o) => Object .fromEntries (
Object .entries (o) .map (([k, v]) => [k, fn (v)])
)
const map = (fn) => (xs) => xs .map(x => fn (x))
// main functions
const deepestValue = (o) =>
typeof o .value== "object" ? deepestValue (o .value) : o .value
const transform = map (mapObject (deepestValue))
// demo
const input=[{accountName: {type: "link", value: {value: "1234567890123456789", to: "/q?Id=1237896540789654"}}, bank: {type: "text", value: "Foo Bank"}}, {accountName: {type: "link", value: {value: "9234567890123456789", to: "/q?Id=9234567890123456789"}}, bank: {type: "text", value: "Foo Bank"}}]
console.log (transform (input))
We can build this in a step-by-step manner:
We start by writing a simple recursive deepestValue
function like this:
const deepestValue = (o) =>
typeof o .value== "object" ? deepestValue (o .value) : o .value
That could be used to write our transformation function this way:
const transform = (xs) => xs .map (x => Object.fromEntries (
Object .entries (x) .map (([k, v]) => [k, deepestValue (v)])
))
(If your environment does not support the relatively new Object.fromEntries
, it's quite easy to shim.)
We could stop there. But it's worth noting that this entries
->map
->fromEntries
dance is a very reusable pattern. It's basically mapping a function over the properties of an object. Perhaps a good name is mapObject
. We can simply extract that and use it like this:
const mapObject = (fn) => (o) => Object .fromEntries (
Object .entries (o) .map (([k, v]) => [k, fn (v)])
)
const transform = (xs) => xs .map (mapObject (deepestValue))
But there is another function we might want to abstract out of this, a map
function that works like Array.prototype.map
but which is a stand-alone function. That is also very easy to write.
const map = (fn) => (xs) => xs .map (x => fn (x))
(I don't write simply (fn) => (xs) => xs .map (fn)
for reasons described many places, including in Why does parseInt yield NaN with Array#map?.)
With this, we can now write the snippet above.
Functions such as map
and mapObject
can go in our personal utility libraries to be reused in various places in our application. Libraries such as Underscore or Ramda (disclaimer: I'm a Ramda author) collect such things into useful collections. Ramda's map
actually covers both these cases, so in Ramda we might write const transform = map (map (deepestValue))
.
Note that I would not extract these helper functions on the basis of single cases. If I'd never seen the pattern before, I would be perfectly happy with the first version of transform
. But I've done similar things often enough that this abstraction makes sense to me. And I think it always helps to break things down into really simple pieces.