parsing array in array into some arrays in swift 4? - ios

I used parsing son in my codes for news api
the son is some thing like this
> {
"status": "ok",
"source": "associated-press",
"sortBy": "top",
-"articles": [
-{
"author": "CHRISTINA A. CASSIDY and MEGHAN HOYER",
"title": "Pro-Trump states most affected by his health care decision",
"description": "President Donald Trump's decision to end a provision of the Affordable Care Act that was benefiting roughly 6 million Americans helps fulfill a campaign promise",
"url": "https:urlexample",
"urlToImage": "url example",
},
-{
"author": "CHRISTINA A. CASSIDY and MEGHAN HOYER",
"title": "Pro-Trump states most affected by his health care decision",
"description": "President Donald Trump's decision to end a provision of the Affordable Care Act that was benefiting roughly 6 million Americans helps fulfill a campaign promise",
"url": "https:urlexample",
"urlToImage": "url example",
},
]
}
as you see in each array we have title - description and more
I want to parse this jason into separated array for example append all of the titles in one array and append all of the descriptions in another ones and more
here is my code
struct Response : Decodable {
let articles: articles
}
struct articles: Decodable {
let title: String
let description : String
let url : String
let urlToImage : String
}
and here is the codes for json
let jsonUrl = "https://newsapi.org/[your codes]"
guard let url = URL(string : jsonUrl) else {
return
}
URLSession.shared.dataTask(with: url) { (data , response , error) in
guard let data = data else {return}
do {
let article = try JSONDecoder().decode(Response.self , from : data)
print(article.articles.title)
print(article.articles.description)
print(article.articles.url)
print(article.articles.urlToImage)
}
catch {
print(error)
}
}.resume()
and when I run this I will receive this error
"Expected to decode Dictionary but found an array instead.", underlyingError: nil))

First, to distinguish between property/method names and type names, try to follow Swift naming conventions: (the following is from Swift API Design guidelines)
Names of types and protocols are UpperCamelCase. Everything else is lowerCamelCase.
Also, your articles struct represents data for just one article, not multiple. So it should start with a capital A, and be singular:
struct Article: Decodable {
Secondly, if you take another look at the JSON you're getting back, articles is an array of dictionaries:
-"articles": [
-{
"author": "CHRISTINA A. CASSIDY and MEGHAN HOYER",
...
},
-{
"author": "CHRISTINA A. CASSIDY and MEGHAN HOYER",
...
},
So the articles property in your Response struct should be an array of Article.
struct Response : Decodable {
let articles: [Article]
}

Related

Swift "No value associated with key CodingKeys" Error when parsing JSON from NewsCatcher API

Still new in Swift. I've searched through many similar questions asked about the subject before and for some reason still can't pinpoint where the issue is in how my structs are built or what I am doing wrong. I'd appreciate your help!
I am using newswatcher api in my swift application, and the data I fetch is built like this:
{
"status": "ok",
"total_hits": 10000,
"page": 1,
"total_pages": 10000,
"page_size": 1,
"articles": [
{
"title": "Pizza Pizza has teamed up with Mattel for the ultimate game night combo",
"author": null,
"published_date": "2022-03-18 20:34:17",
"published_date_precision": "full",
"link": "https://dailyhive.com/vancouver/pizza-pizza-mattel-canada-uno",
"clean_url": "dailyhive.com",
"excerpt": "Pizza and game nights are a match made in heaven. Pizza Pizza has partnered with Mattel Canada for the ultimate package deal.",
"summary": "Pizza and game nights are a match made in heaven — even Pizza Pizza knows that. The Canadian chain has partnered with Mattel Canada for the ultimate package deal.\nReturning for another year is the pizza chain's UNO collab but this time it features a new limited-edition Pizza Pizza or Pizza 73 branded UNO deck with every featured UNO combo purchase.\nThe decks feature pizza art and a surprise bonus offer too.\n\n \nView this post on Instagram\n A post shared by Pizza Pizza (#pizzapizzaltd)\n\n 'For over 50 years, UNO has become a staple at game nights across the country, bringing families and friends together through gameplay,' said Jennifer Gileno, Head of Licensing and Retail Development for Mattel Canada.",
"rights": null,
"rank": 9543,
"topic": "news",
"country": "CA",
"language": "en",
"authors": [],
"media": "https://images.dailyhive.com/20220318132250/pizza-pizza-uno-500x256.jpg",
"is_opinion": false,
"twitter_account": "https://dailyhive.com/vancouver/pizza-pizza-mattel-canada-uno",
"_score": 14.017945,
"_id": "feca5f5fe473e561bf0b8c11b01b87bf"
}
],
"user_input": {
"q": "pizza",
"search_in": [
"title_summary"
],
"lang": null,
"not_lang": null,
"countries": null,
"not_countries": null,
"from": "2022-03-15 00:00:00",
"to": null,
"ranked_only": "True",
"from_rank": null,
"to_rank": null,
"sort_by": "relevancy",
"page": 1,
"size": 1,
"sources": null,
"not_sources": null,
"topic": null,
"published_date_precision": null
}
}
I have created the following structs in order to decode the data:
struct ArticleModel: Codable {
let totalPages: Int
let articles: [Articles]
let numOfResults: Int
enum CodingKeys: String, CodingKey {
case totalPages = "total_pages"
case articles = "articles"
case numOfResults = "total_hits"
}
}
struct Articles: Codable {
let id: String
let articleTitle: String
let date: String
let url: String
let content: String
let author: String?
let topic: String
let imageUrl: String?
enum CodingKeys: String, CodingKey {
case id = "_id"
case articleTitle = "title"
case content = "summary"
case author = "author"
case url = "link"
case date = "published_date"
case topic = "topic"
case imageUrl = "media"
}
}
I am using Pagination in my app and my initial fetch is working great with no issue. However - when scrolling down to the bottom of the UITableView I fire another fetch request (for the next batch of data i.e. page 2 of data) and I get the following error in my console:
keyNotFound(CodingKeys(stringValue: "total_pages", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: "total_pages", intValue: nil) ("total_pages").", underlyingError: nil))
The pagination works fine, the data-batches are retrieved as should.. but I don't understand why this error keeps popping and why it happens only when fetching from the 2nd time forward.
Edit #1: No matter which query or page I fetched for - total_pages is always returned in the results and always has a value.
Edit #2: I tried making total_pages optional but then the error in the console changes to:
keyNotFound(CodingKeys(stringValue: "articles", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: "articles", intValue: nil) ("articles").", underlyingError: nil))
which also doesn't make any sense because I am seeing the new results on the screen..
Edit #3: Here is the response I am getting back on the 2nd page -
From Postman:
{
"status": "ok",
"total_hits": 10000,
"page": 2,
"total_pages": 10000,
"page_size": 1,
"articles": [
{
"title": "Broadway & Beyond Hosts Webinar on Anti-Racist Stage Management Practices",
"author": "Raven Brunner",
"published_date": "2022-03-21 17:17:43",
"published_date_precision": "full",
"link": "https://playbill.com/article/broadway-beyond-hosts-webinar-on-anti-racist-stage-management-practices",
"clean_url": "playbill.com",
"excerpt": "The webinar will be led by veteran stage managers Narda E. Alcorn and Lisa Porter.",
"summary": "Education News Education News Education News Education News Theatre Alternatives Industry News Industry News Industry News Education News Education News Education News Education News Education News Education News Industry News Industry News Industry News Education News Education News Industry News Industry News Education News Education News Industry News Education News Industry News Education News Industry News Industry News Education News Education News Industry News Education News Industry New",
"rights": "playbill.com",
"rank": 5215,
"topic": "entertainment",
"country": "US",
"language": "en",
"authors": [
"Raven Brunner"
],
"media": "https://assets.playbill.com/editorial/_1200x630_crop_center-center_82_none/Narda-E.-Alcorn-and-Lisa-Porter_HR.jpg?mtime=1647876883",
"is_opinion": false,
"twitter_account": "#playbill",
"_score": 5.5872316,
"_id": "7e297f463684c344e3bb9b70d6229fbf"
}
],
"user_input": {
"q": "news",
"search_in": [
"title_summary"
],
"lang": null,
"not_lang": null,
"countries": null,
"not_countries": null,
"from": "2022-03-15 00:00:00",
"to": null,
"ranked_only": "True",
"from_rank": null,
"to_rank": null,
"sort_by": "relevancy",
"page": 2,
"size": 1,
"sources": null,
"not_sources": null,
"topic": null,
"published_date_precision": null
}
}
From the console:
ArticleModel(totalPages: 10000, articles: [Dispatcher_Development.Articles(id: "7e297f463684c344e3bb9b70d6229fbf", articleTitle: "Broadway & Beyond Hosts Webinar on Anti-Racist Stage Management Practices", date: "2022-03-21 17:17:43", url: "https://playbill.com/article/broadway-beyond-hosts-webinar-on-anti-racist-stage-management-practices", content: "Education News Education News Education News Education News Theatre Alternatives Industry News Industry News Industry News Education News Education News Education News Education News Education News Education News Industry News Industry News Industry News Education News Education News Industry News Industry News Education News Education News Industry News Education News Industry News Education News Industry News Industry News Education News Education News Industry News Education News Industry New", author: Optional("Raven Brunner"), topic: "entertainment", imageUrl: Optional("https://assets.playbill.com/editorial/_1200x630_crop_center-center_82_none/Narda-E.-Alcorn-and-Lisa-Porter_HR.jpg?mtime=1647876883"))], numOfResults: 10000)
In case it matters - here is how I detect the user scrolled all the way down:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let position = scrollView.contentOffset.y
if position > (tableView.contentSize.height - 100 - scrollView.frame.size.height) {
fetchNewsFromAPI() {
DispatchQueue.main.async {
self.tableView.tableFooterView = nil
}
}
}
}
and this is the function that is responsible for fetching the data:
func fetchNewsFromAPI(completionHandler: #escaping () -> ()) {
let alamofireQuery = AlamofireManager(from: "\(Constants.apiCalls.newsUrl)?q=news&page_size=\(amountToFetch)&page=\(currentPaginationPage)")
if !alamofireQuery.isPaginating && currentPaginationPage <= totalPaginationPages {
alamofireQuery.executeGetQuery(){
(result: Result<ArticleModel,Error>) in
switch result {
case .success(let response):
self.currentPaginationPage += 1
self.totalPaginationPages = response.totalPages
self.newsArray.append(contentsOf: response.articles)
DispatchQueue.main.async {
self.dataSource.models = self.newsArray
self.tableView.reloadData()
}
case .failure(let error):
print(error)
}
completionHandler()
}
}
}
and this is the executeQuery function inside my Alamofire file:
func executeGetQuery<T>(completion: #escaping (Result<T, Error>) -> Void) where T: Codable {
isPaginating = true
AF.request(url, method: .get, headers: headers).responseData(completionHandler: { response in
do {
switch response.result {
case .success:
completion(.success(try JSONDecoder().decode(T.self, from: response.data ?? Data())))
self.isPaginating = false
case .failure(let error):
completion(.failure(error))
}
} catch let error {
completion(.failure(error))
self.isPaginating = false
}
})
}
The thing that comes to my mind is that you need to decode totalPages as optional.
let totalPages: Int?
why it happens only when fetching from the 2nd time forward
Check JSON data of the 2nd time response. Cause according to the error message:
keyNotFound(CodingKeys(stringValue: "total_pages", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: "total_pages", intValue: nil) ("total_pages").", underlyingError: nil))
, total_pages is missing.
It might be a backend bug, depending on whether it makes sense that an ArticleModel doesn't has a total_pages.
If it is intended, then make totalPages optional:
let totalPages: Int?
Response to edit#2:
| keyNotFound(CodingKeys(stringValue: "articles", intValue: nil).
This new error indicates articles being nil.
The error provided by Apple is quite straightforward. It's just the lack of formatting that confuses people, same with the auto-layout error info.
I'm doing nothing but interpreting the debug info for you.
Postman doesn't necessarily produce the same responses with your real-world requests. You should instead use tools like Proxyman or Charles to capture the responses to your simulator or test device.

Decode KeyValuePairs<String: Person> from JSON

I have a JSON response that looks something like this:
{
"persons": {
"John": {
"name": "John",
"age": 24
},
"Michael": {
"name": "Michael",
"age": 44
},
"Jack": {
"name": "Jack",
"age": 25
}
}
}
As you can see this could be parse with a struct that looks like this:
struct PersonsResponse: Decodable {
let persons: [String: Person]
}
struct Person: Decodable {
let name: String
let age: Ing
}
However, what this does, is returns a dictionary and parses fine. What I would need is to preserve the order of the persons as they arrive inside this JSON response. I have come across KeyValuePairs in swift which basically are ordered dictionaries but for the love of God I can't figure out how to decode it into being an KeyValuePairs<String, Person>.
Apple documentation says that instantiating a KeyValuePairs object is as easy as doing:
let recordTimes: KeyValuePairs = ["Florence Griffith-Joyner": 10.49,
"Evelyn Ashford": 10.76,
"Evelyn Ashford": 10.79,
"Marlies Gohr": 10.81]
Literally. But when I am decoding my response with:
struct PersonsResponse: Decodable {
let persons: KeyValuePairs<String, Person>
...
...
init(from decoder: Decoder) throws {
...
let personsDictionary = try container.decode([String: Person].self, forKey: .persons)
and then try to do:
persons = personsDictionary
of course it doesn't work at all. I tried to do all kinds of magic already with no luck. Does anyone have any solution to parsing dictionaries into ordered sequences or even Arrays? Thanks for helping!
There is NO WAY the JSON response changes into being an array and YES it always is the same order.
Both Swift, JSON Dictionaries are unordered by there nature. The JSON format does notmaintain key ordering, and as such, does not required parser to preserve the order.
If you need an ordered collection, you its better to returning an array of key-value pairs in the JSON
{
"persons": [
{ "John": {
"name": "John",
"age": 24
}
},
{"Michael": {
"name": "Michael",
"age": 44
}
},
{ "Jack": {
"name": "Jack",
"age": 25
}
}
]
}

Sort through JSON to find each instance that a string is different

I am attempting to find each instance of the string name: being different.
As for the example of JSON below I want to pull Alamo Draft House Lamar and Alamo Draft House Ritz and place them into an array.
JSON:
[{
"tmsId": "MV011110340000",
"rootId": "15444050",
"subType": "Feature Film",
"title": "Bohemian Rhapsody",
"releaseYear": 2018,
"releaseDate": "2018-11-02",
"titleLang": "en",
"descriptionLang": "en",
"entityType": "Movie",
"genres": ["Biography", "Historical drama", "Music"],
"longDescription": "Singer Freddie Mercury, guitarist Brian May, drummer Roger Taylor and bass guitarist John Deacon take the music world by storm when they form the rock 'n' roll band Queen in 1970. Surrounded by darker influences, Mercury decides to leave Queen years later to pursue a solo career. Diagnosed with AIDS in the 1980s, the flamboyant frontman reunites with the group for Live Aid -- leading the band in one of the greatest performances in rock history.",
"shortDescription": "Singer Freddie Mercury of Queen battles personal demons after taking the music world by storm.",
"topCast": ["Rami Malek", "Lucy Boynton", "Gwilym Lee"],
"directors": ["Bryan Singer"],
"officialUrl": "https://www.foxmovies.com/movies/bohemian-rhapsody",
"ratings": [{
"body": "Motion Picture Association of America",
"code": "PG-13"
}],
"advisories": ["Adult Language", "Adult Situations"],
"runTime": "PT02H15M",
"preferredImage": {
"width": "240",
"height": "360",
"uri": "assets/p15444050_v_v5_as.jpg",
"category": "VOD Art",
"text": "yes",
"primary": "true"
},
"showtimes": [{
{
"theatre": {
"id": "9489",
"name": "Alamo Drafthouse at the Ritz"
},
"dateTime": "2018-11-10T19:15",
"barg": false,
"ticketURI": "http://www.fandango.com/tms.asp?t=AAUQP&m=185586&d=2018-11-10"
}, {
"theatre": {
"id": "9489",
"name": "Alamo Drafthouse at the Ritz"
},
"dateTime": "2018-11-10T22:30",
"barg": false,
"ticketURI": "http://www.fandango.com/tms.asp?t=AAUQP&m=185586&d=2018-11-10"
}, {
"theatre": {
"id": "5084",
"name": "Alamo Drafthouse South Lamar"
},
"dateTime": "2018-11-10T12:00",
"barg": false,
"ticketURI": "http://www.fandango.com/tms.asp?t=AATHS&m=185586&d=2018-11-10"
}, {
"theatre": {
"id": "5084",
"name": "Alamo Drafthouse South Lamar"
},
"dateTime": "2018-11-10T15:40",
"barg": false,
"ticketURI": "http://www.fandango.com/tms.asp?t=AATHS&m=185586&d=2018-11-10"
},
}]
}]
Here is my api code:
var shows = [Shows]()
struct Shows: Codable {
let showtimes: [Showtimes]
struct Showtimes: Codable {
let theatre: Theater
struct Theater: Codable {
let id: String
let name: String
}
}
}
func loadShowtimes() {
let apiKey = ""
let today = "2018-11-10"
let zip = "78701"
let filmId = "MV011110340000"
let radius = "15"
let url = URL(string: "http://data.tmsapi.com/v1.1/movies/\(filmId)/showings?startDate=\(today)&numDays=5&zip=\(zip)&radius=\(radius)&api_key=\(apiKey)")
let request = URLRequest(
url: url! as URL,
cachePolicy: URLRequest.CachePolicy.reloadIgnoringLocalCacheData,
timeoutInterval: 10 )
let session = URLSession (
configuration: URLSessionConfiguration.default,
delegate: nil,
delegateQueue: OperationQueue.main
)
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
if let data = data {
do { let shows = try! JSONDecoder().decode([Shows].self, from: data)
self.shows = shows
}
}
})
task.resume()
}
How would I approach sorting through the array and finding each instance of name: being different, then take each name and place them into a new array?
There are several ways to iterate through your array of Shows and their array of Theater to get the complete list of names. Once you have the full list of names you can get a unique list of those names.
Here is one approach:
let names = Array(Set(shows.map { $0.showtimes.map { $0.theatre.name }}.reduce([]) { $0 + $1 }))
Let's split that up to better explain what is going on.
let allNames = shows.map { $0.showtimes.map { $0.theatre.name }}.reduce([]) { $0 + $1 }
let uniqueNames = Array(Set(allNames))
The shows.map iterates through each Shows in shows. The inner map in turn iterates each Theatre in each of those Shows returning its name. So the inner map gives an array of names. The first map results in an array of arrays of names. The reduce merges those arrays of names into a single array of names leaving allNames with a single array containing every name.
The use of Array(Set(allNames)) first creates a unique set of the names and then it creates an array from that set.
If you want the final result to be sorted alphabetically then add .sorted() to the end.
If you need to keep the original order you can make use of NSOrderedSet and remove any use of sorted.
let names = NSOrderedSet(array: shows.map { $0.showtimes.map { $0.theatre.name }}.reduce([]) { $0 + $1 }).array as! [String]

swift 4 decode json with unknown root name to table view [duplicate]

This question already has answers here:
Using Codable on a dynamic type/object
(2 answers)
Closed 4 years ago.
I have a json data like this, the root element will be generate by php which data is stored in mysql, and in the future it will be increased or change
{
"Category name 1": [
{
"name": "name 1",
"URL": "http://google.com"
}
],
"Php generated Category name 2": [
{
"name": "name 2",
"URL": "http://google.com"
}
]
}
what I want is i need the category name to be table view section header title so the section row will be listed nicely
however all information that I googled was provided that category name is a fixed name
Thanks in advance
From the data you posted, it looks like you will have this kind of a model:
struct Category: Decodable {
let name: String
let content: [Content]
struct Content: Decodable {
let name: String
let URL: String
}
}
In order to decode the JSON structure to match this model, we will need to do some custom parsing. The first issue we need to address is that we don't know the names of each category in order to parse it, since the key for the category is also the name of it. We will need to introduce a CodingKey that can take any String value, so that it can dynamically load any JSON string key.
/// When encoding/decoding, this struct allows you to dynamically read/create any coding key without knowing the values ahead of time.
struct DynamicCodingKey: CodingKey {
var stringValue: String
init?(stringValue: String) { self.stringValue = stringValue }
var intValue: Int? { return nil }
init?(intValue: Int) { return nil }
}
We will also need a new type that we can use to handle the custom parsing for the entire JSON list. With that new type, we must implement Decodable.init(from:) to do our custom parsing.
struct CategoryList: Decodable {
let categories: [Category]
// This is the model we created in the first step.
struct Category: Decodable {...}
init(from decoder: Decoder) throws {
// Key the JSON container with our dynamic keys.
let categoriesContainer = try decoder.container(keyedBy: DynamicCodingKey.self)
// The container's keys will be the names of each of the categories.
// We can loop over each key and decode the Content from the JSON for that
// key, then use the key as the name to create our Category.
categories = try categoriesContainer.allKeys.map { key in
let content = try categoriesContainer.decode([Category.Content].self, forKey: key)
return Category(name: key.stringValue, content: content)
}
}
}
With this CategoryList JSON decoding wrapper, we can decode the JSON in a way that fits our model, and use the model to populate the table with sections. Each Category in the CategoryList would be a section, and each Content within the Category would be a row.
let categories = try JSONDecoder().decode(CategoryList.self, from: data).categories
You can try this structure , and set the generated category title inside the key named title
{
"AllCategories": [{
"title":"any1",
"content" : [
{
"name": "name 1",
"URL": "http://google.com"
},
{
"name": "name 1",
"URL": "http://google.com"
}
]
},
{
"title":"any2",
"content" : [
{
"name": "name 1",
"URL": "http://google.com"
},
{
"name": "name 1",
"URL": "http://google.com"
}
]
}
]
}

json parsing in swift

Here is my Json
{
"id": "63",
"name": "Magnet",
"price": "₹1250",
"description": "",
"image": [
"catalog/IMG-20150119-WA0012_azw1e3ge.jpg",
"catalog/IMG-20150119-WA0029_6mr3ndda.jpg",
"catalog/IMG-20150119-WA0028_ooc2ea52.jpg",
"catalog/IMG-20150119-WA0026_4wjz5882.jpg",
"catalog/IMG-20150119-WA0024_e38xvczi.jpg",
"catalog/IMG-20150119-WA0020_vyzhfkvf.jpg",
"catalog/IMG-20150119-WA0018_u686bmde.jpg",
"catalog/IMG-20150119-WA0016_c8ffp19i.jpg"
],
"thumb_image": [
"cache/catalog/IMG-20150119-WA0012_azw1e3ge-300x412.jpg",
"cache/catalog/IMG-20150119-WA0029_6mr3ndda-300x412.jpg",
"cache/catalog/IMG-20150119-WA0028_ooc2ea52-300x412.jpg",
"cache/catalog/IMG-20150119-WA0026_4wjz5882-300x412.jpg",
"cache/catalog/IMG-20150119-WA0024_e38xvczi-300x412.jpg",
"cache/catalog/IMG-20150119-WA0020_vyzhfkvf-300x412.jpg",
"cache/catalog/IMG-20150119-WA0018_u686bmde-300x412.jpg",
"cache/catalog/IMG-20150119-WA0016_c8ffp19i-300x412.jpg"
],
"specifications": [
{
"Fabrics": [
"Pure chiffon straight cut suits 48" length"
]
},
{
"MOQ": [
"Minimum 10"
]
}
]
}
In above json string the "specification" arraylist has dynamic number of key and each key has dynamic number of values
So how can parse this? Please help if anyone knows this...
Thanks in advance
There are multiple ways to do parsing. In your case specifications should be an array, so you'll be able to loop on each items.
You might want to :
Create your own JSON parse class / methods ;
Use an existing library to parse JSON.
For the second option, you can give a look at the following :
https://github.com/Wolg/awesome-swift#jsonxml-manipulation
var yourJson = data as? NSDictionary
if let id = yourJson.valueForKey("id") as String
{
//save your id from json
}
if let name = yourJson.valueForKey("name") as String
{
//save your name
}
...
if let images = yourJson.valueForKey("image") as NSArray
{
for im in images
{
//save image
}
//the same for all othe images
}
... And so on...
You should also watch some tutorials, to understand the basics of JSON parsing..
https://www.youtube.com/watch?v=MtcscjMxxq4

Resources