mutAnT
mutAnT

Reputation: 464

Merge Two Array of Struct in Swift

I am receiving response from server like below. Here, review and rating are in separate object and the booking_id is same.

{
    "status": "Success",
    "records": [
        {
            "user_name": "123user",
            "review": "Ggg",
            "booking_id": "Booking_23749",
            "review_id": "review_38405",
            "status": "active"
        },
        {
            "_id": "5e0c43ea5bd0377f4cfdfa19",
            "user_name": "123user",
            "rating": 5,
            "booking_id": "Booking_23749"
        }
    ]
}

Then I have created a Modal struct to store the data coming from server.

struct ReviewRecord: Codable, Equatable {

    static func == (lhs: ReviewRecord, rhs: ReviewRecord) -> Bool {
        return lhs.bookingID == rhs.bookingID
    }

    let userName, review, bookingID, reviewID: String?
    let status, id: String?
    let rating: Int?

    enum CodingKeys: String, CodingKey {
        case userName = "user_name"
        case review
        case bookingID = "booking_id"
        case reviewID = "review_id"
        case status
        case id = "_id"
        case rating
    }
}

and I am using it as,

var reviewsData = [ReviewRecord]() // appending all data received in reviewsData

Question : How to merge the two objects to create one final object. So basically, I want it to make it like this:

[
"review": "Ggg",
"review_id": "review_38405",
"status": "active"
"_id": "5e0c43ea5bd0377f4cfdfa19",
"user_name": "123user",
"rating": 5,
"booking_id": "Booking_23749"
]

UPDATE

More detail on what actually I am trying to achieve:

if there are four records in json like this :

    {
        "user_name": "123user",
        "review": "Ggg",
        "booking_id": "Booking1",
        "review_id": "review_38405",
        "status": "active"
    },
    {
        "_id": "5e0c43ea5bd0377f4cfdfa19",
        "user_name": "123user",
        "rating": 5,
        "booking_id": "Booking1"
    }, 
    {
        "user_name": "123user",
        "review": "Ggg",
        "booking_id": "Booking2",
        "review_id": "review_38405",
        "status": "active"
    },
    {
        "_id": "5e0c43ea5bd0377f4cfdfa19",
        "user_name": "123user",
        "rating": 5,
        "booking_id": "Booking2"
    }

I need to merge booking1 and booking2 like this:

    {
        "user_name": "123user",
        "review": "Ggg",
        "booking_id": "Booking1",
        "review_id": "review_38405",
        "status": "active"
        "_id": "5e0c43ea5bd0377f4cfdfa19",
        "rating": 5,
    }, 
    {
        "user_name": "123user",
        "review": "Ggg",
        "booking_id": "Booking2",
        "review_id": "review_38405",
        "status": "active"
        "_id": "5e0c43ea5bd0377f4cfdfa19",
        "rating": 5,
    }

I hope its much clear now.

Upvotes: 1

Views: 1037

Answers (2)

Reinier Melian
Reinier Melian

Reputation: 20804

I think that a good approach for this will be create a merge function in your ReviewRecord model and then a dict of [String:ReviewRecord] where key is booking_id something like this

struct ReviewRecord: Codable, Equatable {

//All previous logic
func merge(_ anotherRecord: ReviewRecord) -> ReviewRecord {
    let userName = self.userName ?? anotherRecord.userName
    let review = self.review ?? anotherRecord.review
    let reviewID = self.reviewID ?? anotherRecord.reviewID
    let status = self.status ?? anotherRecord.status
    let id = self.id ?? anotherRecord.id
    let rating = self.rating ?? anotherRecord.rating
        return ReviewRecord(userName: userName, review: review, bookingID: self.bookingID, reviewID: reviewID, status: status, id: id, rating: rating)
    }
}

then where your are decoding

you need to make a cycle over your json dict objects and create one by one adding it to a dict

func getRecordsFromRequestData(data:Data?) -> [ReviewRecord] {
    if let dataNew = data, let responseString = String(data: dataNew, encoding: .utf8)  {
        print("----- Records -----")
        print(responseString)
        print("----------")
        do {
            let jsonObject = try JSONSerialization.jsonObject(with: dataNew, options: .mutableContainers) as! AnyObject
            if let recordsArray = jsonObject["records"] as? [AnyObject] {
                var reviewsData = [String:ReviewRecord]()
                for dictObj in recordsArray {
                    let dictData = try JSONSerialization.data(withJSONObject: dictObj, options: .fragmentsAllowed)
                    do {
                        var reviewRecord = try JSONDecoder().decode(ReviewRecord.self, from: dictData)
                        if reviewsData[reviewRecord.bookingID] != nil {
                            let finalRecord = reviewRecord.merge(reviewsData[reviewRecord.bookingID]!)
                            reviewsData[reviewRecord.bookingID] = finalRecord
                        } else {
                            reviewsData[reviewRecord.bookingID] = reviewRecord
                        }
                    }
                    catch {

                    }
                }
                let finalValue = reviewsData.map({$0.value})
                debugPrint(finalValue)
                return finalValue
            }
        }
        catch {

        }
    }
    return []
}

