0

I'm trying to add a "path" to all nested objects using their ancestor's attributes. Essentially I want to represent a "path" of the hierarchical structure to a nested object using a concatenation of its parent object's attribute values instead of using keys (like I could with dot notation, lodash, etc).

What I've tried:

interface SimpleObject {
    name : string
    slug : string
    slug_path? : string
    fields? : SimpleObject[]
}


const addNestedObjSlug = (data:SimpleObject[], parents:SimpleObject[] = []) => {

    data.map((obj) => {
        obj.slug_path = parents.length > 0 ? `${parents.map(({slug}) => slug).join('.')}.${obj.slug}` : obj.slug
        if(obj.fields && obj.fields.length > 0) {
            parents.push(obj)

            // I think the issue is here, that I probably need to set parents to an empty array at some point
            return addNestedObjSlug(obj.fields, parents)
        }
    })

    return data
}

const desiredResult = addNestedObjSlug([
    {
        name : 'Item 1',
        slug : 'i1',
        fields : [
            {
                name : 'Item 1 - 1',
                slug : 'i1-1'
            },
            {
                name : 'Item 1 - 2',
                slug : 'i1-2'
            },
            {
                name : 'Item 1 - 3',
                slug : 'i1-3'
            }
        ]
    },
    {
        name : 'Item 2',
        slug : 'i2',
        fields : [
            {
                name : 'Item 2 - 1',
                slug : 'i2-1',
                fields : [
                    {
                        name : 'Item 2 - 1 - 1',
                        slug : 'i2-1-1'
                    }
                ]
            }
        ]
    }
])

My expected result is:

[
    {
        "name": "Item 1",
        "slug": "i1",
        "fields": [
            {
                "name": "Item 1 - 1",
                "slug": "i1-1",
                "slug_path": "i1.i1-1"
            },
            {
                "name": "Item 1 - 2",
                "slug": "i1-2",
                "slug_path": "i1.i1-2"
            },
            {
                "name": "Item 1 - 3",
                "slug": "i1-3",
                "slug_path": "i1.i1-3"
            }
        ],
        "slug_path": "i1"
    },
    {
        "name": "Item 2",
        "slug": "i2",
        "fields": [
            {
                "name": "Item 2 - 1",
                "slug": "i2-1",
                "fields": [
                    {
                        "name": "Item 2 - 1 - 1",
                        "slug": "i2-1-1",
                        "slug_path": "i2.i2-1.i2-1-1"
                    }
                ],
                "slug_path": "i2.i2-1"
            }
        ],
        "slug_path": "i2"
    }
]

But instead I get the following, where the original nested object's slug is part of the new slug_path attribute of objects which are not ancestors.

[
    {
        "name": "Item 1",
        "slug": "i1",
        "fields": [
            {
                "name": "Item 1 - 1",
                "slug": "i1-1",
                "slug_path": "i1.i1-1" // correct
            },
            {
                "name": "Item 1 - 2",
                "slug": "i1-2",
                "slug_path": "i1.i1-2" // correct
            },
            {
                "name": "Item 1 - 3",
                "slug": "i1-3",
                "slug_path": "i1.i1-3" // correct
            }
        ],
        "slug_path": "i1" // correct
    },
    {
        "name": "Item 2",
        "slug": "i2",
        "fields": [
            {
                "name": "Item 2 - 1",
                "slug": "i2-1",
                "fields": [
                    {
                        "name": "Item 2 - 1 - 1",
                        "slug": "i2-1-1",
                        "slug_path": "i1.i2.i2-1.i2-1-1" // incorrect
                    }
                ],
                "slug_path": "i1.i2.i2-1" // incorrect
            }
        ],
        "slug_path": "i1.i2" // incorrect
    }
]

2 Answers 2

2

If you don't mind also adding the slugPath to your root elements (which I personally think is better for consistency in any case), then this should do:

const addSlugPath = (items, path = []) => 
  items .map (({slug, fields, ...rest}, _, __, newPath = [...path, slug]) => ({
    ... rest, 
    slug,
    slugPath: newPath .join ('.'),
    ... (fields ? {fields: addSlugPath (fields, newPath)} : {})
  }))

const input = [{name: "Item 1", slug: "i1", fields: [{name: "Item 1 - 1", slug: "i1-1"}, {name: "Item 1 - 2", slug: "i1-2"}, {name: "Item 1 - 3", slug: "i1-3"}]}, {name: "Item 2", slug: "i2", fields: [{name: "Item 2 - 1", slug: "i2-1", fields: [{name: "Item 2 - 1 - 1", slug: "i2-1-1"}]}]}]

console .log (addSlugPath (input))
.as-console-wrapper {max-height: 100% !important; top: 0}

Note that we don't mutate any input data here but only return a copy with the slugPath added appropriately.

We keep an array of nodes in path and add to it at each recursive level. The _ and __ parameters are just to cover the index and full array that map supplies to its callback function.

Sign up to request clarification or add additional context in comments.

Comments

1

You could take a closure over the path.

const
    add = p => o => {
        const
            slug_path = p + (p && '.') + o.slug,
            fields = (o.fields || []).map(add(slug_path));

        return { ...o, slug_path, ...(fields.length ? { fields } : {}) };
    },
    addNestedObjSlug = array => array.map(add(''));

console.log(addNestedObjSlug([{ name: 'Item 1', slug: 'i1', fields: [{ name: 'Item 1 - 1', slug: 'i1-1' }, { name: 'Item 1 - 2', slug: 'i1-2' }, { name: 'Item 1 - 3', slug: 'i1-3' }] }, { name: 'Item 2', slug: 'i2', fields: [{ name: 'Item 2 - 1', slug: 'i2-1', fields: [{ name: 'Item 2 - 1 - 1', slug: 'i2-1-1' }] }] }]));
.as-console-wrapper { max-height: 100% !important; top: 0; }

1 Comment

Thanks so much for this, not only the answer but also for reminding me of how much I have to learn.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.