Reputation: 764
Okay, first of all: if I could have phrased my question in a less awkwardly obtuse manner, I would have done so. 😜
I'm struggling with how to describe what I'm trying to do.
Goal
I'm attempting to iterate over an object (specifically, the colors
object in the default TailwindCss config) in order to generate a list of tokens I can pull into a nunjucks template. This is proving more challenging than I'd expected.
I'm probably missing something basic, but I don't know what I don't know, ya know?
I've got the following function that just loops over the colors object:
const tailwindSrc = require("../node_modules/tailwindcss/defaultConfig.js");
const colors = tailwindSrc.theme.colors;
const iterateObject = function (object) {
let tokenList = [];
Object.keys(object).forEach((key) => {
Object.keys(object[key]).forEach((subkey) => {
tokenList.push({
token: {
collectionKey: key,
key: subkey,
value: object[key][subkey],
},
});
});
});
return tokenList;
};
iterateObject(colors);
This is a representative portion of my current result 👇
{ token: { collectionKey: 'gray', key: '50', value: '#f9fafb' } },
{ token: { collectionKey: 'gray', key: '100', value: '#f3f4f6' } },
{ token: { collectionKey: 'gray', key: '200', value: '#e5e7eb' } },
{ token: { collectionKey: 'gray', key: '300', value: '#d1d5db' } },
{ token: { collectionKey: 'gray', key: '400', value: '#9ca3af' } },
{ token: { collectionKey: 'gray', key: '500', value: '#6b7280' } },
{ token: { collectionKey: 'gray', key: '600', value: '#4b5563' } },
{ token: { collectionKey: 'gray', key: '700', value: '#374151' } },
{ token: { collectionKey: 'gray', key: '800', value: '#1f2937' } },
{ token: { collectionKey: 'gray', key: '900', value: '#111827' } }
But I want the gray
"collection" to have a value that is itself an object containing the other two name-value pairs.
Something sort of like the following, I guess. I'm not hung up on the structure below at all; just trying to illustrate more or less how I need things to be grouped.
{
token: {
collectionKey: 'gray',
collectionEntries: {
item: { key: '100', value: '#f3f4f6' }
item: { key: '200', value: '#e5e7eb' }
item: { key: '300', value: '#d1d5db' }
item: { key: '400', value: '#9ca3af' }
item: { key: '500', value: '#6b7280' }
item: { key: '600', value: '#4b5563' }
item: { key: '700', value: '#374151' }
item: { key: '800', value: '#1f2937' }
item: { key: '900', value: '#111827' }
}
}
}
I've made probably 8-10 tries to accomplish what I'm after over the past two days. No dice so far.
It seems to me that I'm not recursing deeply enough to accomplish what I want, but I haven't been able to make things work out in a consistent, repeatable manner as I've tried to go deeper. I asked lodash for help and it laughed at me. I've even tried to create two objects that contain bits of what I need and then merge them together (a la this). No dice.
So, I now turn to you, dear reader.
Sure, a complete solution would be great. But I'll also gladly take links, suggestions, warnings, observations, etc. Any help welcome.
Thanks!
UPDATE
Thanks for your help, Amir!
What you've provided above looks promising. It's not quite there yet, but it's a step in the right direction, I think.
Here's what I'm getting with your code:
[
{
collectionKey: 'pink',
collectionEntries: [
[Object], [Object],
[Object], [Object],
[Object], [Object],
[Object], [Object],
[Object], [Object]
]
},
...
]
I should note that every collectionKey
in the series is 'pink'
. 😁
Looks an array of arrays of objects. Hmm.
I should also note that I changed one line of your code from
let token: {};
to
let token = {};
Any idea what's goin awry above?
Thanks, again!
UPDATE #2
Amir, you asked to see the structure of the data I'm working with. I've simplified things (I hope) by importing the colors
file directly, as opposed to the entire tailwind config.
Here it is:
module.exports = {
black: '#000',
white: '#fff',
rose: {
50: '#fff1f2',
100: '#ffe4e6',
200: '#fecdd3',
300: '#fda4af',
400: '#fb7185',
500: '#f43f5e',
600: '#e11d48',
700: '#be123c',
800: '#9f1239',
900: '#881337',
},
pink: {
50: '#fdf2f8',
100: '#fce7f3',
200: '#fbcfe8',
300: '#f9a8d4',
400: '#f472b6',
500: '#ec4899',
600: '#db2777',
700: '#be185d',
800: '#9d174d',
900: '#831843',
},
...
};
Note that some entries are not nested (black
and white
, for example), but the remainder of them are.
It would great to be able to accomodate differences of that sort. Have tried wrapping two different .push
methods in if
statements, but it seems like when I fix one use case, I break the other.
Thanks again for your help!
Upvotes: 2
Views: 199
Reputation: 2181
Here is an iterative solution that uses object-scan
First we collect all the necessary entries and then we remap to an array
Note that since no "key" exists for simple colors I just left it as null
// const objectScan = require('object-scan');
const myColors = {transparent: "transparent", current: "currentColor", black: "#000", white: "#fff", gray: {50: "#f9fafb", 100: "#f3f4f6", 200: "#e5e7eb", 300: "#d1d5db", 400: "#9ca3af", 500: "#6b7280", 600: "#4b5563", 700: "#374151", 800: "#1f2937", 900: "#111827"}, red: {50: "#fef2f2", 100: "#fee2e2", 200: "#fecaca", 300: "#fca5a5", 400: "#f87171", 500: "#ef4444", 600: "#dc2626", 700: "#b91c1c", 800: "#991b1b", 900: "#7f1d1d"}, yellow: {50: "#fffbeb", 100: "#fef3c7", 200: "#fde68a", 300: "#fcd34d", 400: "#fbbf24", 500: "#f59e0b", 600: "#d97706", 700: "#b45309", 800: "#92400e", 900: "#78350f"}, green: {50: "#ecfdf5", 100: "#d1fae5", 200: "#a7f3d0", 300: "#6ee7b7", 400: "#34d399", 500: "#10b981", 600: "#059669", 700: "#047857", 800: "#065f46", 900: "#064e3b"}, blue: {50: "#eff6ff", 100: "#dbeafe", 200: "#bfdbfe", 300: "#93c5fd", 400: "#60a5fa", 500: "#3b82f6", 600: "#2563eb", 700: "#1d4ed8", 800: "#1e40af", 900: "#1e3a8a"}, indigo: {50: "#eef2ff", 100: "#e0e7ff", 200: "#c7d2fe", 300: "#a5b4fc", 400: "#818cf8", 500: "#6366f1", 600: "#4f46e5", 700: "#4338ca", 800: "#3730a3", 900: "#312e81"}, purple: {50: "#f5f3ff", 100: "#ede9fe", 200: "#ddd6fe", 300: "#c4b5fd", 400: "#a78bfa", 500: "#8b5cf6", 600: "#7c3aed", 700: "#6d28d9", 800: "#5b21b6", 900: "#4c1d95"}, pink: {50: "#fdf2f8", 100: "#fce7f3", 200: "#fbcfe8", 300: "#f9a8d4", 400: "#f472b6", 500: "#ec4899", 600: "#db2777", 700: "#be185d", 800: "#9d174d", 900: "#831843"}};
const remap = (colors) => {
const result = objectScan(['*', '*.*'], {
filterFn: ({ key, value, isLeaf, context }) => {
if (!(key[0] in context)) {
context[key[0]] = [];
}
if (isLeaf) {
context[key[0]].push({
key: key[1] || null,
value
});
}
}
})(colors, {});
return Object.entries(result).map(([k, v]) => ({ token: { [k]: v } }));
};
console.log(remap(myColors));
// => [ { token: { pink: [ { key: '900', value: '#831843' }, { key: '800', value: '#9d174d' }, { key: '700', value: '#be185d' }, { key: '600', value: '#db2777' }, { key: '500', value: '#ec4899' }, { key: '400', value: '#f472b6' }, { key: '300', value: '#f9a8d4' }, { key: '200', value: '#fbcfe8' }, { key: '100', value: '#fce7f3' }, { key: '50', value: '#fdf2f8' } ] } }, { token: { purple: [ { key: '900', value: '#4c1d95' }, { key: '800', value: '#5b21b6' }, { key: '700', value: '#6d28d9' }, { key: '600', value: '#7c3aed' }, { key: '500', value: '#8b5cf6' }, { key: '400', value: '#a78bfa' }, { key: '300', value: '#c4b5fd' }, { key: '200', value: '#ddd6fe' }, { key: '100', value: '#ede9fe' }, { key: '50', value: '#f5f3ff' } ] } }, { token: { indigo: [ { key: '900', value: '#312e81' }, { key: '800', value: '#3730a3' }, { key: '700', value: '#4338ca' }, { key: '600', value: '#4f46e5' }, { key: '500', value: '#6366f1' }, { key: '400', value: '#818cf8' }, { key: '300', value: '#a5b4fc' }, { key: '200', value: '#c7d2fe' }, { key: '100', value: '#e0e7ff' }, { key: '50', value: '#eef2ff' } ] } }, { token: { blue: [ { key: '900', value: '#1e3a8a' }, { key: '800', value: '#1e40af' }, { key: '700', value: '#1d4ed8' }, { key: '600', value: '#2563eb' }, { key: '500', value: '#3b82f6' }, { key: '400', value: '#60a5fa' }, { key: '300', value: '#93c5fd' }, { key: '200', value: '#bfdbfe' }, { key: '100', value: '#dbeafe' }, { key: '50', value: '#eff6ff' } ] } }, { token: { green: [ { key: '900', value: '#064e3b' }, { key: '800', value: '#065f46' }, { key: '700', value: '#047857' }, { key: '600', value: '#059669' }, { key: '500', value: '#10b981' }, { key: '400', value: '#34d399' }, { key: '300', value: '#6ee7b7' }, { key: '200', value: '#a7f3d0' }, { key: '100', value: '#d1fae5' }, { key: '50', value: '#ecfdf5' } ] } }, { token: { yellow: [ { key: '900', value: '#78350f' }, { key: '800', value: '#92400e' }, { key: '700', value: '#b45309' }, { key: '600', value: '#d97706' }, { key: '500', value: '#f59e0b' }, { key: '400', value: '#fbbf24' }, { key: '300', value: '#fcd34d' }, { key: '200', value: '#fde68a' }, { key: '100', value: '#fef3c7' }, { key: '50', value: '#fffbeb' } ] } }, { token: { red: [ { key: '900', value: '#7f1d1d' }, { key: '800', value: '#991b1b' }, { key: '700', value: '#b91c1c' }, { key: '600', value: '#dc2626' }, { key: '500', value: '#ef4444' }, { key: '400', value: '#f87171' }, { key: '300', value: '#fca5a5' }, { key: '200', value: '#fecaca' }, { key: '100', value: '#fee2e2' }, { key: '50', value: '#fef2f2' } ] } }, { token: { gray: [ { key: '900', value: '#111827' }, { key: '800', value: '#1f2937' }, { key: '700', value: '#374151' }, { key: '600', value: '#4b5563' }, { key: '500', value: '#6b7280' }, { key: '400', value: '#9ca3af' }, { key: '300', value: '#d1d5db' }, { key: '200', value: '#e5e7eb' }, { key: '100', value: '#f3f4f6' }, { key: '50', value: '#f9fafb' } ] } }, { token: { white: [ { key: null, value: '#fff' } ] } }, { token: { black: [ { key: null, value: '#000' } ] } }, { token: { current: [ { key: null, value: 'currentColor' } ] } }, { token: { transparent: [ { key: null, value: 'transparent' } ] } } ]
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/[email protected]"></script>
Disclaimer: I'm the author of object-scan
Upvotes: 0
Reputation: 686
you can do things like that :
const tailwindSrc = require("../node_modules/tailwindcss/defaultConfig.js");
const colors = tailwindSrc.theme.colors;
const iterateObject = function (object) {
let tokenList = [];
let token = {}
Object.keys(object).forEach((key) => {
token['collectionKey']= key,
token['collectionEntries'] = []
Object.keys(object[key]).forEach((subkey) => {
token.collectionEntries.push({
key: subkey,
value: object[key][subkey],
});
});
tokenList.push({token : token});
});
return tokenList;
};
iterateObject(colors);
but result here will be :
{
token: {
collectionKey: 'gray',
collectionEntries: [
{ key: '100', value: '#f3f4f6' }
{ key: '200', value: '#e5e7eb' }
{ key: '300', value: '#d1d5db' }
{ key: '400', value: '#9ca3af' }
{ key: '500', value: '#6b7280' }
{ key: '600', value: '#4b5563' }
{ key: '700', value: '#374151' }
{ key: '800', value: '#1f2937' }
{ key: '900', value: '#111827' }
]
}
}
so you can avoid duplication of key : 'item'
Upvotes: 1
Reputation: 50797
Now that we have your input format, we can convert it fairly simply.
Here is one technique, assuming that the entries like transparent
and black
should return output as much like the others as possible, and further assuming that collectionEntries
should be an array. (Your format doesn't work, repeating the key item
over and over in your object.)
const collectColors = (colors) => Object .entries (colors)
.map (([k, v]) => (
Object (v) === v
? {token: {
collectionKey: k,
collectionEntries: Object .entries (v) .map (([key, value]) => ({item: {key, value}}))
}}
: {token: {collectionKey: k, collectionEntries: []}}
))
const colors = {transparent: "transparent", current: "currentColor", black: "#000", white: "#fff", gray: {50: "#f9fafb", 100: "#f3f4f6", 200: "#e5e7eb", 300: "#d1d5db", 400: "#9ca3af", 500: "#6b7280", 600: "#4b5563", 700: "#374151", 800: "#1f2937", 900: "#111827"}, red: {50: "#fef2f2", 100: "#fee2e2", 200: "#fecaca", 300: "#fca5a5", 400: "#f87171", 500: "#ef4444", 600: "#dc2626", 700: "#b91c1c", 800: "#991b1b", 900: "#7f1d1d"}, yellow: {50: "#fffbeb", 100: "#fef3c7", 200: "#fde68a", 300: "#fcd34d", 400: "#fbbf24", 500: "#f59e0b", 600: "#d97706", 700: "#b45309", 800: "#92400e", 900: "#78350f"}, green: {50: "#ecfdf5", 100: "#d1fae5", 200: "#a7f3d0", 300: "#6ee7b7", 400: "#34d399", 500: "#10b981", 600: "#059669", 700: "#047857", 800: "#065f46", 900: "#064e3b"}, blue: {50: "#eff6ff", 100: "#dbeafe", 200: "#bfdbfe", 300: "#93c5fd", 400: "#60a5fa", 500: "#3b82f6", 600: "#2563eb", 700: "#1d4ed8", 800: "#1e40af", 900: "#1e3a8a"}, indigo: {50: "#eef2ff", 100: "#e0e7ff", 200: "#c7d2fe", 300: "#a5b4fc", 400: "#818cf8", 500: "#6366f1", 600: "#4f46e5", 700: "#4338ca", 800: "#3730a3", 900: "#312e81"}, purple: {50: "#f5f3ff", 100: "#ede9fe", 200: "#ddd6fe", 300: "#c4b5fd", 400: "#a78bfa", 500: "#8b5cf6", 600: "#7c3aed", 700: "#6d28d9", 800: "#5b21b6", 900: "#4c1d95"}, pink: {50: "#fdf2f8", 100: "#fce7f3", 200: "#fbcfe8", 300: "#f9a8d4", 400: "#f472b6", 500: "#ec4899", 600: "#db2777", 700: "#be185d", 800: "#9d174d", 900: "#831843"}}
console .log (
collectColors (colors)
)
.as-console-wrapper {max-height: 100% !important; top: 0}
It's easy enough to make variants. If you just want to skip the ones like transparent
, then you use this variant:
const collectColors = (colors) => Object .entries (colors)
.filter (([_, v]) => Object (v) === v)
.map (([k, v]) => ({
token: {
collectionKey: k,
collectionEntries: Object .entries (v) .map (([key, value]) => ({item: {key, value}}))
}
}))
Or if you wanted collectionEntries
to be an array containing key
-value
objects (what is the use of that extra items
wrapper anyway?), you could replace the relevant line with
collectionEntries: Object .entries (v) .map (([key, value]) => ({key, value}))
And so forth.
Upvotes: 1
Reputation: 366
Unless I'm missing something it shouldn't be much more than something like this.
Please note in your example or desired results that would likely need to be an array as you are duplicating the object key "item". The below will produce an object as you wish the but the key will be the numeric value. In the case of those values that aren't objects I just chose to use the numeric value 0 but you could set that to whatever you wish.
const tokens = Object.keys(obj).reduce((result, key) => {
let val = obj[key];
// Used key as "0" here but you should define
// what you want that to be when they aren't
// objects.
if (typeof val === 'string')
val = { 0: val };
result[key] = {
collectionKey: key,
collectionEntries: Object.keys(val).reduce((items, k) => {
// parseInt here to maintain as numeric value.
items[k] = { key: parseInt(k), value: val[k] };
return items;
}, {})
};
return result;
}, {});
Upvotes: 0