0

I have the following data in JSON Format:

{"contacts":[{"name":"Bob"},{"name":"Greg"},{"name":"Sally"},{"name":"Will"},{"name":"George"},
{"name":"Charlie"},{"name":"Alice"},{"name":"Albert"},{"name":"Alfred"},{"name":"Bart"}, 
{"name":"Dan"},{"name":"Dave"},{"name":"Eddie"},{"name":"Frank"},{"name":"Ralph"},{"name":"Tammy"}, 
{"name":"Tom"},{"name":"James"},{"name":"John"},{"name":"Quincy"},{"name":"Peter"},{"name":"Richard"}, 
{"name":"Zachary"},{"name":"Zbiginew"},{"name":"Tentacool"},{"name":"Tentacruel"},{"name":"Metapod"}, 
{"name":"Meowth"}]}

The JSON is stored in some URL, and I want to gather the info and put it into an array that, ideally looks like [Bob, Greg, Sally, Will...].

Right now I've got the code:

let jsonLocation = "http://raw.myjsondatalocation.com/contact.json"
        guard let url = URL(string: jsonLocation) else {
            return
        }

        URLSession.shared.dataTask(with: url){ (data,response,error) in
            guard let data = data else { return }
            do{
                let decoder = JSONDecoder()
                let ListOfNames = try decoder.decode(Contact.self, from:data).contacts
                // print(myListOfContacts)
                print(ListOfNames[0].name)
            }
            catch let jsonErr{
                print("Error!",jsonErr)
            }
            }.resume()

I use a Codable to parse JSON. This seems to work fine, as it returns an array of items with the form [[ContactList.Contact.PersonalInfo(name: "Bob")... and when I am calling print(ListOfNames[0].name), it appropriately returns "Bob" but if I put it outside of URLSession, it says it's unresolved.

The array of names will later be put into a TableView. I really don't know how to solve this. Can anyone give me a hand here?

  • Declare the `listOfNames` property as a `var` outside of the `URLSession` block. Then inside the block you use `listOfNames = try decoder ...`. Note that per Swift convention, property names start with a lowercase. – koen Jun 10 '20 at 18:26
  • Don't map the struct array to a string array. Use `PersonalInfo` as data source array. Maybe you want to add more *personal info* in the future. – vadian Jun 10 '20 at 18:27
  • Does this answer your question? [Returning data from async call in Swift function](https://stackoverflow.com/questions/25203556/returning-data-from-async-call-in-swift-function) – Joakim Danielson Jun 10 '20 at 19:30

3 Answers3

0

https://app.quicktype.io/ using this website above, paste in your json response... this website will give you class or struct code depending on how you want to handle it.

here is the result

// MARK: - Contacts
class Contacts: Codable {
    let contacts: [Contact]

    init(contacts: [Contact]) {
        self.contacts = contacts
    }
}

// MARK: - Contact
class Contact: Codable {
    let name: String

    init(name: String) {
        self.name = name
    }
}

using this you are able to then store data into this class array.

create an array.

var testArr = [Contacts]()

in your code as you posted

    let jsonLocation = "http://raw.myjsondatalocation.com/contact.json"
    guard let url = URL(string: jsonLocation) else {
        return
    }

    URLSession.shared.dataTask(with: url){ (data,response,error) in
        guard let data = data else { return }
        do{
            let decoder = JSONDecoder()
            let ListOfNames = try decoder.decode(Contact.self, from:data).contacts
            // print(myListOfContacts)
            print(ListOfNames[0].name)
        }
        catch let jsonErr{
            print("Error!",jsonErr)
        }
        }.resume()

you need to now append properly to the class array. Inside the code block below see the changes.

URLSession.shared.dataTask(with: url){ (data,response,error) in
                guard let data = data else { return }
            do{
                let decoder = JSONDecoder()
                let ListOfNames = try decoder.decode(Contact.self, from:data).contacts
                // print(myListOfContacts)
                for x in ListOfNames{
                    self.testArr.append(Contacts(contacts: x))
                }
                dump(self.testArr)
            }

EDIT --

Not sure if this will work but worth a try.

URLSession.shared.dataTask(with: url){ (data,response,error) in
                guard let data = data else { return }
            do{
                let decoder = JSONDecoder()
                let jsonData = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
                // print(myListOfContacts)
                for x in jsonData{
                    self.testArr.append(x)
                }
                dump(self.testArr)
            }

My thought here is you take the raw json, loop through it and append the raw data to a standard string array.

Once you have the data stored in the array you can then do an append to your class array as you normally would.

for x in self.testArr{
    self.classArray.append(Contacts(contacts: x))
}
Julian Silvestri
  • 1,970
  • 1
  • 15
  • 33
  • Hi, thanks for your advice. However we have to use the Codable they gave us. I tried to use for x in ListOfNames{print(x.name) self.testArr.append(String(x.name)) } – Ptrick Mahny Jun 10 '20 at 18:56
  • We are required to use the Codable file they supplied. We can't use our own. I declared a ````var testArr = [String]()```` and then tried adding this: for x in ListOfNames{ print(x.name) self.testArr.append(String(x.name))} But it is still returning an empty array for me. – Ptrick Mahny Jun 10 '20 at 18:57
  • @PtrickMahny well can you share that file ? I am assuming no. Would be very helpful to solving problem. – Julian Silvestri Jun 10 '20 at 19:31
0

You'll get empty when printing outside the request because the request is an asynchronous function and the array gets populate only after the data is received from the network. You need to move your request call into a function and call it using a completion block that will provide the array of [Contact]'s. Here is an example:

func getContacts(completion: @escaping (Contact?) -> Void) {
    let jsonLocation = "http://raw.myjsondatalocation.com/contact.json"
    guard let url = URL(string: jsonLocation) else {
        completion(nil)
        return
    }

    URLSession.shared.dataTask(with: url) { (data,response,error) in
        guard let data = data else { return }
        do{
            let decoder = JSONDecoder()
            let contact = try decoder.decode(Contact.self, from:data)
            completion(contact)
        }
        catch let jsonErr{
            print("Error!",jsonErr)
            completion(nil)
        }
    }.resume()
}

And call it like this:

getContacts { contact in
    print(contact?.contacts[0].name)
}
Frankenstein
  • 15,732
  • 4
  • 22
  • 47
0

You can use map to achieve what you want, assuming you properly decoded your data. Make sure your listOfNames variable doesn't start with a capital letter since it's a variable.

  1. Declare a variable of type [Contact] that holds your array of names called arrayOfContactNames:

    var arrayOfContactNames: [Contact] = []

  2. Once your data is decoded map your listOfNames and assign to arrayOfContactNames and if needed, call self.tableView.reloadData():

    let jsonLocation = "http://raw.myjsondatalocation.com/contact.json"
            guard let url = URL(string: jsonLocation) else {
                return
            }

            URLSession.shared.dataTask(with: url){ (data,response,error) in
                guard let data = data else { return }
                do{
                    let decoder = JSONDecoder()
                    let listOfNames = try decoder.decode(Contact.self, from:data).contacts
                    // print(myListOfContacts)
                    print(listOfNames[0].name)
                    arrayOfContactNames = listOfNames.map { $0.name } 
                    // Once your data obtained, reload your tableview on the main thread to populate it
                    DispatchQueue.main.async {
                       self.tableView.reloadData()
                    }
                }
                catch let jsonErr{
                    print("Error!",jsonErr)
                }
                }.resume()
rs7
  • 1,618
  • 1
  • 8
  • 16