Merge Two Array of Struct in Swift - ios

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.

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 ;)

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))]

Related

How to create a struct model from a complex JSON response

I am trying to consume data from an api in swift, json data has successfully been delivered back to the app but the json response from my backend is very complex hence forming struct for my model is very difficult. I'm able to only retrieve the simple strings but if I add the objects and arrays everything stops working
[
{
"type": "movie",
"id": "ffff-ddd968-4cf0-a939-8skeu",
"title": "Sorority Row",
"description": "When five laldkdk",
"categories": [],
"genres": [
"Mystery",
"Horror"
],
"links": {
"amazonPrime": [
{
"link": "somelink",
"format": "native_ios"
},
{
"link": "somelink",
"format": "native_ios"
}
],
"appleTvPlus": [],
"disneyPlus": []
"iTunes": [
{
"link": "www.somelink",
"format": "webview_computer"
}
],
"netflix": [],
"youTubePremium": []
},
"promoted": false,
"certification": "18",
"releaseDate": "2009-09-09",
"runTime": 101,
"userRating": null,
"inUserList": false,
"packShot": {
"thumbnail": "imageurl"
},
"backdrop": {
"thumbnail": "imageurl"
}
}
]
struct Responder: Codable {
let type: String
let id: String
let description: String
let title: String
let promoted: Bool
let certification: String
let firstAirDate: String
let lastAirDate: String
let numberEpisodes: Int
let numberSeasons: Int
let userRating: Int?
let inUserList: Bool
let thumbnail: PackShotObj
let amazonPrime: linksObj
}
struct PackShotObj: Codable {
let packShot: [String]
}
struct linksObj: Codable {
let link: String
let format: String
}
struct genres: Codable {
let empty: String
}
Here is the code that works, decoding your json data. Note the differences between my struct models and yours. You will need to consult the docs of the server to determine which fields are optionals and adjust the code for that:
struct ContentView: View {
#State var responders: [Responder] = []
var body: some View {
List(responders) { responder in
Text(responder.title)
Text(responder.description)
Text(responder.releaseDate)
}
.onAppear {
let json = """
[
{
"type": "movie",
"id": "ffff-ddd968-4cf0-a939-8skeu",
"title": "Sorority Row",
"description": "When five laldkdk",
"categories": [],
"genres": [
"Mystery",
"Horror"
],
"links": {
"amazonPrime": [
{
"link": "somelink",
"format": "native_ios"
},
{
"link": "somelink",
"format": "native_ios"
}
],
"appleTvPlus": [],
"disneyPlus": [],
"iTunes": [
{
"link": "www.somelink",
"format": "webview_computer"
}
],
"netflix": [],
"youTubePremium": []
},
"promoted": false,
"certification": "18",
"releaseDate": "2009-09-09",
"runTime": 101,
"userRating": null,
"inUserList": false,
"packShot": {
"thumbnail": "imageurl"
},
"backdrop": {
"thumbnail": "imageurl"
}
}
]
"""
// simulated API data
let data = json.data(using: .utf8)!
do {
self.responders = try JSONDecoder().decode([Responder].self, from: data)
print("\n---> responders: \n \(responders)\n")
} catch {
print("\n---> decoding error: \n \(error)\n")
}
}
}
}
// MARK: - Responder
struct Responder: Identifiable, Codable {
let type, id, title, description: String
let categories: [String]
let genres: [String]
let links: Links
let promoted: Bool
let certification, releaseDate: String
let runTime: Int
let userRating: Int?
let inUserList: Bool
let packShot, backdrop: Backdrop
}
// MARK: - Backdrop
struct Backdrop: Codable {
let thumbnail: String
}
// MARK: - Links
struct Links: Codable {
let amazonPrime: [Provider]
let appleTvPlus: [Provider]
let disneyPlus: [Provider]
let iTunes: [Provider]
let netflix: [Provider]
let youTubePremium: [Provider]
}
struct Provider: Codable {
let link, format: String
}
Just copy and paste this model to file and you are good to go.
struct Responder: Codable {
let type, id, title, welcomeDescription: String
let categories: [String]
let genres: [String]
let links: Links
let promoted: Bool
let certification, releaseDate: String
let runTime: Int
let userRating: Any?
let inUserList: Bool
let packShot, backdrop: Backdrop
enum CodingKeys: String, CodingKey {
case type, id, title
case welcomeDescription = "description"
case categories, genres, links, promoted, certification, releaseDate, runTime, userRating, inUserList, packShot, backdrop
}
}
// MARK: - Backdrop
struct Backdrop: Codable {
let thumbnail: String
}
// MARK: - Links
struct Links: Codable {
let amazonPrime: [AmazonPrime]
let appleTvPlus, disneyPlus: [String]
let iTunes: [AmazonPrime]
let netflix, youTubePremium: [String]
}
// MARK: - AmazonPrime
struct AmazonPrime: Codable {
let link, format: String
}

