Home > Software engineering >  Merge and sort arrays of objects JS/TS/Svelte - conceptual understanding
Merge and sort arrays of objects JS/TS/Svelte - conceptual understanding

Time:05-08

The goal is to display a recent activity overview.

As an example: I would like it to display posts, comments, users. A post, comment and user object live in its corresponding arrays. All of the objects have a timestamp (below createdAt), but also keys that the objects from the different arrays don't have. The recent activites should be sorted by the timestamp.
(Ultimately it should be sortable by different values, but first I would like to get a better general understanding behind merging and sorting arrays / objects and not making it to complicated)

I thought of somehow merging the arrays into something like an activity array, then sorting it and looping over it and conditionally output an object with its keys depending on what kind of object it is?

If someone is willing to deal with this by giving an example, it would make my day. The best thing I could imagine would be a svelte REPL that solves this scenario. Anyway I'm thankful for every hint. There probably already are good examples and resources for this (I think common) use case that I didn't find. If someone could refer to these, this would also be superb.

The example I'm intending to use to get this conceptual understanding:

const users = [
  { id: 'a', name: 'michael', createdAt: 1 }, 
  { id: 'b', name: 'john', createdAt: 2 },
  { id: 'c', name: 'caren', createdAt: 3 }
]
const posts = [
  { id: 'd', topic: 'food', content: 'nonomnom' createdAt: 4 },
  { id: 'e', name: 'drink', content: 'water is the best' createdAt: 5 },
  { id: 'f', name: 'sleep', content: 'i miss it' createdAt: 6 }
]
const comments = [
  { id: 'g', parent: 'd', content: 'sounds yummy' createdAt: 7 },
  { id: 'h', parent: 'e', content: 'pure life' createdAt: 8 },
  { id: 'i', parent: 'f', content: 'me too' createdAt: 9 }
]

Edit: it would have been a bit better example with more descriptive id keys, like userId and when a post and comment object contains the userId. However, the answers below make it very understandable and applicable for "real world" use cases.

CodePudding user response:

Here's an approach using simple 'helper classes' so that the different objects can be distinguished when displayed REPL

<script>
    class User {
        constructor(obj){
            Object.assign(this, obj)
        }
    }
    class Post {
        constructor(obj){
            Object.assign(this, obj)
        }
    }
    class Comment {
        constructor(obj){
            Object.assign(this, obj)
        }
    }

    const users = [
        { id: 'a', name: 'michael', createdAt: 1652012110220 }, 
        { id: 'b', name: 'john', createdAt: 1652006110121 },
        { id: 'c', name: 'caren', createdAt: 1652018110220 }
    ].map(user => new User(user))

    const posts = [
        { id: 'd', topic: 'food', content: 'nonomnom', createdAt: 1652016900220 },
        { id: 'e', topic: 'drink', content: 'water is the best', createdAt: 1652016910220 },
        { id: 'f', topic: 'sleep', content: 'i miss it', createdAt: 1652016960220 }
    ].map(post => new Post(post))

    const comments = [
        { id: 'g', parent: 'd', content: 'sounds yummy', createdAt: 1652116910220 },
        { id: 'h', parent: 'e', content: 'pure life', createdAt: 1652016913220 },
        { id: 'i', parent: 'f', content: 'me too', createdAt: 1652016510220 }
    ].map(comment => new Comment(comment))

    const recentActivities = users.concat(posts).concat(comments).sort((a,b) => b.createdAt - a.createdAt)  
</script>

<ul>
    {#each recentActivities as activity}        
    <li>
        {new Date(activity.createdAt).toLocaleString()} - 
            {#if activity.constructor.name === 'User'}
                User - {activity.name}
            {:else if activity.constructor.name === 'Post'}
                Post - {activity.topic}
            {:else if activity.constructor.name === 'Comment'}
                Comment - {activity.content}
            {/if}
    </li>
    {/each}
</ul>

CodePudding user response:

This is fun to think about and it's great that you're putting thought into the architecture of the activity feed.

I'd say you're on the right track with how you're thinking of approaching it.

Think about:

  1. How you want to model the data for use in your application
  2. How you process that model
  3. Then think about how you display it

You have 3 different types of data and you have an overall activity feed you want to create. Each type has createdAt in common.

There's a couple of ways you could do this:

  1. Simply merge them all into one array and then sort by createdAt
const activities = [...users, ...posts, ...comments];
activities.sort((a,b) => b.createdAt - a.createdAt); // Sort whichever way you want

The tricky part here is when you're outputting it, you'll need a way of telling what type of object each element in the array is. For users, you can look for a name key, for posts you could look for the topic key, for comments you could look for the parent/content keys to confirm object type but this is a bit of a brittle approach.

Let's try to see if we can do better.

  1. Give each activity object an explicit type variable.
const activities = [...users.map((u) => ({...u, type: 'user'})), ...posts.map((u) => ({...u, type: 'post'})), ...comments.map((u) => ({...u, type: 'comment'}))];

Now you can easily tell what any given element in the whole activities array is based on its type field.

As a bonus, this type field can also let you easily add a feature to filter the activity feed down to just certain types! And it also makes it much simpler to add new types of activities in the future.

Here's a typescript playground showing it and logging the output.

As a typesafe bonus, you can add types in typescript to reinforce the expected data types:

eg.

type Common = {
  id: string;
  createdAt: number;
}

type User = {
  name: string;
} & Common;

type Post = {
  topic: string;
  content: string;
} & Common;

type UserComment = {
  parent: string;
  content: string;
} & Common;

type Activity = User | Post | UserComment;
  • Related