-2

I've a JSON (for now in local), that I want to parse to put these data in a listView.

I've already created the view and tried a few things (like this tutorial : https://www.journaldev.com/21839/ios-swift-json-parsing-tutorial) to parse that JSON, with no success.

Here is some code I tried :

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

@IBOutlet weak var labelHeader: UILabel!
@IBOutlet weak var tableView: UITableView!

var channelList = [channelData]()

override func viewDidLoad() {
    super.viewDidLoad()
let url = Bundle.main.url(forResource: "channels", withExtension: "json")

    guard let jsonData = url
        else{
            print("data not found")
            return
    }

    guard let data = try? Data(contentsOf: jsonData) else { return }

    guard let json = try? JSONSerialization.jsonObject(with: data, options: []) else{return}

    if let dictionary = json as? [String: Any] {

        if let title = dictionary["title"] as? String {
            print("in title")
            labelHeader.text = title
        }

        if let data = dictionary["data"] as? Any {
            print("data is \(data)")
        }
        if let date = dictionary["date"] as? Date {
            print("date is \(date)")
        }
        // And so on

        for (key, value) in dictionary {
            print("Key is: \(key) and value is \(value)" )
            //This print the whole JSON to the console.
        }
    }

    //Now lets populate our TableView
    let newUrl = Bundle.main.url(forResource: "channels", withExtension: "json")

    guard let j = newUrl
        else{
            print("data not found")
            return
    }

    guard let d = try? Data(contentsOf: j)
        else { print("failed")
            return
    }

    guard let rootJSON = try? JSONSerialization.jsonObject(with: d, options: [])
        else{ print("failedh")
            return
    }

    if let JSON = rootJSON as? [String: Any] {
        labelHeader.text = JSON["id"] as? String //Should update the Label in the ListView with the ID found in the JSON

        guard let jsonArray = JSON["type"] as? [[String: Any]] else {
            return
        }

        let name = jsonArray[0]["name"] as? String
        print(name ?? "NA")
        print(jsonArray.last!["date"] as? Int ?? 1970)

        channelList = jsonArray.compactMap{return channelData($0)}

        self.tableView.reloadData()

    }
}

Here is a sample of the JSON file :