How do i parse custom JSON Data

Here i provide the code worked sample as per the guideline given below and seems to get null values.
Here is my complete JSON Data,
some: {
"success": true,
"data":
[
{
"15-10-2020": [
{
"id": 100,
"details": {
"_id": 1,
"_title": "My Title"
},
"created_at": "2020-10-15"
},
{
"snf_id": 101,
"details": {
"_id": 1,
"_title": "My Title"
},
"created_at": "2020-10-15"
},
{
"snf_id": 102,
"details": {
"_id": 1,
"_title": "My Title"
},
"created_at": "2020-10-15"
}
],
"30-09-2020": [
{
"snf_id": 301,
"details": {
"_id": 8,
"_title": "My Title"
},
"created_at": "2020-09-30"
}
]
}
],
"message": "Successfully Retrieved"
}
struct Response : Codable {
var success : Bool?
var data : [Data]?
var message : String?
}
struct Data: Codable {
var snf_id: Int?
var details: Details?
var created_at: String?
}
// MARK: - Details
struct Details: Codable {
var _id: Int?
var _title: String?
}
let Response = try JSONDecoder().decode(Response.self, from: data)
Returns null value for data,
▿ Response
▿ success : Optional
- some : true
▿ data : Optional<Array>
▿ some : 1 element
▿ 0 : Data
- snf_id : nil
- details : nil
- created_at : nil
▿ message : Optional
- some : "Successfully Retrieved"
There is no myData key in your json , your json top structure is an array that contains elements where every element value is an array like [[String: [Root]]]
struct Root: Codable {
let id: Int?
let details: Details
let createdAt: String
let snfID: Int?
enum CodingKeys: String, CodingKey {
case id, details
case createdAt = "created_at"
case snfID = "snf_id"
}
}
// MARK: - Details
struct Details: Codable {
let id: Int
let title: String
enum CodingKeys: String, CodingKey {
case id = "_id"
case title = "_title"
}
}
And to decode
let res = try JSONDecoder().decode([[String: [Root]]].self,from:data)
some: {
"success": true,
"data":
[
{
"15-10-2020": [
{
"id": 100,
"details": {
"_id": 1,
"_title": "My Title"
},
"created_at": "2020-10-15"
},
{
"snf_id": 101,
"details": {
"_id": 1,
"_title": "My Title"
},
"created_at": "2020-10-15"
},
{
"snf_id": 102,
"details": {
"_id": 1,
"_title": "My Title"
},
"created_at": "2020-10-15"
}
],
"30-09-2020": [
{
"snf_id": 301,
"details": {
"_id": 8,
"_title": "My Title"
},
"created_at": "2020-09-30"
}
]
}
],
"message": "Successfully Retrieved"
}
struct Response : Codable {
var success : Bool?
**var data : [[String:[Data]]]?**
var message : String?
}
struct Data: Codable {
var snf_id: Int?
var details: Details?
var created_at: String?
}
// MARK: - Details
struct Details: Codable {
var _id: Int?
var _title: String?
}
let Response = try JSONDecoder().decode(Response.self, from: data)

Swift custom decoding including another custom decoded model

