Search code examples
javascriptarrayslodash

How can I flatten an array targeting only specified children?


I have a collection that looks similar to this:

[
    {
        "name": "Item 1",
        "id": 200244,
        "siblings": []
    }, {
        "name": "Item 2",
        "id": 200134,
        "siblings": []
    }, {
        "name": "Item 3",
        "id": 200179,
        "siblings": [
            {
                "name": "Item 3 SubItem 1",
                "id": 200146
            }
        ]
    }, {
        "name": "Item 4",
        "id": 200133,
        "siblings": []
    }, {
        "name": "Item 5",
        "id": 200135,
        "siblings": [
            {
                "name": "Item 5 SubItem 1",
                "id": 200146
            }
        ]
    }
]

I’m trying to flatten this so that the child siblings values are pulled up a level only when they aren’t empty. The end structure would look like this:

[
    {
        "name": "Item 1",
        "id": 200244
    }, {
        "name": "Item 2",
        "id": 200134
    }, {
        "name": "Item 3",
        "id": 200179
    }, {
        "name": "Item 3 SubItem 1",
        "id": 200146
    }, {
        "name": "Item 4",
        "id": 200133
    }, {
        "name": "Item 5",
        "id": 200135
    }, {
        "name": "Item 5 SubItem 1",
        "id": 200146
    }
]

Suggestions on where to get started with this? I’ve toyed with Lodash’s flatMap without success.


Solution

  • Picking flatMap is the right choice

    const data =
      ...
    
    const flatMap = (f, xs = []) =>
      xs.reduce ((acc, x) => acc.concat (f (x)), [])
    
    flatMap (({ siblings = [], ...item }) => [ item, ...siblings ], data)
    // [ { name: 'Item 1', id: 200244 }
    // , { name: 'Item 2', id: 200134 }
    // , { name: 'Item 3', id: 200179 }
    // , { name: 'Item 3 SubItem 1', id: 200146 }
    // , { name: 'Item 4', id: 200133 }
    // , { name: 'Item 5', id: 200135 }
    // , { name: 'Item 5 SubItem 1', id: 200146 }
    // ]
    

    Using lodash's _.flatMap is almost identical to our implementation above - only the order of the arguments is switched

    const data =
      ...
    
    const result = 
      _.flatMap ( data
                , ({ siblings = [], ...item }) => [ item, ...siblings ]
                )
    
    console.log (result)
    // [ { name: 'Item 1', id: 200244 }
    // , { name: 'Item 2', id: 200134 }
    // , { name: 'Item 3', id: 200179 }
    // , { name: 'Item 3 SubItem 1', id: 200146 }
    // , { name: 'Item 4', id: 200133 }
    // , { name: 'Item 5', id: 200135 }
    // , { name: 'Item 5 SubItem 1', id: 200146 }
    // ]
    

    Here's a full program demonstration using the flatMap implementation provided here. Feel free to use it or any other implementation of your choosing :)

    const flatMap = (f, xs = []) =>
      xs.reduce ((acc, x) => acc.concat (f (x)), [])
      
    const data =
      [ { name: "Item 1"
        , id: 200244
        , siblings: []
        }
      , { name: "Item 2"
        , id: 200134
        , siblings: []
        }
      , { name: "Item 3"
        , id: 200179
        , siblings:
          [ { name: "Item 3 SubItem 1"
            , id: 200146
            }
          ]
        }
      , { name: "Item 4"
        , id: 200133
        , siblings: []
        }
      , { name: "Item 5"
        , id: 200135
        , siblings:
          [ { name: "Item 5 SubItem 1"
            , id: 200146
            }
          ]
        }
      ]
    
    const result = 
      flatMap (({ siblings = [], ...item }) => [ item, ...siblings ], data)
    
    console.log (result)
    // [ { name: 'Item 1', id: 200244 }
    // , { name: 'Item 2', id: 200134 }
    // , { name: 'Item 3', id: 200179 }
    // , { name: 'Item 3 SubItem 1', id: 200146 }
    // , { name: 'Item 4', id: 200133 }
    // , { name: 'Item 5', id: 200135 }
    // , { name: 'Item 5 SubItem 1', id: 200146 }
    // ]