Skip to content

Instantly share code, notes, and snippets.

@alexpaul
Last active July 28, 2020 17:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save alexpaul/3f81787eeffc32e11fdfeef9310cdcf8 to your computer and use it in GitHub Desktop.
Save alexpaul/3f81787eeffc32e11fdfeef9310cdcf8 to your computer and use it in GitHub Desktop.
Working with JSON. Multiple dictionary JSON. Heterogeneous JSON.

JSON Cheatsheet

1. Working with JSON where the root level is ONE dictionary

let jsonDict = """
{
 "results": [
   {
     "firstName": "John",
     "lastName": "Appleseed"
   },
  {
    "firstName": "Alex",
    "lastName": "Paul"
  }
 ]
}
""".data(using: .utf8)!

struct ContactWrapper: Decodable {
  let results: [Contact]
}

struct Contact: Decodable {
  let firstName: String
  let lastName: String
}

let contacts = try JSONDecoder().decode(ContactWrapper.self, from: jsonDict)

example("JSON dictionary") {
  dump(contacts)
}

/*
 JSON dictionary example
 ▿ __lldb_expr_116.ContactWrapper
   ▿ results: 2 elements
     ▿ __lldb_expr_116.Contact
       - firstName: "John"
       - lastName: "Appleseed"
     ▿ __lldb_expr_116.Contact
       - firstName: "Sally"
       - lastName: "Mae"
*/

2. Working with JSON where the root level is an array

let jsonArray = """
[
    {
        "title": "New York",
        "location_type": "City",
        "woeid": 2459115,
        "latt_long": "40.71455,-74.007118"
    }
]
""".data(using: .utf8)!

struct Weather: Decodable {
  let title: String
  let locationType: String
  let woeid: Int
  let coordinate: String
  
  private enum CodingKeys: String, CodingKey {
    case title
    case locationType = "location_type"
    case woeid
    case coordinate = "latt_long"
  }
}

let weather = try JSONDecoder().decode([Weather].self, from: jsonArray)

example("JSON array") {
  dump(weather)
}

/*
 JSON array example
 ▿ 1 element
   ▿ __lldb_expr_116.Weather
     - title: "New York"
     - locationType: "City"
     - woeid: 2459115
     - coordinate: "40.71455,-74.007118"
*/

3. Working with JSON where the root level is made up of multiple dictionary objects with **(multiple keys)

// rare occasions you may come across some JSON like the structure below, (multiple dictionary objects):

let jsonMultipleDictionaries = """
{
  "Afpak": {
    "id": 1,
    "race": "hybrid",
    "flavors": [
      "Earthy",
      "Chemical",
      "Pine"
    ],
    "effects": {
      "positive": [
        "Relaxed",
        "Hungry",
        "Happy",
        "Sleepy"
      ],
      "negative": [
        "Dizzy"
      ],
      "medical": [
        "Depression",
        "Insomnia",
        "Pain",
        "Stress",
        "Lack of Appetite"
      ]
    }
  },
  "African": {
    "id": 2,
    "race": "sativa",
    "flavors": [
      "Spicy/Herbal",
      "Pungent",
      "Earthy"
    ],
    "effects": {
      "positive": [
        "Euphoric",
        "Happy",
        "Creative",
        "Energetic",
        "Talkative"
      ],
      "negative": [
        "Dry Mouth"
      ],
      "medical": [
        "Depression",
        "Pain",
        "Stress",
        "Lack of Appetite",
        "Nausea",
        "Headache"
      ]
    }
  }
}
""".data(using: .utf8)!


struct Strain: Decodable {
  let id: Int
  let race: String
  let flavors: [String]
  let effects: [String: [String]]
}

struct Positive: Decodable {
  
}

example("Strains example") {
  do {
    let strainDictionaries = try JSONDecoder().decode([String: Strain].self, from: jsonMultipleDictionaries)
    let strains = strainDictionaries.map { Strain(id: $0.value.id, 
                                                  race: $0.value.race, 
                                                  flavors: $0.value.flavors, 
                                                  effects: $0.value.effects) }
    dump(strains)
    /*
          Strains example example
      ▿ 2 elements
        ▿ __lldb_expr_168.Strain
          - id: 2
          - race: "sativa"
          ▿ flavors: 3 elements
            - "Spicy/Herbal"
            - "Pungent"
            - "Earthy"
          ▿ effects: 3 key/value pairs
            ▿ (2 elements)
              - key: "positive"
              ▿ value: 5 elements
                - "Euphoric"
                - "Happy"
                - "Creative"
                - "Energetic"
                - "Talkative"
            ▿ (2 elements)
              - key: "medical"
              ▿ value: 6 elements
                - "Depression"
                - "Pain"
                - "Stress"
                - "Lack of Appetite"
                - "Nausea"
                - "Headache"
            ▿ (2 elements)
              - key: "negative"
              ▿ value: 1 element
                - "Dry Mouth"
        ▿ __lldb_expr_168.Strain
          - id: 1
          - race: "hybrid"
          ▿ flavors: 3 elements
            - "Earthy"
            - "Chemical"
            - "Pine"
          ▿ effects: 3 key/value pairs
            ▿ (2 elements)
              - key: "medical"
              ▿ value: 5 elements
                - "Depression"
                - "Insomnia"
                - "Pain"
                - "Stress"
                - "Lack of Appetite"
            ▿ (2 elements)
              - key: "negative"
              ▿ value: 1 element
                - "Dizzy"
            ▿ (2 elements)
              - key: "positive"
              ▿ value: 4 elements
                - "Relaxed"
                - "Hungry"
                - "Happy"
                - "Sleepy"
          */
  } catch {
    dump(error)
  }
}

