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"
*/
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)
}
}
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": "[email protected]",
"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": "[email protected]",
"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: "[email protected]"
▿ 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: "[email protected]"
▿ 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."
*/
}
}