1. Working with JSON where the root level is a dictionary
import Foundation
// Parsing Dictionary
//=====================================================
// JSON Data
//=====================================================
let json = """
{
"results": [
{
"firstName": "John",
"lastName": "Appleseed"
},
{
"firstName": "Alex",
"lastName": "Paul"
}
]
}
""".data(using: .utf8)!
//=====================================================
// Create Model(s)
//=====================================================
// Codable: Decodable & Encodable
// Decodable: converts json data
// Encodale: converts to json data to e.g POST to a Web API
// Top Level JSON is a Dictionary
struct ResultsWrapper: Decodable {
let results: [Contact]
}
struct Contact: Decodable {
let firstName: String
let lastName: String
}
//=====================================================
// decode the JSON data to our Swift model
//=====================================================
do {
let dictionary = try JSONDecoder().decode(ResultsWrapper.self, from: json)
let contacts = dictionary.results // [Contact]
dump(contacts)
} catch {
print("decoding error: \(error)")
}
/*
▿ 2 elements
▿ __lldb_expr_1.Contact
- firstName: "John"
- lastName: "Appleseed"
▿ __lldb_expr_1.Contact
- firstName: "Alex"
- lastName: "Paul"
*/
2. Working with JSON where the root level is an array
import Foundation
//===================================================
// JSON data
//===================================================
let json = """
[
{
"title": "New York",
"location_type": "City",
"woeid": 2459115,
"latt_long": "40.71455,-74.007118"
}
]
""".data(using: .utf8)!
//===================================================
// Create model(s)
//===================================================
struct City: Decodable {
let title: String
let locationType: String
// reminder - once property names are changed
// using CodingKeys, they must match identically to
// the case type
//let location_type: String
let coordinate: String
let woeid: Int
//let latt_long: String
private enum CodingKeys: String, CodingKey {
case title
case locationType = "location_type"
case woeid
case coordinate = "latt_long"
}
}
//===================================================
// decode JSON to Swift objects
//===================================================
do {
let weatherArray = try JSONDecoder().decode([City].self, from: json)
dump(weatherArray)
} catch {
print("decoding error: \(error)")
}
3. Working with JSON where the root level is made up of multiple dictionary objects with **(multiple keys)
import Foundation
// rare occasions you may come across some JSON like the structure below, (multiple dictionary objects):
//===================================================
// JSON data
//===================================================
let json = """
{
"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"
]
}
},
"Afternoon Delight": {
"id": 3,
"race": "hybrid",
"flavors": [
"Pepper",
"Flowery",
"Pine"
],
"effects": {
"positive": [
"Relaxed",
"Hungry",
"Euphoric",
"Uplifted",
"Tingly"
],
"negative": [
"Dizzy",
"Dry Mouth",
"Paranoid"
],
"medical": [
"Depression",
"Insomnia",
"Pain",
"Stress",
"Cramps",
"Headache"
]
}
}
}
""".data(using: .utf8)!
//===================================================
// Swift Model(s)
//===================================================
struct Strain: Decodable {
let id: Int
let race: String
let flavors: [String]
let effects: [String: [String]]
}
//===================================================
// decode JSON to Swift objects
//===================================================
do {
let dictionary = try JSONDecoder().decode([String: Strain].self, from: json)
// use a for-loop to create [Strain] or use map {}
var strains = [Strain]()
for (_, value) in dictionary {
let strain = Strain(id: value.id,
race: value.race,
flavors: value.flavors,
effects: value.effects)
strains.append(strain)
}
dump(strains)
} catch {
print(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 override init(from decoder:)
to handle either case as the object is being decoded.
import Foundation
//===================================================
// JSON data
//===================================================
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)!
//===================================================
// Swift Model(s)
//===================================================
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
}
}
//===================================================
// decode the JSON to Swift objects
//===================================================
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."
*/
}
More on Github