Creating Data Structures with Array.reduce()
Tags: javascript, programming, tutorial
Published: 2021-10-20 00:05:06

I recently saw an older youtube video on using array.reduce to build data structures on the fly in ways that you may find surprising or unintuitive. Normally we always think of reduce when it comes to doing math on array elements or something similar, and while that is a great use case, lets explore some of the more unique ways leverage this array method.

### Create An Object From An Array

To do this you could use any old loop, but lets say you need to build an object of objects, with the properties equal to one of the objects property values, eg.

```javascript
// this is the data we have...
const data = [
  {
    id: 1,
    name: 'New Post',
    author: 'Jeff',
    date: '2021-05-01'
  },
  {
    id: 2,
    name: 'Newer Post',
    author: 'Sabrina',
    date: '2021-05-02'
  },
  {
    id: 3,
    name: 'Newest Post',
    author: 'Mike',
    date: '2021-05-02'
  },
  {
    id: 4,
    name: 'Fourth Post',
    author: 'Mike',
    date: '2021-03-02'
  },
  {
    id: 5,
    name: 'Fifth Post',
    author: 'Sabrina',
    date: '2021-08-09'
  }
];

// this is the structure we want...
const authors = {
  jeff: {
    posts: [
      {
        id: 1,
        title: 'Post Name',
        created_at: '2021-05-01'
      }
    ]
  },
  sabrina: {
    posts: [ ...posts ]
  },
  mike: {
    posts: [ ...posts ]
  },
}
```

Basically we want build an object containing author objects that each contain an array of any posts they've written. A map won't do because we don't really want to return an array of course (contrived on purpose for the example) and we would like to easily aggregate them into the appropriate arrays keyed by the name. Also the specification says we should rename the `date` to `created_at` and `name` to `title`.

So how could we reduce this array to the data structure specified in a functional way and it make sense to the reader of our code?

Remember that array.reduce will return any value you want it to...aha...so we want to return an object.

```javascript
reduce((previousValue, currentValue) => { ... }, initialValue)
```

This above is the function we'll use. Notice the initialValue argument. That'll set the stage for our returned value.

#### Let's Reduce

```javascript
(data || []).reduce((acc, curr) => ({}), {});
```

This is our basic setup. We'll pass `acc` or the accumulated value and the `curr` or current array element into the callback, returning an expression, which is an object literal. Our default value you may notice is an empty object.

```javascript
const result = (data || []).reduce((acc, curr) => ({
  ...acc,
  [curr?.author?.toLowerCase()]: {
    ...acc[curr?.author?.toLowerCase()],
    posts: [
      ...(acc[curr?.author?.toLowerCase()]?.posts || []),
      {
        id: curr?.id,
        title: curr?.name,
        created_at: curr?.date
      }
    ]
  }
}), {});
```

This is our workhorse above. We'll step through each stage of working with the data. It's done in a functional way meaning we're copying data, never overwriting it.

##### First, we spread the value of `acc` into the object that we're returning
```javascript
const result = data.reduce((acc, curr) => ({
  ...acc,
  // more stuffs
}), {});
```

##### Second, we'll use the computed value to set our property name of an author

```javascript
const result = data.reduce((acc, curr) => ({
  ...acc,
  [curr?.author?.toLowerCase()]: {
    // more stuffs
  }
}), {});
```
This way it ensures we're preserving any objects that don't match the computed property name in the carry. We use the toLowerCase bc the spec says it wants lowercase author names as the object property.

##### Third, we'll set and spread on the posts property of a computed name author object

```javascript
const result = data.reduce((acc, curr) => ({
  ...acc,
  [curr?.author?.toLowerCase()]: {
    ...acc[curr?.author?.toLowerCase()],
    posts: [
     // we'll use a short circuit since the posts property won't e 
     // exist on the first of any given author, just spread an 
     // empty array
      ...(acc[curr?.author?.toLowerCase()]?.posts || []),
     // add our object with the specified data mapping
      {
        id: curr?.id,
        title: curr?.name,
        created_at: curr?.date
      }
    ]
  }
}), {});
```

##### Success

If we serialize the result and pretty print it we'd get....

```json
{
    "jeff": {
        "posts": [
            {
                "id": 1,
                "title": "New Post",
                "created_at": "2021-05-01"
            }
        ]
    },
    "sabrina": {
        "posts": [
            {
                "id": 2,
                "title": "Newer Post",
                "created_at": "2021-05-02"
            },
            {
                "id": 5,
                "title": "Fifth Post",
                "created_at": "2021-08-09"
            }
        ]
    },
    "mike": {
        "posts": [
            {
                "id": 3,
                "title": "Newest Post",
                "created_at": "2021-05-02"
            },
            {
                "id": 4,
                "title": "Fourth Post",
                "created_at": "2021-03-02"
            }
        ]
    }
}
```

Please leave me any thoughts on optimization or better ways to accomplish the given task. The primary focus of this is to get people thinking about array.reduce in interesting ways but I always enjoy learning new or better ways to do stuff.