4. Working with JSON where a value is heterogenous

In the JSON below postcode can be an Int or a String. In this case we make postcode a custom enum type and overreide init(from decoder:) to handle either case as the object is being decoded.

let jsonHeterogeneous = """
{
  "results": [{
      "gender": "male",
      "name": {
        "title": "Mr",
        "first": "Asher",
        "last": "King"
      },
      "location": {
        "street": {
          "number": 6848,
          "name": "Madras Street"
        },
        "city": "Whanganui",
        "state": "Manawatu-Wanganui",
        "country": "New Zealand",
        "postcode": 83251,
        "coordinates": {
          "latitude": "64.3366",
          "longitude": "-140.5100"
        },
        "timezone": {
          "offset": "0:00",
          "description": "Western Europe Time, London, Lisbon, Casablanca"
        }
      },
      "email": "asher.king@example.com",
      "login": {
        "uuid": "157a7e5d-6023-40bc-8b72-d391c5a20e73",
        "username": "lazycat871",
        "password": "punkin",
        "salt": "xchlaTXG",
        "md5": "1de85cd9e9fb19e4932fa2d94397f9f7",
        "sha1": "695dbe4886625b61d766320f4c759d920bd03c06",
        "sha256": "7df1eb54d38a127e7f181590f802c8ee5a6c887b141b5e90ee981eee719b9f71"
      },
      "dob": {
        "date": "1955-07-04T19:24:43.057Z",
        "age": 65
      },
      "registered": {
        "date": "2012-04-01T10:07:09.601Z",
        "age": 8
      },
      "phone": "(736)-108-4205",
      "cell": "(736)-836-4316",
      "id": {
        "name": "",
        "value": null
      },
      "picture": {
        "large": "https://randomuser.me/api/portraits/men/6.jpg",
        "medium": "https://randomuser.me/api/portraits/med/men/6.jpg",
        "thumbnail": "https://randomuser.me/api/portraits/thumb/men/6.jpg"
      },
      "nat": "NZ"
    },
    {
      "gender": "female",
      "name": {
        "title": "Ms",
        "first": "Madison",
        "last": "Williams"
      },
      "location": {
        "street": {
          "number": 64,
          "name": "Argyle St"
        },
        "city": "Kingston",
        "state": "Ontario",
        "country": "Canada",
        "postcode": "L7J 7K7",
        "coordinates": {
          "latitude": "-80.5612",
          "longitude": "2.7939"
        },
        "timezone": {
          "offset": "+4:00",
          "description": "Abu Dhabi, Muscat, Baku, Tbilisi"
        }
      },
      "email": "madison.williams@example.com",
      "login": {
        "uuid": "b5ad5a75-2a2c-4bfb-9028-a6ef95f85068",
        "username": "lazyostrich722",
        "password": "genesis1",
        "salt": "58Mw0Gvb",
        "md5": "af2a89591d1be11120ac0de395818eb7",
        "sha1": "63c2a6c7ddf7051a56c88ca767bc1579d88472fe",
        "sha256": "509e975c1f0ef8720b6990b6922e8c30a7f589e728b001bcd6092b4a6a55b234"
      },
      "dob": {
        "date": "1977-01-11T04:47:50.049Z",
        "age": 43
      },
      "registered": {
        "date": "2003-01-06T04:41:11.111Z",
        "age": 17
      },
      "phone": "326-431-8326",
      "cell": "042-933-3343",
      "id": {
        "name": "",
        "value": null
      },
      "picture": {
        "large": "https://randomuser.me/api/portraits/women/9.jpg",
        "medium": "https://randomuser.me/api/portraits/med/women/9.jpg",
        "thumbnail": "https://randomuser.me/api/portraits/thumb/women/9.jpg"
      },
      "nat": "CA"
    }
  ]
}
""".data(using: .utf8)!


struct PersonWrapper: Decodable {
  let results: [Person]
}

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

struct Location: Decodable {
  let city: String
  let state: String
  let country: String
  // heterogeneious property
  //let postcode: String // Int
  let postcode: PostCode
}

enum DecodingError: Error {
  case missingValue
}

enum PostCode: Decodable {
  case int(Int)
  case string(String)
  
  init(from decoder: Decoder) throws {
    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 DecodingError.missingValue
  }
}

example("Heterogeneous JSON") {
  do {
    let people = try JSONDecoder().decode(PersonWrapper.self, from: jsonHeterogeneous)
    dump(people)
    /*
     Heterogeneous JSON example
     ▿ __lldb_expr_140.PersonWrapper
       ▿ results: 2 elements
         ▿ __lldb_expr_140.Person
           - gender: "male"
           - email: "asher.king@example.com"
           ▿ location: __lldb_expr_140.Location
             - city: "Whanganui"
             - state: "Manawatu-Wanganui"
             - country: "New Zealand"
             ▿ postcode: __lldb_expr_140.PostCode.int
               - int: 83251
         ▿ __lldb_expr_140.Person
           - gender: "female"
           - email: "madison.williams@example.com"
           ▿ location: __lldb_expr_140.Location
             - city: "Kingston"
             - state: "Ontario"
             - country: "Canada"
             ▿ postcode: __lldb_expr_140.PostCode.string
               - string: "L7J 7K7"
    */
  } catch {
    dump(error)
    /*
     1).
     if postcode is defined as an Int
     debugDescription: "Expected to decode Int but found a string/data instead."
     2).
     if postcode is defined as a String
     debugDescription: "Expected to decode String but found a number instead."
    */
  }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment