Parsing JSON Review – Part 2

In this lesson, we work with a heterogeneous data type found in the JSON data.

// Parsing Heterogeneous (mixed types) Type
// ex: postcode on occasion is an Int or a String
// this will lead to decoding errors while parsing

import Foundation

//==============================================
// JSON Data
//==============================================

let json = """
{
  "results": [{
      "gender": "female",
      "name": {
        "title": "Mrs",
        "first": "April",
        "last": "Watts"
      },
      "location": {
        "street": {
          "number": 6533,
          "name": "E North St"
        },
        "city": "Kansas City",
        "state": "Oklahoma",
        "country": "United States",
        "postcode": 83696,
        "coordinates": {
          "latitude": "80.0183",
          "longitude": "82.3050"
        },
        "timezone": {
          "offset": "-8:00",
          "description": "Pacific Time (US & Canada)"
        }
      },
      "email": "april.watts@example.com",
      "login": {
        "uuid": "1f72a37b-dad7-42c0-8f38-c61c8cc16fb3",
        "username": "orangerabbit451",
        "password": "colt",
        "salt": "P1EkWcX0",
        "md5": "5fd24b5b5533c30092525a7b625aeaa8",
        "sha1": "834dff83eea9a0243edfa9ce3cbaf4da2806630e",
        "sha256": "b81912ada362314edb0681234bc29792a872025a89b4f1bf7d83f63203feaeb7"
      },
      "dob": {
        "date": "1948-10-23T16:57:10.463Z",
        "age": 72
      },
      "registered": {
        "date": "2015-07-27T15:43:32.525Z",
        "age": 5
      },
      "phone": "(880)-397-2424",
      "cell": "(681)-007-8567",
      "id": {
        "name": "SSN",
        "value": "901-71-3377"
      },
      "picture": {
        "large": "https://randomuser.me/api/portraits/women/51.jpg",
        "medium": "https://randomuser.me/api/portraits/med/women/51.jpg",
        "thumbnail": "https://randomuser.me/api/portraits/thumb/women/51.jpg"
      },
      "nat": "US"
    },
    {
      "gender": "female",
      "name": {
        "title": "Ms",
        "first": "Sofia",
        "last": "Gill"
      },
      "location": {
        "street": {
          "number": 7310,
          "name": "Frederick Ave"
        },
        "city": "Georgetown",
        "state": "Nova Scotia",
        "country": "Canada",
        "postcode": "Y5N 0E9",
        "coordinates": {
          "latitude": "-0.1529",
          "longitude": "-79.4845"
        },
        "timezone": {
          "offset": "-1:00",
          "description": "Azores, Cape Verde Islands"
        }
      },
      "email": "sofia.gill@example.com",
      "login": {
        "uuid": "6ad311bd-8acf-401c-a66c-b28e5e6b7570",
        "username": "bluedog205",
        "password": "boating",
        "salt": "XtuMADSl",
        "md5": "142db797c2bc687900db98629f78a435",
        "sha1": "d71acef64a75c5cb72c4439cbdd313f4f1a3593b",
        "sha256": "1ac31d458590319d68de43a7fd8f5e0b4d91f18f48182e6b3feab263bf96ea98"
      },
      "dob": {
        "date": "1978-06-12T22:45:12.529Z",
        "age": 42
      },
      "registered": {
        "date": "2009-10-16T05:54:27.726Z",
        "age": 11
      },
      "phone": "818-113-5421",
      "cell": "824-320-7893",
      "id": {
        "name": "",
        "value": null
      },
      "picture": {
        "large": "https://randomuser.me/api/portraits/women/92.jpg",
        "medium": "https://randomuser.me/api/portraits/med/women/92.jpg",
        "thumbnail": "https://randomuser.me/api/portraits/thumb/women/92.jpg"
      },
      "nat": "CA"
    }
  ]
}
""".data(using: .utf8)!


//==============================================
// Swift Model(s)
//==============================================

// Top Level JSON data structure is a dictionary
struct ResultsWrapper: Decodable {
  let results: [Person]
}

struct Person: Decodable {
  let gender: String
  let location: Location
}

struct Location: Decodable {
  let city: String
  let country: String
  let postcode: Postcode
}

enum AppError: Error {
  case missingValue // if not an Int or String throw this error
}

// create a custom object to be the data type of postcode
enum Postcode: Decodable {
  // handle the two cases in the JSON "postcode" property
  // where postcode can be either an (Int) or a (String)
  
  // we will also use associative values to capture the
  // value of postcode
  // e.g if it's an Int we will capture the Int value
  case int(Int)
  case string(String)
  
  // overridbe the init(from decoder:) initializer
  init(from decoder: Decoder) throws {
    // handle the two cases of Int or String
    
    // if it's not an Int or a String then we will throw
    // an error
    if let intValue = try? decoder.singleValueContainer().decode(Int.self) {
      self = .int(intValue)
      return
    }
    if let stringValue = try? decoder.singleValueContainer().decode(String.self) {
      self = .string(stringValue)
      return
    }
    throw AppError.missingValue // was not an Int or String
  }
}


//==============================================
// decoding JSON to Swift objects
//==============================================

do {
  let dictionary = try JSONDecoder().decode(ResultsWrapper.self, from: json)
  let people = dictionary.results
  
  // if trying to get to Postcode
  let _ = people[0]
  let secondPerson = people[1]
  let postcode = secondPerson.location.postcode
  switch postcode {
  case .int(let intValue):
    print("postcode is \(intValue)")
  case .string(let stringValue):
    print("postcode is \(stringValue)")
  }
  
  // postcode is Y5N 0E9
  
  //dump(people)
} catch {
  dump(error)
}

More on Github

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s