1

I would like to decode two JSON urls that have different fields for the same item and then combine them into one model object using a struct or class. I am able to work with one JSON url and decode it as needed, but am struggling with adding the second url. I currently have been using structs, but I will be working with large amounts of data, so I understand a class would be better to use here. Any other suggestions would be greatly appreciated. I have searched quite a bit but have been unable to find information that is relevant to this situation in swift 4.

I have below an example that I hope can be manipulated to perform what I'm trying to do. Here we have 1 endpoint that provides state, abbreviation, and region members. The other endpoint provides state, population, and area.

class CollectionViewController: UIViewController {

let urlStates1 = "https://......endpoint1"
let urlStates2 = "https://......endpoint2"

var states = [StatesData]()

override func viewDidLoad() {
    super.viewDidLoad()

    downloadJSON() {
        self.CollectionView.reloadData()
    }
}

func downloadJSON(completed:@escaping ()->()){
    let url1 = URL(string: urlStates1)

    URLSession.shared.dataTask(with: url1!) { (data, response, error) in
        if error == nil {
            do{
                self.states = try JSONDecoder().decode([StatesData].self, from: data!)
                DispatchQueue.main.async{
                    completed()
                }
            } catch {
                print("JSON Error")
            }}
        }.resume()
}

Here's my struct

struct StatesData : Decodable {

//////Members from URL1///////

   var state : String?
   var abrev : String?
   var region : String?

//////Members specific to URL2///////

   var population : Int?
   var area : Double?

private enum AttributeKeys: String, CodingKey {
    case state = "state"
    case abrev = "abrev"
    case region = "region"
   }

 private enum StatisticsKeys: String, CodingKey {
    case state = "state"
    case population = "population"
    case area = "area"
  }

init(from decoder: Decoder) throws {
    let values = try decoder.container(keyedBy: AttributeKeys.self)
    if let stateName = try values.decodeIfPresent(String.self, forKey: .state) {
        state = stateName
        abrev = try values.decodeIfPresent(String.self, forKey: .abrev)!
          region = try values.decodeIfPresent(String.self, forKey: .region)!
    } else {
        let values = try decoder.container(keyedBy: StatisticsKeys.self)
        state = try values.decodeIfPresent(String.self, forKey: .state)!
        population = try values.decodeIfPresent(Int.self, forKey: .population)!
        area = try values.decodeIfPresent(Double.self, forKey: .area)!
    }
   }
 }
}

Let's say the JSON response for urlStates1 is as follows

[
 {
  "state": "Connecticut",
  "abrev": "CT",
  "region": "Northeast"
 },
 {
  "state": "Texas",
  "abrev": "TX",
  "region": "Southwest"
 }
]

And the JSON response for urlStates2 is as follows

[
 {
  "state": "Connecticut",
  "population": 3588000,
  "area": 5543.00
 },
 {
  "state": "Texas",
  "population": 28300000,
  "area": 268597.00
 }
]
greg42627
  • 79
  • 6

1 Answers1

0

In your current implementation, there are two issues in your StatesData struct.

1) Property types are not matching the value types in the response (i.e population and area).

2) Your if statement is always true in decoder as the key .state is present to both kind of responses so it never decodes second type of response.

Please see the corrected StatesData, Now you should be able to decode the responses correctly.

struct StatesData : Decodable {

//////Members from URL1///////

var state : String?
var abrev : String?
var region : String?

//////Members specific to URL2///////

var population : String?
var area : String?

private enum AttributeKeys: String, CodingKey {
    case state = "state"
    case abrev = "abrev"
    case region = "region"
}

private enum StatisticsKeys: String, CodingKey {
    case state = "state"
    case population = "population"
    case area = "area"
}

init(from decoder: Decoder) throws {
    let values = try decoder.container(keyedBy: AttributeKeys.self)
    if let abrevName = try values.decodeIfPresent(String.self, forKey: .abrev) {
        state = try values.decodeIfPresent(String.self, forKey: .state)
        abrev = abrevName
        region = try values.decodeIfPresent(String.self, forKey: .region)
    } else {
        let values = try decoder.container(keyedBy: StatisticsKeys.self)
        state = try values.decodeIfPresent(String.self, forKey: .state)
        population = try values.decodeIfPresent(String.self, forKey: .population)
        area = try values.decodeIfPresent(String.self, forKey: .area)
    }
   }
}
Kamran
  • 14,987
  • 4
  • 33
  • 51
  • Thank you! I fixed the JSON response to be the correct Int & Double values. I am now able to decode both JSON urls into two separate arrays. Now how would I be able to merge the two arrays into one so that all members are contained in a single model object? – greg42627 Jun 22 '18 at 00:00
  • Adding/merging two arrays is simple addition in swift like let arrayC = arrayA + arrayB. You can see more details here https://stackoverflow.com/questions/25146382/how-do-i-concatenate-or-merge-arrays-in-swift – Kamran Jun 22 '18 at 00:05