Home > database >  How to merge JSON objects based on key/value pair?
How to merge JSON objects based on key/value pair?

Time:10-02

I need to combine results from two different documents in Mongo. I have a function like this:

async function getReviewsByUserId (req, res) {
  const { userId } = req.params

  const reviews = await Review.find({ userId }).lean() || []

  return res.status(200).send(reviews.reverse())
}

The reviews array looks like this:

{
    "_id" : ObjectId("1263b55ef2cdd3ebb0654d1dd"),
    "launchId" : "7fb83b40-7c6f-4099-aaed-fe9d0dc03111",
    "userId" : "1",
}
{
    "_id" : ObjectId("6355565cf5ef2cddebb065584"),
    "launchId" : "12b53940-136f-3399-aaed-fe9d0dc05473",
    "userId" : "7fb83b40-7c6f-4099-aaed-fe9d0dc03112",
}

I need to use the launchId from each review, look up a launch object from my mongo database, and combine that with the correct object in the reviews array.

Example of what I mean:

async function getReviewsByUserId (req, res) {
  const { userId } = req.params

  const reviews = await Review.find({ userId }).lean() || []

  const launches = await Launch.find(/* find all launches where launch._id is equal to reviews.launchId*/)

  return res.status(200).send(launches.reverse())
}

So if launches data looks like this (and launches is also an array of ALL launches):

{
    "_id" : "12b53940-136f-3399-aaed-fe9d0dc05473",
    "name" : "The Park",
}

Then how can I merge this with the reviews payload where the launch._id == reviews.launchId so that the final data looks like this:

{
    "_id" : ObjectId("1263b55ef2cdd3ebb0654d1dd"),
    "launchId" : "7fb83b40-7c6f-4099-aaed-fe9d0dc03111",
    "userId" : "1",
}
{
    "_id" : ObjectId("6355565cf5ef2cddebb065584"),
    "launchId" : "12b53940-136f-3399-aaed-fe9d0dc05473",
    "userId" : "7fb83b40-7c6f-4099-aaed-fe9d0dc03112",
    "launch": {
        "_id" : "12b53940-136f-3399-aaed-fe9d0dc05473",
        "name" : "The Park",
    }
}

CodePudding user response:

This could be achieved by using aggregate pipelines.

  1. Filter reviews by userId in $match stage
  2. $lookup for launches
  3. $unwind the launch array to an object

The solution could be:

  async function getReviewsByUserId(req, res) {
    const { userId } = req.params;

    const launches = await Review.aggregate([
      {
        $match: {
          userId
        }
      },
      {
        $lookup: {
          from: "launches",
          localField: "launchId",
          foreignField: "_id",
          as: "launch"
        }
      },
      {
        $unwind: {
          path: "$launch",
          preserveNullAndEmptyArrays: true
        }
      }
    ]);

    return res.status(200).send(launches.reverse());
  }

CodePudding user response:

You can map() your reviews, and for each item you can use find() on the launches array to check if the launch id matches the current review id.

If there is no match, just return the review unaltered; if you get a match, you add the launches property to the current review and return it.

function ObjectId(oid) {
  return oid
}

const reviews = [{
    "_id": ObjectId("1263b55ef2cdd3ebb0654d1dd"),
    "launchId": "7fb83b40-7c6f-4099-aaed-fe9d0dc03111",
    "userId": "1",
  },
  {
    "_id": ObjectId("6355565cf5ef2cddebb065584"),
    "launchId": "12b53940-136f-3399-aaed-fe9d0dc05473",
    "userId": "7fb83b40-7c6f-4099-aaed-fe9d0dc03112",
  }
]

const launches = [{
  "_id": "12b53940-136f-3399-aaed-fe9d0dc05473",
  "name": "The Park",
}]


const res = reviews.map(r => {
  const found = launches.find(l => l._id === r.launchId)

  if (found) {
    r.launch = {
      _id: found._id,
      name: found.name
    }
    return r
  } else {
    return r
  }
})

console.log(res)

  • Related