How to integrate data from a web service into an array

Hello,

This test code for creating an array using a loop works:


var quotes: [(id: String, name: String)] {
    var output: [(id: String, name: String)] = []
    for i in 1...numberOfRows {
        let item: (id: String, name: String) = ("\(i)", "Name \(i)")
        output.append(item)
    }
    return output
}

But if I try to apply this logic to retrieving data from a web service using the below code I am getting 2 errors:

For the line “quotes.append(item)” I am getting the error message “Cannot use mutating member on immutable value: ‘quotes’ is a get-only property."

For the line “return output” I am getting the error message “Cannot find ‘output’ in scope."

    if let url = URL(string:"https://www.TEST.com/test_connection.php"){
        URLSession.shared.dataTask(with: url) { (data, response, error) in
            if let data = data{
                if let json = try? JSONDecoder().decode([[String:String]].self, from: data){
                    json.forEach { row in
                        var item: (id: String, name: String) = ("test id value", "test name value")
                        quotes.append(item)
                    }
                    return output
                }
            }
        }
}

OK, quotes is no more an array of dict but an array of t-uples. Is it on purpose ?

Look at your code.

  • quotes is a computed property for which you have defined only a get and no set (see Swift doc to understand precisely). So you cannot modify it.
  • you have not defined output.
  • what is the decode type ? Do you want a dictionary ? But that's not how quotes is declared. Same confusion as before
[[String:String]]

You should refactor your code and put the url related code inside the quotes declaration.

Try this (I have not set up the php so I cannot fully test) and tell if it works:

var quotes: [(id: String, name: String)] {
    var output: [(id: String, name: String)] = []
    if let url = URL(string:"https://www.TEST.com/test_connection.php") {
        URLSession.shared.dataTask(with: url) { (data, response, error) in
            if let data = data {
                if let json = try? JSONDecoder().decode([[String:String]].self, from: data){
                    json.forEach { row in
                        var item: (id: String, name: String) = ("test id value", "test name value")
                        output.append(item)
                    }
                }
            }
        }
    }
    return output
}

Note that with recent versions of Swift, you don't need anymore to write

if let data = data

if it is the same name, but simply

if let data {

There is something curious in your code. You append the same item for all rows in the json.

It should probably be something like (I'm guessing about your Json, it would help if you showed it):

let item: (id: String, name: String) = (id: row.id, name: row.name) // ("test id value", "test name value") - no need for var, let is enough

PS: it is a good practice here to define a type alias:

So code would look like:

typealias Item = (id: String, name: String)

var quotes: [Item] {
    var output: [Item] = []

    if let url = URL(string:"https://www.TEST.com/test_connection.php") {
        URLSession.shared.dataTask(with: url) { (data, response, error) in
            if let data {
                if let json = try? JSONDecoder().decode([[String:String]].self, from: data){  // if 
                    json.forEach { row in
                        let item: Item = (id: row.id, name: row.name) //  ("test id value", "test name value")
                        output.append(item)
                    }
                }
            }
        }
    }
    return output
}

If effectively the JSON is an array of Dict, you should more have something like;

typealias MyDict = [String: String] // A dictionary is defined as [key:Value]

var quotes: [MyDict] {
    var output: [MyDict] = []
    if let url = URL(string:"https://www.TEST.com/test_connection.php") {
        URLSession.shared.dataTask(with: url) { (data, response, error) in
            if let data { //
                if let json = try? JSONDecoder().decode([MyDict].self, from: data){
                    output = json // That's the beauty of decoder to convert directly in the right structure [MyDict]
                }
            }
        }
    }
    return output
}

To further test, I mimicked your JSON, considering it would look like this (each element with an id and a name). They are in fact Dictionaries:

[{"id":"First","name":"One"},
{"id":"Second","name":"Two"},
{"id":"Third","name":"Three"}]

If so, the test code (I commented out the url part to replace by hardwired data)

typealias MyDict = [String: String] // A dictionary is defined as [key:Value]

struct Item : Codable {  // Need Codable to be used by Decoder
    var id: String              // Identical to the key:value of the JSON (you can later change the name using CodingKeys …
    var name: String
}

// ----------- this part for hardwired test  --------------
// This to create data by hand ; in your case, data will come from php request 
let one = Item(id: "First", name:"One")
let two = Item(id: "Second", name:"Two")
let three = Item(id: "Third", name:"Three")
let itemData = [one, two, three]    // Here, 3 elements in JSON

let data = try JSONEncoder().encode(itemData)      // create the data that you'll in fact retrieve from url
print("JSON Data", String(data: testJSONData, encoding: .utf8)!)    // To check what is in data
// -------------------------------------------------------

var quotes: [MyDict] {    // back to array of Dict
    var output: [MyDict] = []
// ----------- 3 lines commented for test with hardwired data --------------
    // if let url = URL(string:"https://www.TEST.com/test_connection.php") {
        // URLSession.shared.dataTask(with: url) { (data, response, error) in
            // if let data {            // data were built with encode, but that would work with url if you uncomment those 3 lines

                if let json = try? JSONDecoder().decode([MyDict].self, from: data) {
                    output = json
                }
            // }
        // }
    // }
    return output
}

print("quotes", quotes)

You get this (once again, dictionaries are not ordered):

JSON Data [{"id":"First","name":"One"},{"id":"Second","name":"Two"},{"id":"Third","name":"Three"}]
quotes [["name": "One", "id": "First"], ["name": "Two", "id": "Second"], ["name": "Three", "id": "Third"]]

if json in server is like this:

[{"id":"First","name":"One"},
{"id":"Second","name":"Two"},
{"id":"Third","name":"Three"}]

Your own code should then be:

struct Item : Codable {  // Need Codable to be used by Decoder
    var id: String       // Identical to the key:value of the JSON (you can later change the name using CodingKeys …
    var name: String
}

var quotes: [Item] {   
    var output: [Item] = []
    if let url = URL(string:"https://www.TEST.com/test_connection.php") {
        URLSession.shared.dataTask(with: url) { (data, response, error) in
            if let data {    
                if let json = try? JSONDecoder().decode([Item].self, from: data) {
                    output = json
                }
            }
        }
    }
    return output
}
How to integrate data from a web service into an array
 
 
Q