[SOLVED] How to create a builder function that transforms key-value data, and is supplied with both the data and the structure/format of the output

Issue

This is a follow-up question to: How to build/transform an object from a previous object

Given data in a key-value format, I want to transform it into a new structure.

// given data example #1
const dataAnimals = {"monkeys": 10, "alligators": 20}

// given data example #2
const dataColors = {"red": 100, "green": 4, "yellow": 17}

And the new structure of the output can be chosen from any prespecified format.
If we stick with dataAnimals as the input, there are three possible output formats:

structure type 1

[
    {
        label: {
            en: "monkeys"
        },
        value: 10
    },
    {
        label: {
            en: "alligators"
        },
        value: 20
    }
]

structure type 2

[
    {
        count: 10,
        label: {
            en: "monkeys"
        }
    },
    {
        count: 20,
        label: {
            en: "alligators"
        }
    }
]

structure type 3

[
    {
        count: 10,
        value: "monkeys"
    },
    {
        count: 20,
        value: "alligators"
    }
]

I’m likely to have several more types of structures.


I wonder if there’s a way to supply the chosen/desired structure (i.e., pass the structure to a builder function alongside the input data itself).

So far, using ramda I could only write 3 separate builder functions with the structure being hard-coded inside each:

const buildArr1 = R.pipe(R.toPairs, R.map(R.applySpec({value: R.nth(1), label: {en: R.nth(0)}})))
const buildArr2 = R.pipe(R.toPairs, R.map(R.applySpec({count: R.nth(1), label: {en: R.nth(0)}})))
const buildArr3 = R.pipe(R.toPairs, R.map(R.applySpec({value: R.nth(0), count: R.nth(1)})))

buildArr1(dataAnimals)
buildArr2(dataAnimals)
buildArr3(dataAnimals)

The differences between buildArr1(), buildArr2(), and buildArr3() are subtle and only in terms of the structure inside applySpec(). So it got me thinking, whether it’s possible to have just one function buildArr(), that takes as input both the data to transform and the format to be returned.

// pseudo-code for desired code
buildArr(dataAnimals, structureType1)
// which will return
// [
//     {
//         label: {
//             en: "monkeys"
//         },
//         value: 10
//     },
//     {
//         label: {
//             en: "alligators"
//         },
//         value: 20
//     }
// ]


buildArr(dataAnimals, structureType2)

// [
//     {
//         count: 10,
//         label: {
//             en: "monkeys"
//         }
//     },
//     {
//         count: 20,
//         label: {
//             en: "alligators"
//         }
//     }
// ]

Is there any proper way to do something like that? I’m also not sure what should be the nature of structureType1/structureType2 being passed. An object? Or rather a function that "knows to interact" with the input data?

Please note that while this question revolves around ramda, it seems (to me) to be broader than a specific library.


EDIT


User @Scott Sauyet has nicely pointed out that writing the individual builders (i.e., buildArr1(), buildArr2, etc.) could be achieved with plain JS. This speaks to my point above, that this isn’t necessarily a ramda question.

const buildArr1PlainJS = (o) => ({data: Object .entries (o) .map (([k, v]) => ({value: v, label: {en: k}}))})
const buildArr2PlainJS = (o) => ({data: Object .entries (o) .map (([k, v]) => ({count: v, label: {en: k}}))})
const buildArr3PlainJS = (o) => ({data: Object .entries (o) .map (([k, v]) => ({value: k, count: v}))})

Solution

I think you can do this fairly easily by passing in a formatting function to a generic processor. It might look something like this:

// main function
const transform = (fn) => (o) => Object .entries (o) .map (([k, v]) => fn (k, v))

// formatting functions
const format1 = transform ((k, v) => ({label: {en: k}, value: v}))
const format2 = transform ((k, v) => ({count: v, label: {en: k}}))
const format3 = transform ((k, v) => ({count: v, value: k}))

// input data
const inputs = {
  dataAnimals: {"monkeys": 10, "alligators": 20},
  dataColors: {"red": 100, "green": 4, "yellow": 17},
};

// demo
Object .entries (inputs) .forEach (([name, input]) => [format1, format2, format3] .forEach ((format, i) =>
  console .log (`${name}, format ${i + 1}:`, format (input))
))
.as-console-wrapper {max-height: 100% !important; top: 0}

Again, I don’t see Ramda adding much value here, although we could of course do this with Ramda’s tools. We might write:

const transform = (fn) => (o) => map (unapply (fn)) (toPairs (o))

Or if you had a point-free fetish, even this:

const transform = compose (flip (o) (toPairs), map, unapply)

But I don’t think these — especially the latter — add anything useful.

Answered By – Scott Sauyet

Answer Checked By – Clifford M. (BugsFixing Volunteer)

Leave a Reply

Your email address will not be published.