I want to print path of object key, dynamically. Here is my code:
const Tree = ({ data }) => {
let path = "onboarding";
return Object.keys(data).map((key) => {
if (Array.isArray(data[key])) {
path = path + "." + key;
return (
<Tree data={data[key]}></Tree>
);
}
if (typeof data[key] === "object") {
path = path + "." + key;
return (
<Tree data={data[key]}></Tree>
);
} else {
path = path + "." + key;
return (
<input defaultValue={data[key]}
style={{ fontWeight: "bold" }}
disabled={!this.props.isEditable}/>
);
}
});
};
and its my data
onboarding: {
enumType: 1,
key: "key1",
steps: [
{
title: "STEP ONE",
description: "des1",
instructions: [
{
icon: "step_power",
label: {
text: "text1",
color: "A11111",
location: "top",
},
},
],
},
{
title: "STEP TWO",
description: "des2",
instructions: [
{
icon: "step_power",
label: {
text: "text2",
color: "A11111",
location: "top",
},
},
],
}
And i want to print path of key for each iteration, expected output :
You have to pass the path along as prop, I made a codebox: https://codesandbox.io/s/old-browser-crgd9r
Edit: adding relevant code here as comment suggested
const Tree = ({ path, data }) => {
// We have 3 cases: Array, Object or neither
// If we have an array we want to cycle through the elements
// and keep track of the index
// If we have an object we want to cicle through the keys
// otherwise just return the value
if (Array.isArray(data)) {
return data.map((element, index) => {
let currentPath = `${path}[${index}]`;
return (
<Tree
path={currentPath}
data={element}
key={currentPath}
></Tree>
);
});
} else if (data instanceof Object) {
return Object.keys(data).map((key) => {
let currentPath = path !== "" ? `${path}.${key}` : key;
return <Tree data={data[key]} path={currentPath} key={currentPath} />;
});
} else {
return (
<div>
<label>{path}</label>
<input defaultValue={data} style={{ fontWeight: "bold" }} />
</div>
);
}
};
Another approach would be to separate out the path-extraction code from the DOM-generation part. This would let you work with more reusable functions.
Here is how I might write the path-extraction piece, in two different ways. First as an all-in one function for your output format:
const stringPaths = (o, p = '') =>
Array .isArray (o)
? o .flatMap ((v, i) => [p, ...stringPaths (v, `${p}[${i}]`)]) .filter (Boolean)
: Object (o) === o
? [
p,
... Object .entries (o) .flatMap (([k, v]) => stringPaths (v, p ? `${p}.${k}` : k))
] .filter (Boolean)
: p
const onboarding = {enumType: 1, key: "key1", steps: [{title: "STEP ONE", description: "des1", instructions: [{icon: "step_power", label: {text: "text1", color: "A11111", location: "top"}}]}, {title: "STEP TWO", description: "des2", instructions: [{icon: "step_power", label: {text: "text2", color: "A11111", location: "top"}}]}]}
console .log (stringPaths (onboarding))
.as-console-wrapper {max-height: 100% !important; top: 0}
And then second, in my preferred style, generating a more widely useful intermediate format ([["enumType"], ["key"], ["steps"], ["steps", 0], ["steps", 0, "title"], ..., ["steps", 1, "instructions", 0, "label", "location"]], and then converting that into your target format:
const getPaths = (o) => Object (o) === o
? Object .entries (o)
.flatMap (([k, v], _, __, k1 = Array .isArray (o) ? Number (k) : k) =>
[[k1], ...getPaths (v) .map (p => [k1, ...p])]
)
: []
const stringPaths = (o) =>
getPaths (o) .map (
(path) => path .reduce ((p, n, i) => i == 0 ? n : Number .isInteger (n) ? `${p}[${n}]` : `${p}.${n}`, ``)
)
const onboarding = {enumType: 1, key: "key1", steps: [{title: "STEP ONE", description: "des1", instructions: [{icon: "step_power", label: {text: "text1", color: "A11111", location: "top"}}]}, {title: "STEP TWO", description: "des2", instructions: [{icon: "step_power", label: {text: "text2", color: "A11111", location: "top"}}]}]}
console .log (stringPaths (onboarding))
.as-console-wrapper {max-height: 100% !important; top: 0}
I think this breakdown make for much nicer coding.