Output with first example json provided by question

[CodableQuestion.ReviewRecord(userName: Optional("123user"), review: Optional("Ggg"), reviewID: Optional("review_38405"), bookingID: "Booking_23749", status: Optional("active"), id: Optional("5e0c43ea5bd0377f4cfdfa19"), rating: Optional(5))]

Output with second example json provided by updated question

[CodableQuestion.ReviewRecord(userName: Optional("123user"), review: Optional("Ggg"), reviewID: Optional("review_38405"), bookingID: "Booking1", status: Optional("active"), id: Optional("5e0c43ea5bd0377f4cfdfa19"), rating: Optional(5)),
 CodableQuestion.ReviewRecord(userName: Optional("123user"), review: Optional("Ggg"), reviewID: Optional("review_38405"), bookingID: "Booking2", status: Optional("active"), id: Optional("5e0c43ea5bd0377f4cfdfa19"), rating: Optional(5))]

Upvotes: 3

Luca Angeletti
Luca Angeletti

Reputation: 59496

The JSON

Given this JSON

let data = """
{
    "status": "Success",
    "records": [
        {
            "user_name": "123user",
            "review": "Ggg",
            "booking_id": "Booking_23749",
            "review_id": "review_38405",
            "status": "active"
        },
        {
            "_id": "5e0c43ea5bd0377f4cfdfa19",
            "user_name": "123user",
            "rating": 5,
            "booking_id": "Booking_23749"
        }
    ]
}
""".data(using: .utf8)!

And given the associated Codable struct

We need to add the merged(with: method to the Record struct

struct Response: Decodable {

    let status: String
    let records: [Record]

    struct Record: Decodable {

        let userName: String?
        let review: String?
        let bookingID: String
        let reviewID: String?
        let status: String?
        let id: String?
        let rating: Int?

        enum CodingKeys: String, CodingKey {
            case userName = "user_name"
            case review
            case bookingID = "booking_id"
            case reviewID = "review_id"
            case status
            case id = "_id"
            case rating
        }

        func merged(with record: Record) -> Record? {
            guard bookingID == record.bookingID else { return nil }
            return Record(userName: userName ?? record.userName,
                          review: review ?? record.review,
                          bookingID: bookingID,
                          reviewID: reviewID ?? record.reviewID,
                          status: status ?? record.status,
                          id: id ?? record.id,
                          rating: rating ?? record.rating
            )
        }
    }
}

Decoding and merging

Now we can decode and merge the Record(s) having the same recordID

do {
    let response = try JSONDecoder().decode(Response.self, from: data)

    let mergedRecords = response
        .records
        .reduce(into: [String: Response.Record]()) { (result, record) in

            guard let existingRecord = result[record.bookingID] else {
                result[record.bookingID] = record
                return
            }

            let merged = existingRecord.merged(with: record)
            result[record.bookingID] = merged
        }
        .values


    print(mergedRecords)

} catch {
    print(error)
}

Result

[
    Response.Record(
        userName: Optional("123user"),
        review: Optional("Ggg"),
        bookingID: "Booking_23749",
        reviewID: Optional("review_38405"),
        status: Optional("active"),
        id: Optional("5e0c43ea5bd0377f4cfdfa19"),
        rating: Optional(5)
    )
]

Consideratoins

This code will work for any number of elements into the records field of your input JSON.

UPDATE

I tested my code with your new input JSON

let data = """
{
    "status": "Success",
    "records": [
        {
            "user_name": "123user",
            "review": "Ggg",
            "booking_id": "Booking1",
            "review_id": "review_38405",
            "status": "active"
        },
        {
            "_id": "5e0c43ea5bd0377f4cfdfa19",
            "user_name": "123user",
            "rating": 5,
            "booking_id": "Booking1"
        },
        {
            "user_name": "123user",
            "review": "Ggg",
            "booking_id": "Booking2",
            "review_id": "review_38405",
            "status": "active"
        },
        {
            "_id": "5e0c43ea5bd0377f4cfdfa19",
            "user_name": "123user",
            "rating": 5,
            "booking_id": "Booking2"
        }
    ]
}
""".data(using: .utf8)!

And I got the expected result

[
    Response.Record(userName: Optional("123user"),
                 review: Optional("Ggg"),
                 bookingID: "Booking1",
                 reviewID: Optional("review_38405"),
                 status: Optional("active"),
                 id: Optional("5e0c43ea5bd0377f4cfdfa19"),
                 rating: Optional(5)),
    Response.Record(userName: Optional("123user"),
                 review: Optional("Ggg"),
                 bookingID: "Booking2",
                 reviewID: Optional("review_38405"),
                 status: Optional("active"),
                 id: Optional("5e0c43ea5bd0377f4cfdfa19"),
                 rating: Optional(5))
]

So please double check you are following exactly my instructions ;)

Upvotes: 3

Related Questions