Reputation: 464
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
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
Reputation: 59496
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)!
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
)
}
}
}
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)
}
[
Response.Record(
userName: Optional("123user"),
review: Optional("Ggg"),
bookingID: "Booking_23749",
reviewID: Optional("review_38405"),
status: Optional("active"),
id: Optional("5e0c43ea5bd0377f4cfdfa19"),
rating: Optional(5)
)
]
This code will work for any number of elements into the records
field of your input JSON.
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