I am currently building a food app using Yelp Fusion API. There are two models called Business and BusinessDatail in my project. To list up restaurants in a tableView, I fetch a restaurant list from backend and custom decode it as an array of Business, [Business]. And then, when a tableViewCell of Business is clicked, I fetch detailed business info and custom decode it as a BusinessDetail.
struct Business {
let id: String
let name: String
let rating: Float?
let price: String?
let displayPhone: String?
let imageUrl: URL
let category: String
let reviewCount: Int
let coordinates: Coordinates
let address: Address
}
struct BusinessDetail {
let id: String
let name: String
let rating: Float?
let price: String?
let displayPhone: String?
let imageUrl: URL
let category: String
let reviewCount: Int
let coordinates: Coordinates
let address: Address
let isClaimed: Bool
let isClosed: Bool
let url: URL
let phone: String?
let photoURLs: [URL]
var hours: [Hours]?
}
Both Business and BusinessDetail are custom decoded models and they are currently initialised this way.
extension Business: Decodable {
enum CodingKeys: String, CodingKey {
case id
case name
case rating
case price
case displayPhone = "display_phone"
case imageUrl = "image_url"
case category = "categories"
case reviewCount = "review_count"
case coordinates
case address = "location"
enum CategoryCodingKeys: String, CodingKey {
case title
}
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(String.self, forKey: .id)
name = try container.decode(String.self, forKey: .name)
rating = try container.decode(Float.self, forKey: .rating)
price = try container.decodeIfPresent(String.self, forKey: .price)
displayPhone = try container.decodeIfPresent(String.self, forKey: .displayPhone)
imageUrl = try container.decode(URL.self, forKey: .imageUrl)
var categoriesContainer = try container.nestedUnkeyedContainer(forKey: .category)
var tempCategory = ""
while !categoriesContainer.isAtEnd {
let categoryContainer = try categoriesContainer.nestedContainer(keyedBy: CodingKeys.CategoryCodingKeys.self)
let title = try categoryContainer.decode(String.self, forKey: .title)
tempCategory += tempCategory == "" ? title: ", \(title)"
}
category = tempCategory
reviewCount = try container.decode(Int.self, forKey: .reviewCount)
coordinates = try container.decode(Coordinates.self, forKey: .coordinates)
address = try container.decode(Address.self, forKey: .address)
}
}
extension PlaceDetail: Decodable {
enum CodingKeys: String, CodingKey {
case id
case name
case rating
case price
case displayPhone = "display_phone"
case imageUrl = "image_url"
case category = "categories"
case reviewCount = "review_count"
case coordinates
case address = "location"
enum CategoryCodingKeys: String, CodingKey {
case title
}
case isClaimed = "is_claimed"
case isClosed = "is_closed"
case url
case phone
case photoURLs = "photos"
case hours
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(String.self, forKey: .id)
name = try container.decode(String.self, forKey: .name)
rating = try container.decode(Float.self, forKey: .rating)
price = try container.decodeIfPresent(String.self, forKey: .price)
displayPhone = try container.decode(String.self, forKey: .displayPhone)
imageUrl = try container.decode(URL.self, forKey: .imageUrl)
var categoriesContainer = try container.nestedUnkeyedContainer(forKey: .category)
var tempCategory = ""
while !categoriesContainer.isAtEnd {
let categoryContainer = try categoriesContainer.nestedContainer(keyedBy: CodingKeys.CategoryCodingKeys.self)
let title = try categoryContainer.decode(String.self, forKey: .title)
tempCategory += tempCategory == "" ? title: ", \(title)"
}
category = tempCategory
reviewCount = try container.decode(Int.self, forKey: .reviewCount)
coordinates = try container.decode(Coordinates.self, forKey: .coordinates)
address = try container.decode(Address.self, forKey: .address)
isClaimed = try container.decode(Bool.self, forKey: .isClaimed)
isClosed = try container.decode(Bool.self, forKey: .isClosed)
url = try container.decode(URL.self, forKey: .url)
phone = try container.decode(String.self, forKey: .phone)
photoURLs = try container.decode([URL].self, forKey: .photoURLs)
hours = try container.decodeIfPresent([Hours].self, forKey: .hours)
}
}
Here is the json for a business list.
{
"businesses": [
{
"id": "I1D8NHvMWf8oMYceTyLlHg",
"name": "John Mills Himself",
"image_url": "https://s3-media3.fl.yelpcdn.com/bphoto/OH84e6eP8zpzBECF0WvTXA/o.jpg",
"is_closed": false,
"url": "https://www.yelp.com/biz/john-mills-himself-brisbane?adjust_creative=0mCaOEYvfM9_oOaXgMuW6A&utm_campaign=yelp_api_v3&utm_medium=api_v3_business_search&utm_source=0mCaOEYvfM9_oOaXgMuW6A",
"review_count": 55,
"categories": [
{
"alias": "coffee",
"title": "Coffee & Tea"
},
{
"alias": "bakeries",
"title": "Bakeries"
},
{
"alias": "wine_bars",
"title": "Wine Bars"
}
],
"rating": 4.5,
"coordinates": {
"latitude": -27.47151,
"longitude": 153.025654
},
"transactions": [],
"price": "$",
"location": {
"address1": "40 Charlotte St",
"address2": "",
"address3": "",
"city": "Brisbane",
"zip_code": "4000",
"country": "AU",
"state": "QLD",
"display_address": [
"40 Charlotte St",
"Brisbane Queensland 4000",
"Australia"
]
},
"phone": "",
"display_phone": "",
"distance": 383.2254392716822
},
{
"id": "KaIoCOg-IZJtLdN39Cw__Q",
"alias": "strauss-brisbane",
"name": "Strauss",
"image_url": "https://s3-media2.fl.yelpcdn.com/bphoto/eYKx68uOaEY5k9Jt4TrQPw/o.jpg",
"is_closed": false,
"url": "https://www.yelp.com/biz/strauss-brisbane?adjust_creative=0mCaOEYvfM9_oOaXgMuW6A&utm_campaign=yelp_api_v3&utm_medium=api_v3_business_search&utm_source=0mCaOEYvfM9_oOaXgMuW6A",
"review_count": 33,
"categories": [
{
"alias": "cafes",
"title": "Cafes"
}
],
"rating": 4.5,
"coordinates": {
"latitude": -27.469804,
"longitude": 153.027384
},
"transactions": [],
"price": "$",
"location": {
"address1": "189 Elizabeth St",
"address2": "",
"address3": "",
"city": "Brisbane",
"zip_code": "4000",
"country": "AU",
"state": "QLD",
"display_address": [
"189 Elizabeth St",
"Brisbane Queensland 4000",
"Australia"
]
},
"phone": "+61732365232",
"display_phone": "+61 7 3236 5232",
"distance": 247.3760157828213
}
}
And this is json response for individual BusinessDetail.
{
"id": "I1D8NHvMWf8oMYceTyLlHg",
"name": "John Mills Himself",
"image_url": "https://s3-media3.fl.yelpcdn.com/bphoto/OH84e6eP8zpzBECF0WvTXA/o.jpg",
"is_claimed": false,
"is_closed": false,
"url": "https://www.yelp.com/biz/john-mills-himself-brisbane?adjust_creative=0mCaOEYvfM9_oOaXgMuW6A&utm_campaign=yelp_api_v3&utm_medium=api_v3_business_lookup&utm_source=0mCaOEYvfM9_oOaXgMuW6A",
"phone": "",
"display_phone": "",
"review_count": 55,
"categories": [
{
"alias": "coffee",
"title": "Coffee & Tea"
},
{
"alias": "bakeries",
"title": "Bakeries"
},
{
"alias": "wine_bars",
"title": "Wine Bars"
}
],
"rating": 4.5,
"location": {
"address1": "40 Charlotte St",
"address2": "",
"address3": "",
"city": "Brisbane",
"zip_code": "4000",
"country": "AU",
"state": "QLD",
"display_address": [
"40 Charlotte St",
"Brisbane Queensland 4000",
"Australia"
],
"cross_streets": ""
},
"coordinates": {
"latitude": -27.47151,
"longitude": 153.025654
},
"photos": [
"https://s3-media3.fl.yelpcdn.com/bphoto/OH84e6eP8zpzBECF0WvTXA/o.jpg",
"https://s3-media1.fl.yelpcdn.com/bphoto/L8dJYv1GCW1m5IvD0M3qXw/o.jpg",
"https://s3-media4.fl.yelpcdn.com/bphoto/oUH4cJmPRuAs_jv_xRtXQg/o.jpg"
],
"price": "$",
"hours": [
{
"open": [
{
"is_overnight": false,
"start": "0630",
"end": "1530",
"day": 0
},
{
"is_overnight": false,
"start": "0630",
"end": "1530",
"day": 1
},
{
"is_overnight": false,
"start": "1600",
"end": "2100",
"day": 1
},
{
"is_overnight": false,
"start": "0630",
"end": "1530",
"day": 2
},
{
"is_overnight": false,
"start": "1600",
"end": "2100",
"day": 2
},
{
"is_overnight": false,
"start": "0630",
"end": "1530",
"day": 3
},
{
"is_overnight": false,
"start": "1600",
"end": "2330",
"day": 3
},
{
"is_overnight": false,
"start": "0630",
"end": "1530",
"day": 4
},
{
"is_overnight": false,
"start": "1600",
"end": "2330",
"day": 4
},
{
"is_overnight": false,
"start": "1600",
"end": "2330",
"day": 5
}
],
"hours_type": "REGULAR",
"is_open_now": true
}
]
}
As you can see, BusinessDetail has exactly same info as Business at the top and I'd like to change BusinessDetail like below.
struct BusinessDetail {
let business: Business
let isClaimed: Bool
let isClosed: Bool
let url: URL
let phone: String?
let photoURLs: [URL]
var hours: [Hours]?
}
However, I am not sure if it is technically possible.
Here's a conceptual example, but you can apply the same principle to your case, with the following JSON:
{ "a": 1, "b": 2, "c": "three" }
and these models:
struct Smaller: Decodable {
var a, b: Int
}
struct Larger: Decodable {
var smaller: Smaller
var c: String
}
Decoding Smaller doesn't need any extra work (i.e. no need to manually implement the init(from: Decoder) initializer):
let decoder = JSONDecoder()
let small = try! decoder.decode(Smaller.self, from: jsonData)
But with Larger, you need to manually do this:
struct Larger: Decodable {
var smaller: Smaller
var c: String
enum CodingKeys: CodingKey {
case c
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.smaller = try Smaller.init(from: decoder)
self.c = try container.decode(String.self, forKey: .c)
}
}

Swift Codable Json Response

I Have the following JSON response from API,
{
"status_code": 1000,
"data": [
{
"id": 3,
"invoice_id": 100000,
"user_id": 1000,
"contact_number": "NA",
"province": 0,
"location": "100000",
"total": 0,
"confirm_code": 1234,
"status": 0,
"created_at": "2020-03-18 22:07:25",
"updated_at": "2020-03-18 22:07:25"
},
{
"id": 4,
"invoice_id": 100000,
"user_id": 1000,
"contact_number": "NA",
"province": 0,
"location": "100000",
"total": 0,
"confirm_code": 1234,
"status": 0,
"created_at": "2020-03-18 22:10:40",
"updated_at": "2020-03-18 22:10:40"
},
{
"id": 5,
"invoice_id": 100000,
"user_id": 1000,
"contact_number": "NA",
"province": 0,
"location": "100000",
"total": 0,
"confirm_code": 1234,
"status": 0,
"created_at": "2020-03-18 22:12:29",
"updated_at": "2020-03-18 22:12:29"
}
],
"message": null
}
and my struct is,
struct Invoice: Codable {
let statusCode: Int
let data: [Datum]
let message: String?
enum CodingKeys: String, CodingKey {
case statusCode
case data, message
}
}
struct Datum: Codable {
let id, invoiceID, userID: Int
let contactNumber: String
let province: Int
let location: String
let total, confirmCode, status: Int
let createdAt, updatedAt: String
enum CodingKeys: String, CodingKey {
case id
case invoiceID
case userID
case contactNumber
case province, location, total
case confirmCode
case status
case createdAt
case updatedAt
}
}
and In View Controller,
override func viewDidLoad() {
super.viewDidLoad()
let url = URL(string: "http://xxxxx/api/invoices/\(1000)")!
var request = URLRequest(url: url)
request.httpMethod = "get"
let task = session.dataTask(with: request) { (data, response, error) in
guard let data = data else { return }
do {
let jsonData = try JSONDecoder().decode([Invoice].self, from: data)
print(jsonData)
}
catch let jsonerr {
print("error serrializing error",jsonerr)
}
}
task.resume()
}
But Im keeping below error message,
error serrializing error typeMismatch(Swift.Array, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Array but found a dictionary instead.", underlyingError: nil))
Please what Im missing here ! Any help will be much appreciated
The Codable struct will be like
struct Invoice: Codable {
let statusCode: Int
let data: [Datum]
let message: String?
enum CodingKeys: String, CodingKey {
case statusCode = "status_code"
case data, message
}
}
struct Datum: Codable {
let id, invoiceID, userID: Int
let contactNumber: String
let province: Int
let location: String
let total, confirmCode, status: Int
let createdAt, updatedAt: String
enum CodingKeys: String, CodingKey {
case id = "id"
case invoiceID = "invoice_id"
case userID = "user_id"
case contactNumber = "contact_number"
case province, location, total
case confirmCode = "confirm_code"
case status
case createdAt = "created_at"
case updatedAt = "updated_at"
}
}
And also use
let jsonData = try JSONDecoder().decode(Invoice.self, from: data)
instead of
let jsonData = try JSONDecoder().decode([Invoice].self, from: data)

How to retrieve data from json response

I am trying to get data from json response, and the response format is mentioning below. I want to fetch "recipient" dictionary, and need to show in table.each cell contains name and unique id and image. How to get this dictionary to story in local dictionary?
{
"success": 1,
"status": 200,
"data": {
"chat": [
{
"id": 5,
"status": 0,
"created_at": "2019-02-19 13:29:15",
"updated_at": "2019-02-19 13:29:15",
"recipient": {
"id": 5,
"unique_id": "10004",
"name": "Pandu",
"avatar": "https://www.planetzoom.co.in/img/default_avatar_female.png"
},
"conversation": {
"id": 67,
"chat_id": 5,
"user_id": 4,
"type": 0,
"message": "I have sent a msg now",
"status": 0,
"created_at": "2019-02-26 04:02:20"
}
},
{
"id": 3,
"status": 0,
"created_at": "2019-02-19 13:17:49",
"updated_at": "2019-02-19 13:17:49",
"recipient": {
"id": 8,
"unique_id": "10007",
"name": "Mahitha",
"avatar": "https://www.planetzoom.co.in/storage/user/avatar/cZt9yQlBzIEewOdQ1lYZhl3dFiOv2k3bxG7HLOzR.jpeg"
},
"conversation": {
"id": 57,
"chat_id": 3,
"user_id": 4,
"type": 0,
"message": "Hi",
"status": 0,
"created_at": "2019-02-24 13:04:29"
}
},
{
"id": 2,
"status": 0,
"created_at": "2019-02-19 07:59:05",
"updated_at": "2019-02-19 07:59:05",
"recipient": {
"id": 1,
"unique_id": "1111",
"name": "Angadi World Tech",
"avatar": "https://www.planetzoom.co.in/storage/user/avatar/NlVzdUAfmLfIG9677szYZz7NkWyY4ULHAqnlCiiV.png"
},
"conversation": {
"id": 21,
"chat_id": 2,
"user_id": 4,
"type": 0,
"message": "Hi\\uD83D\\uDE0A",
"status": 0,
"created_at": "2019-02-21 10:35:26"
}
}
]
}
}
The best way to decode json in my opinion is to use Codable
I've created a few structs to represent the data and to decode it, please note this json wasn't valid so have had to wrap it in {}
Here's the json:
let jsonString = """
{
"chat": [
{
"id": 12,
"status": 0,
"created_at": "2019-02-22 04:57:12",
"updated_at": "2019-02-22 04:57:12",
"recipient": {
"id": 26,
"unique_id": "10024",
"name": "Kaverinew",
"avatar": "https://www.planetzoom.co.in/storage/user/avatar/1PyI4ceM3zPsG1fxbfatktWUT75sOE2Ttah8ctIp.png"
},
"conversation": {
"id": 65,
"chat_id": 12,
"user_id": 4,
"type": 1,
"message": "https://www.planetzoom.co.in/storage/chat/message/e759KWdSBegwXslAoS2xst0lohbbjNZMdpVnbxQG.png",
"status": 0,
"created_at": "2019-02-25 15:39:24"
}
},
{
"id": 6,
"status": 0,
"created_at": "2019-02-20 07:16:35",
"updated_at": "2019-02-20 07:16:35",
"recipient": {
"id": 7,
"unique_id": "10006",
"name": "Hema",
"avatar": "https://www.planetzoom.co.in/img/default_avatar_female.png"
},
"conversation": {
"id": 44,
"chat_id": 6,
"user_id": 4,
"type": 1,
"message": "https://www.planetzoom.co.in/storage/chat/message/qJjOtCRcBKBuq3UKaKVuVOEIQhaVPeJr3Bd4NoLo.png",
"status": 0,
"created_at": "2019-02-22 10:17:49"
}
}
]
}
Here are the structs:
struct Recipient: Codable {
var identifier: Int
var unique_id: Int
var name: String
var avatar: String
enum CodingKeys: String, CodingKey {
case identifier = "id"
case unique_id = "unique_id"
case name = "name"
case avatar = "avatar"
}
}
struct Conversation: Codable {
var identifier: Int
var chat_id: Int
var user_id: Int
var conversationType: Int
var message: String
var status: Int
var created_at: String
enum CodingKeys: String, CodingKey {
case identifier = "id"
case chat_id = "chat_id"
case user_id = "user_id"
case conversationType = "type"
case message = "message"
case status = "status"
case created_at = "created_at"
}
}
struct Chat: Codable {
var identifier: Int
var status: Int
var created_at: String
var updated_at: String
enum CodingKeys: String, CodingKey {
case identifier = "id"
case status = "status"
case created_at = "created_at"
case updated_at = "updated_at"
}
}
struct RootObject: Codable {
var chats: [Chat]
enum CodingKeys: String, CodingKey {
case chats = "chat"
}
}
And here is how you decode it:
if let jsonData = jsonString.data(using: .utf8) {
do {
let root = try JSONDecoder().decode(RootObject.self, from: jsonData)
} catch {
print(error)
}
}
Assuming you already have a Data object representing your JSON you can use JSONSerialization to convert it to concrete Swift object. Once that is done you simply need to go step by step and extract the data. Something like the following should work nicely:
func retrieveRecipients(jsonData: Data?) throws -> [[String: Any]] {
guard let data = jsonData else { throw NSError(domain: "Parsing Recipients", code: 404, userInfo: ["dev_message": "Null JSON data inserted"]) }
guard let parsedJSON = try? JSONSerialization.jsonObject(with: data, options: .allowFragments) else { throw NSError(domain: "Parsing Recipients", code: 500, userInfo: ["dev_message": "Data could not be parsed as JSON"]) }
guard let object = parsedJSON as? [String: Any] else { throw NSError(domain: "Parsing Recipients", code: 500, userInfo: ["dev_message": "Parsed JSON is not a dictionary"]) }
guard let items = object["chat"] as? [[String: Any]] else { throw NSError(domain: "Parsing Recipients", code: 404, userInfo: ["dev_message": "JSON is missing \"chat\" array"]) }
return items.compactMap { $0["recipient"] as? [String: Any] }
}
This is all the safety enabled. Otherwise you could do it very shortly:
func retreiveRecipientsStrict(jsonData: Data?) -> [[String: Any]] {
return ((try! JSONSerialization.jsonObject(with: data!, options: .allowFragments) as! [String: Any])["chat"] as! [[String: Any]]).compactMap { $0["recipient"] as? [String: Any] }
}
But this will crash if there is a mistake and it will be extremely hard to debug what went wrong.

Resources