{
"format": "json",
"data": [
    {
        "type": "channel",
        "id": "123",
        "updated_at": "2019-05-03 11:32:57",
        "context": "search",
        "relationships": {
            "recipients": [
                {
                    "type": "user",
                    "id": 321,
                    "participant_id": 456
                }
            ],
            "search": {
                "type": "search",
                "title": "Title"
            },
        }
    },

I'd like to find the best way to work with kind of this JSON.

For now I'm not able to get the data to the listView. The most I have is my JSON in the console of xCode (at least it means I'm able to open the JSON).

Hawkydoky
  • 194
  • 1
  • 15
  • You have a long way to go before this question is ready for an answer. See the [JSONSerialization](https://developer.apple.com/documentation/foundation/jsonserialization) class for a place to start. There are also libraries for working with JSON data. – strwils May 07 '19 at 19:31
  • Parsing JSON is one of the most frequently asked questions. There are [more than 4000 related questions](https://stackoverflow.com/search?q=%5Bswift%5D+parse+json) – vadian May 07 '19 at 19:34
  • I didn't wanna post my whole code but I'll update the question with some more code. @vadian the problem is that the questions I found that could be related to my case were outdated (3 years or more). – Hawkydoky May 07 '19 at 19:46
  • Don't you realize that your JSON doesn't contain keys like `name` and `date`? Please learn to **read** JSON, it's really easy. I wrote a quick overview [here](https://stackoverflow.com/questions/39423367/correctly-parsing-json-in-swift-3/39423764#39423764) – vadian May 07 '19 at 20:06
  • @vadian No I don't. I'm a noob at using JSON, and didn't use Swift for more than a year now, so i'm rusty. I'll have a look at your answer, thanks. – Hawkydoky May 07 '19 at 20:16
  • @vadian My JSON is a local file, does it change a lot of things from your example? – Hawkydoky May 07 '19 at 20:20
  • No, but if it's a local file you can omit all the checks like `guard let`, `if let` and `try?` as you exactly **know** what the file contains. Files in the application bundle are immutable. Any crash reveals a **design** mistake. – vadian May 07 '19 at 20:28
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/193040/discussion-between-hawkydoky-and-vadian). – Hawkydoky May 08 '19 at 15:00
  • I wrote an answer. – vadian May 08 '19 at 15:21

2 Answers2

0

Please use this as your base code on your current development. I've made use of Structs that will help you keep an order on your JSON model aswell encode it properly and use it as an object somewhere in the future.

STRUCTS

// http request response results iTunes Site.
struct SearchResult: Decodable {
    let resultCount: Int
    let results: [Result]
}

// Raw Result
struct Result: Decodable {
    let trackId: Int
    let artistId: Int
    let artistName: String
    let collectionName: String
    let trackName: String
    let artworkUrl30: String
    let artworkUrl60: String
    let artworkUrl100: String
    let primaryGenreName: String
    let trackPrice: Float
    let collectionPrice: Float
    let trackTimeMillis: Int

}

FUNCTION CODE

func fetchArtists(searchTerm: String, completion: @escaping (SearchResult?, Error?) -> ()) {
        let escapedString = searchTerm.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!
        let urlString = "https://itunes.apple.com/search?term=\(escapedString)&entity=musicTrack"
        fetchGenericJSONData(urlString: urlString, completion: completion)
    }

    func fetchGenericJSONData<T: Decodable>(urlString: String, completion: @escaping (T?, Error?) -> ()) {

        guard let url = URL(string: urlString) else { return }
        URLSession.shared.dataTask(with: url) { (data, resp, err) in
            if let err = err {
                completion(nil, err)
                return
            }
            do {
                let objects = try JSONDecoder().decode(T.self, from: data!)
                completion(objects, nil)
            } catch {
                completion(nil, error)
            }
            }.resume()
    }

REQUEST CODE HOW TO USE IT.

fetchArtists(searchTerm: searchText) { res, err in
    if let err = err {
        print("Failed to fetch artists:", err)
        return
    }
    self.iTunesResults = res?.results ?? []
    print(self.iTunesResults.artistName)
    // Uncomment this in case you have a tableview to refresh
    // DispatchQueue.main.async {
    //     self.tableView.reloadData()
    // }
}
Eddwin Paz
  • 2,842
  • 4
  • 28
  • 48
0

The recommended way to parse JSON in Swift 4+ is the Codable protocol.

Create the structs

struct Root: Decodable {
    let format: String
    let data: [ChannelData]
}

struct ChannelData: Decodable {
    let type, id, updatedAt, context: String
    let relationships: Relationships
}

struct Relationships: Decodable {
    let recipients: [Recipient]
    let search: Search
}

struct Recipient: Decodable {
    let type: String
    let id: Int
    let participantId: Int
}

struct Search: Decodable {
    let type: String
    let title: String
}

As the channels.json file is in the application bundle and cannot be modified you can reduce viewDidLoad to

var channelList = [ChannelData]()

override func viewDidLoad() {
    super.viewDidLoad()
    let url = Bundle.main.url(forResource: "channels", withExtension: "json")!
    let data = try! Data(contentsOf: url)
    let decoder = JSONDecoder()
    decoder.keyDecodingStrategy = .convertFromSnakeCase
    let result = try! decoder.decode(Root.self, from: data)
    channelList = result.data
    self.tableView.reloadData()
}

If the code crashes it reveals a design mistake. The structs match the JSON in the question. Probably it's much larger, then you must adjust or extend the structs.

vadian
  • 274,689
  • 30
  • 353
  • 361
  • I understand more clearly how it works. I tried it and adapt it to my code, but I've an error: `Thread 1: Fatal error: 'try!' expression unexpectedly raised an error: Swift.DecodingError.keyNotFound(CodingKeys(stringValue: "unread_count", intValue: nil), Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "data", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0)], debugDescription: "No value associated with key CodingKeys(stringValue: \"unread_count\", intValue: nil) (\"unread_count\").", underlyingError: nil))` – Hawkydoky May 08 '19 at 16:22
  • There is no key `unread_count` in the question. It makes no sense to post pseudo-code. – vadian May 08 '19 at 16:31
  • I added it in the ChannelData (there is one in the JSON) `struct ChannelData: Decodable { let type, id, updatedAt, context: String let archived: Bool let unread_count: Int let relationships: Relationships }` The `unread_count` is not in the nested array, I was able to get it before changing my code to yours. – Hawkydoky May 08 '19 at 18:20
  • Note the key decoding strategy `.convertFromSnakeCase`. Compare the JSON key `updated_at` with the corresponding struct member. What's the difference? – vadian May 08 '19 at 18:26
  • I got it. Thanks. I had no idea the upper and lowercase could be such an issue for a variable. Thanks. I'm really sorry, but I still can't access the "rating" and it's driving me crazy after a few hours of trial... I added `let rating: Double` to the `Struct Recipient` (that's where it is in the JSON). I don't know how to access it from my code. I tried `xxxx.relationships.recipients.something` but nothing showed after i hit the . I tried to create another decoded as in your exemple, with no success... I'm really sorry to bother you, I swear I'm trying. – Hawkydoky May 09 '19 at 00:43
  • `recipients` is an array (index based). If there is only one item you need to write `xxxx.relationships.recipients[0].something` and if there are multiple items use a loop or `map`. – vadian May 09 '19 at 04:36