0

I am trying to split the below array

{
    "Base/Brand/0101-color-brand-primary-red": "#fe414d",
    "Base/Brand/0106-color-brand-secondary-green": "#00e6c3",
    "Base/Brand/0102-color-brand-primary-light-gray": "#eaecf0",
    "Base/Brand/0107-color-brand-secondary-black": "#000000",
    "Base/Brand/0103-color-brand-primary-white": "#ffffff",
    "Base/Brand/0108-color-brand-secondary-dark-gray": "#b4b4b4",
    "Base/Brand/0104-color-brand-secondary-blue": "#079fff",
    "Base/Light/Extended/Red/0201-color-extended-900-red": "#7f1d1d",
    "Base/Brand/0105-color-brand-secondary-yellow": "#ffe63b",
    "Base/Light/Extended/Red/0202-color-extended-800-red": "#991b1b"
}

to something like this

{
  "Base": {
    "Brand": {
      "0101-color-brand-primary-red": "#fe414d",
      "0106-color-brand-secondary-green": "#00e6c3",
      "Light": {
        "Extended": {
          "Red": {
            "0201-color-extended-900-red": "#7f1d1d",
            "0202-color-extended-800-red": "#991b1b"
          }
        }
      }
    }
  }
}

Basically I need to split array by '/' and create nested array Please help how can I achieve this.

Peter Seliger
  • 11,747
  • 3
  • 28
  • 37
Sanjeev Hegde
  • 140
  • 13

4 Answers4

1

Use Object.entries to get all entries in the original object. Use split('/') to get an array of the object path keys in the output. Then use reduce on that path array to create levels inside the result, until the very last element in the path is reached. At this point, we set the last path element to the required color value.

const d = {
  "Base/Brand/0101-color-brand-primary-red": "#fe414d",
  "Base/Brand/0106-color-brand-secondary-green": "#00e6c3",
  "Base/Brand/0102-color-brand-primary-light-gray": "#eaecf0",
  "Base/Brand/0107-color-brand-secondary-black": "#000000",
  "Base/Brand/0103-color-brand-primary-white": "#ffffff",
  "Base/Brand/0108-color-brand-secondary-dark-gray": "#b4b4b4",
  "Base/Brand/0104-color-brand-secondary-blue": "#079fff",
  "Base/Light/Extended/Red/0201-color-extended-900-red": "#7f1d1d",
  "Base/Brand/0105-color-brand-secondary-yellow": "#ffe63b",
  "Base/Light/Extended/Red/0202-color-extended-800-red": "#991b1b"
}

const r = Object.entries(d).reduce((a, [p,v]) => 
  (p.split('/').reduce((a,c,i,r) => 
    a[c] ??= i<(r.length-1) ? {} : v, a), a), {})

console.log(r)
Andrew Parks
  • 6,358
  • 2
  • 12
  • 27
  • 2
    You shouldn't give the complete answer/upvote to ppl who don't make any effort to solve the problem themselves or at least the don't post their attempt in the question. That will just promote lazyness – Chris G May 24 '23 at 15:17
1

Since the provided structure is a flat (not nested) object of key-value pairs (entries), one could start aggregating the OP's desired target data-structure by reduceing the source-object's entries.

The initial value of this outer reduce task will be an empty object (literal).

The reducer function of the very same task does receive with each iteration the to be aggregated object reference (which in the very beginning is the initial value / the empty object). It also receives the current iteration's entry which is a key-value pair provided as an array. The key actually is the key-path, a string which can be further split at every occurring slash. The value is the path-value which has to be assigned as last value of any aggregated (partial) object reference.

Thus, one is in need of a second, nested, reduce task which does process each of the key-path' partial key values. The initial value will be always the base or root reference of the to be aggregated object. The inner reduce task does aggregate the received node reference by either accessing an already existing node, or by creating a new (empty node) or by assigning the final path-value to the last created node. It also always does pass this reference as current node into the next iteration step.

const sampleData = {
  "Base/Brand/0101-color-brand-primary-red": "#fe414d",
  "Base/Brand/0106-color-brand-secondary-green": "#00e6c3",
  "Base/Brand/0102-color-brand-primary-light-gray": "#eaecf0",
  "Base/Brand/0107-color-brand-secondary-black": "#000000",
  "Base/Brand/0103-color-brand-primary-white": "#ffffff",
  "Base/Brand/0108-color-brand-secondary-dark-gray": "#b4b4b4",
  "Base/Brand/0104-color-brand-secondary-blue": "#079fff",
  "Base/Light/Extended/Red/0201-color-extended-900-red": "#7f1d1d",
  "Base/Brand/0105-color-brand-secondary-yellow": "#ffe63b",
  "Base/Light/Extended/Red/0202-color-extended-800-red": "#991b1b",
};

function createObjectFromKeyPartialsAndValues(data) {
  return Object
    .entries(data)
    .reduce((root, [keyPath, pathValue]) => {

      keyPath
        .split('/')
        .reduce((node, key, idx, keyList) => {

          const isLastKey = !keyList.hasOwnProperty(idx + 1);
          const value = isLastKey ? pathValue : {};

          const obj = (node[key] ??= value);
          return obj;

        }, root);

      return root;

    }, {});
}

