0
\$\begingroup\$

EDIT

To me JSON.stringify is not the solution here, as it is much slower than recursion

    JSON.stringify(fakeData)
        .replace(/,/g, ';')
        .replace(/:(?={)/g, '')
        .replace(/"/g, '')
//Result
Recursion with reduce x 816,321 ops/sec ±7.38% (80 runs sampled)
JSON.stringify x 578,221 ops/sec ±1.72% (92 runs sampled)
Fastest is Recursion with reduce

I am making a css-in-js library, and I need to use recursion to turn object into string like this:

Input:

const fakeData = {
  a: {
    b: 'c',
    d: 'e'
  }
}

Output:

a{b:c;d:e;}

My recursion function:

const buildKeyframe = (obj) => {
    return Object.entries(obj).reduce((acc, [prop, value]) => {
        if (typeof value === 'string') {
            return `${acc}${prop}:${value};`
        }

        return `${acc}${prop}:{${buildKeyframe(value)}}`
    }, '')
}

This function works, but I think there is room for improvement(e.g. use TCO, avoid using reduce)... How can I write a better function to recurse this data structure?

\$\endgroup\$
2
  • \$\begingroup\$ The function does not work. Eg for object {a:{b:"1",c:{d:"2"}}} the pair b:"1" is missing from the returned string. \$\endgroup\$ – Blindman67 Jan 13 at 4:37
  • \$\begingroup\$ Fixed the function now \$\endgroup\$ – John Winston Jan 13 at 4:47
0
\$\begingroup\$

I agree that JSON.stringify isn't the most appropriate for this problem, not just because it's slower, but also because it's a bit harder to understand. And you're also right in assuming that reduce() isn't the best tool for the job. Using a .map() reads in a much more natural way.

const convert = obj => (
  Object.entries(obj)
    .map(([key, value]) => (
      typeof value === 'string'
        ? `${key}:${value};`
        : `${key}{${convert(value)}}`
    ))
    .join('')
)

console.log(convert({
  a: {
    b: 'c',
    d: 'e'
  },
}))

When optimizing this, think about the use case. While tail-call recursion may or may not be possible, it will make the code much more difficult to read, and it really won't help. The main advantage to tail-call recursion is to save memory on the call stack, and to let a recursive function call into itself an unlimited amount of times while still using just one call-frame worth of memory on the stack. There's no practical chunk of extremely nested CSS that can be written to make tail-call optimization worth it.

In your question, speed seems to be one of your primary concerns, but I'll play a bit of devil's advocate here and ask if it really should be for this use case. How much CSS-in-JS would you need to write before any of these micro-performance improvements even become noticeable? Tens of thousands of lines? Hundreds of thousands? I would suggest just building out the library first, run some benchmarks afterwards, and find out what parts of it actually run slow. Then only worry about fixing the slower areas.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.