0

I have a standard URL get request that returns a JSON response. The standard JSON response is written something like this

//get request for the user
{
"USER":
    {
    "NAME":"myName",
    "ID":10000
    }
}
//get request for the forums
{  
"FORUM":[  
  {  
     "SORT":1,
     "ID":35,
     "TITLE":"Feedback",
     "SECTION":"secion1"
  },
  {  
     "SORT":2,
     "ID":92,
     "TITLE":"Choosing an ISP",
     "SECTION":"secion2"
  },
  {  
     "SORT":3,
     "ID":100,
     "TITLE":"Broadband",
     "SECTION":"section2"
  },
  {  
     "SORT":4,
     "ID":142,
     "TITLE":"“NBN”",
     "SECTION":"section2"
  },
 ]
}
//get request for news
{"NEWS":
  [
    {
      "DATE":"2018-10-13T03:56:06+1000",
      "SOURCE":"smh.com.au",
      "BLURB":"Assistant Treasurer Stuart Robert says he has repaid $37,975 of \"excess usage charges\" in home internet bills footed by taxpayers.",
      "ID":102347,
      "TITLE":"Stuart Robert pays back $38,000 in excessive home internet charges"
    },
    {
      "DATE":"2018-10-12T18:00:38+1000",
      "SOURCE":"itwire.com",
      "BLURB":"The CVC costs set by the NBN Co make it very difficult for ISPs to offer gigabit connections to more than a select band of customers who are willing to sign up in numbers and pay slightly more than other speed tiers, according to one ISP who caters to this type of consumer.",
      "ID":102343,
      "TITLE":"NBN gigabit connections will remain mostly a pipe dream"},
    {
      "DATE":"2018-10-12T09:48:43+1000",
      "SOURCE":"computerworld.com.au",
      "BLURB":"The Department of Home Affairs has rejects calls to include independent judicial oversight of the decision to issue Technical Assistance Notices and Technical Capability Notices as part of proposed legislation intended to tackle police agencies’ inability to access encrypted communications services.",
      "ID":102342,
      "TITLE":"Home Affairs rejects calls for additional safeguards in ‘spyware’ law"
    },
    {
    "DATE":"2018-10-11T12:16:05+1000",
    "SOURCE":"itnews.com.au",
    "BLURB":"NBN Co is hoping to “speed up” building works on the fibre-to-the-curb (FTTC) portion of its network as it tries to make up lost ground.",
    "ID":102334,
    "TITLE":"NBN Co says fibre-to-the-curb build is more complex that it hoped"
    },
  ]
}

As we can see, there's top level JSON object for each request and some requests may have an array of nested dictionaries and some like the User request don't.

Issue

I've been googling for a few hours now and have come across dynamic keys and here but not quite what I was after. As the type remains the same. I'd like to have some sort of Generic type that is able to be identified during runtime. A simple method would be to do switch/if statements, but I'd like to be able to let this grow/shrink without much maintenance and repeated code - i'd need to repeat t he URLSession block each time I make a different request.

URLSession.shared.dataTask(with: url) { (data, response, err) in
        guard let dataStr = data else {
            return
        }
        do {

            let json = try JSONSerialization.jsonObject(with: dataStr, options: [.mutableContainers, .allowFragments]) as! [String: Any]
            print("json - \(json)")

            let decoder = JSONDecoder()
            decoder.dateDecodingStrategy = .iso8601
            let root = try decoder.decode(RootUser.self, from: dataStr)// **Identify the Type.self during runtime without conditional statements** 
            print(root)


        } catch let jsonErr {
            print("error serializing json", jsonErr)
        }

        }.resume()

    URLSession.shared.dataTask(with: url) { (data, response, err) in
        //....
            let root = try decoder.decode(RootNews.self, from: dataStr)// **Identify the Type.self during runtime without conditional statements** 
       //...

        }.resume()

    URLSession.shared.dataTask(with: url) { (data, response, err) in
        //....
            let root = try decoder.decode(RootForum.self, from: dataStr)// **Identify the Type.self during runtime without conditional statements** 
       //...

        }.resume()

Question

I'm wondering how can I group these requests into a generic approach without using conditionals

Update

I've expanded a little on the accepted answer based on a generic fetch function. By combining my structures (RootUser, RootNews) into one structure

struct Base: Decodable {
let news: [News]?
let user: User?

enum CodingKeys: String, CodingKey {
    case news = "NEWS"
    case user = "USER"
}
}

Now when I call the fetch function, I only need to modify the URL not the type (RootUser,RootNews or Foo in the accepted answer), it'll be just type Base.

Mark
  • 621
  • 1
  • 10
  • 24
  • 2
    The topic is a bit misleading. You got 3 different requests with static keys and types. I'd use a switch on the URL which is the distinguishing feature. By the way: remove the `options` parameter for 3 reasons: 1) Generally `.mutableContainers` and `.allowFragments` is pointless in Swift. 2) You assign the result to an **im**mutable constant (`let`). 3) You are only reading the JSON, you are not going to mutate it. – vadian Oct 15 '18 at 07:04
  • thanks for the pointers. I had .allowFragments as it didn't parse in an earlier version of my code. – Mark Oct 15 '18 at 07:17
  • 2
    `Allowfragments` is only reasonable if the root object is **not** an array or dictionary. In your case it couldn't be the parsing failure reason. – vadian Oct 15 '18 at 07:21

1 Answers1

1

Not sure if this answers your question, but you can make your network requests generic for any Decodable.

    // Sample Model to be decoded
    struct Foo: Decodable {
        var id: String
        var name: String
    }

    // Generic function to fetch decodables
    func fetch<T: Decodable>(for url: URL, complete: @escaping (T?) -> Void) {
        URLSession.shared.dataTask(with: url) { (data, response, err) in
            do {
                let model = try JSONDecoder().decode(T.self, from: data!)
                complete(model)
            }
            catch {
                print(error)
                complete(nil)
            }
        }
    }

    // Test
    let request = URL(string: "some://url.com")
    fetch(for: request!) { (model: Foo?) -> Void in
        print("found \(model)")
    }

Test caller knows which model it's expecting so the Type Foo is inferred and correctly decoded at run time.

justintime
  • 352
  • 3
  • 11
  • you live up to your name. This is very close to what I am aiming for. One method that encapsulates each request – Mark Oct 15 '18 at 12:50