console.log(
  createObjectFromKeyPartialsAndValues(sampleData)
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
Peter Seliger
  • 11,747
  • 3
  • 28
  • 37
1

First off, in the future, please share your own effort when asking a question. I for one wouldn't answer until I saw your own approach if there weren't already decent answers here.


I keep handy a number of utility functions. One of those is hydrate, which accepts an array of what I call pathEntries (similar to the result of Object .entries, but with an array of strings/integers representing the path rather than a single string for the property) and returns an object. With that, we can write a very simple version of this function:

const expand = (o) =>
  hydrate (Object .entries (o) .map (([k, v]) => [k.split('/'), v]))

hydrate is based on setPath which creates a copy (sharing as much as possible) of an object, with a new value at the given path, creating new nodes along the way.

For example,

setPath 
  (['foo', 'bar', 0, 'baz']) 
  (42) 
  ({foo: {qux: {corge: 100, grault: 99}}, waldo: 0})
//=> {foo: {bar: [{baz: 42}], qux: {corge: 100, grault: 99}}, waldo: 0}

Put together, my solution would look like this:

const setPath = ([p, ...ps]) => (v) => (o) =>
  p == undefined ? v : Object .assign (
    Array .isArray (o) || Number .isInteger (p) ? [] : {},
    {...o, [p]: setPath (ps) (v) ((o || {}) [p])}
  )

const hydrate = (xs) =>
  xs .reduce ((a, [p, v]) => setPath (p) (v) (a), {})

const expand = (o) =>
  hydrate (Object .entries (o) .map (([k, v]) => [k.split('/'), v]))

const flatData = {"Base/Brand/0101-color-brand-primary-red": "#fe414d", "Base/Brand/0106-color-brand-secondary-green": "#00e6c3", "Base/Brand/0102-color-brand-primary-light-gray": "#eaecf0", "Base/Brand/0107-color-brand-secondary-black": "#000000", "Base/Brand/0103-color-brand-primary-white": "#ffffff", "Base/Brand/0108-color-brand-secondary-dark-gray": "#b4b4b4", "Base/Brand/0104-color-brand-secondary-blue": "#079fff", "Base/Light/Extended/Red/0201-color-extended-900-red": "#7f1d1d", "Base/Brand/0105-color-brand-secondary-yellow": "#ffe63b", "Base/Light/Extended/Red/0202-color-extended-800-red": "#991b1b"}

console .log (expand (flatData))
.as-console-wrapper {max-height: 100% !important; top: 0}
Scott Sauyet
  • 49,207
  • 4
  • 49
  • 103
0

You can achieve that with a recursive function like the one below. What it does is to split each path by "/" and then check them in a recursive manner and add each section to the output object if they're not already appended.

const list = {
  "Base/Brand/0101-color-brand-primary-red": "#fe414d",
  "Base/Brand/0106-color-brand-secondary-green": "#00e6c3",
  "Base/Brand/0102-color-brand-primary-light-gray": "#eaecf0",
  "Base/Brand/0107-color-brand-secondary-black": "#000000",
  "Base/Brand/0103-color-brand-primary-white": "#ffffff",
  "Base/Brand/0108-color-brand-secondary-dark-gray": "#b4b4b4",
  "Base/Brand/0104-color-brand-secondary-blue": "#079fff",
  "Base/Light/Extended/Red/0201-color-extended-900-red": "#7f1d1d",
  "Base/Brand/0105-color-brand-secondary-yellow": "#ffe63b",
  "Base/Light/Extended/Red/0202-color-extended-800-red": "#991b1b",
};

const result = {};

Object.entries(list).forEach((entry) => {
  const path = entry[0]; // "Base/Brand/0101-color-brand-primary-red"
  const value = entry[1]; // "#fe414d"
  sections = path.split("/"); // ["Base", "Brand", "0101-color-brand-primary-red"]

  // start creating the output object using a recursive function
  sett(result, sections, sections[0], 0, sections.length, value);
});

function sett(parentObj, sections, currentSection, currentIndex, depth, value) {
  // stop if we've reached the end of the path
  if (currentIndex >= depth) return;

  // if not, check if this is the last section of the path
  const isLastSection = sections[currentIndex + 1] === undefined;

  // if there is not already an entry in result for this section (e.g  "Base" or "brand")
  if (!parentObj[currentSection])
    // if it's the end of the path (e.g one of the colors like 0101-color-brand-primary-red) assign it the value else assign {} to it
    parentObj[currentSection] = isLastSection ? value : {};

  // else go deeper in the path
  const nextSection = sections[currentIndex + 1];

  sett(
    parentObj[currentSection],
    sections,
    nextSection,
    currentIndex + 1,
    depth,
    value
  );
}

console.log(result);
mohsen dorparasti
  • 8,107
  • 7
  • 41
  • 61