I am able to decode straight forward json responses but as the JSON gets more nested, I'm struggling.
To decode JSON that looks like this:
[
{
"id": "string",
"username": "string",
"firstName": "string",
"lastName": "string",
"fullName": "string",
"email": "string",
"isInActivity": true,
"activeEventId": "string",
"initials": "string"
}
]
My struct is:
struct FriendsStruct: Decodable, Hashable {
var initials: String
var username: String
var firstName: String
var lastName: String
var fullName: String
var email: String
var isInActivity: Bool
var activeEventId: String
var id: String
}
And to decode:
func getFriends(token: String, force: Bool) async throws -> Int {
var request = EndPoints().getFriendsEndPoint(force: force)
request.httpMethod = "GET"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
let (data, response) = try await URLSession.shared.data(for: request)
let httpResponse = response as? HTTPURLResponse
guard (response as? HTTPURLResponse)?.statusCode == 200 else {return httpResponse?.statusCode ?? 0}
let decodedFriends = try JSONDecoder().decode([FriendsStruct].self, from: data)
self.updateCoreDataFriendsRecords(friendsData: decodedFriends)
return httpResponse?.statusCode ?? 1000
}
Can someone advise as to how I might decode with the same approach but with a nested response such as:
{
"members": [
{
"memberId": "string",
"memberName": "string",
"memberUsername": "string",
"memberType": 0,
"isMyFriend": true,
"initials": "string"
}
],
"name": "string",
"owner": "string",
"description": "string",
"groupType": 0,
"expiryDate": "2023-02-06T20:00:03.834Z",
"readOnly": true,
"isDeleted": true,
"approvalRequired": true,
"joinWithCode": true,
"numberOfMembers": 0,
"groupAssociation": 0,
"id": "string",
"etag": "string"
}
You need two structs: a Member
struct with properties in the JSON (as you did for the simple example) and an outer level struct for the base data that includes a property which is an array of Member
. For example:
struct Member: Decodable {
let memberId: String
let memberName: String
let memberUsername: String
let memberType: Int
let isMyFriend: Bool
let initials: String
}
struct Group: Decodable {
let members: [Member]
let name: String
let owner: String
let description: String
let groupType: Int
let expiryDate: String
let readOnly: Bool
let isDeleted: Bool
let approvalRequired: Bool
let joinWithCode: Bool
let numberOfMembers: Int
let groupAssociation: Int
let id: String
let etag: String
}
This is treating the data exactly as it is in the JSON. You'd probably want to go further and use a CodingKeys enum to map some of the json fields onto more suitable property names, and maybe, depending on needs, use a Date
for the expiry date and decode the date string.
EDIT
As a follow up I mentioned the next step might be decoding the expiryDate
field into a Date
property. The answer passed over this as it appeared to be an .iso8601
date format, in which case all that is required is to set the date decoding strategy on the decoder accordingly:
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(DateFormatter.iso8601)
let group = decoder.decode(Group.self, from: json.data(using: .utf8)!) // force unwrapping for brevity. Don't ;-)
However this won't work as the date field contains fractional seconds and Swift's decoder only supports whole seconds. This makes it a bit more interesting :-) as you'll need to define a custom decoder:
extension DateFormatter {
static let iso8601WithFractionalSeconds: DateFormatter = {
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
formatter.calendar = Calendar(identifier: .iso8601)
formatter.timeZone = TimeZone(secondsFromGMT: 0)
return formatter
}()
}
This then lets you decoder the expiryDate
string to a Date
within the decoder. Change the expiry date field to a Date
and decode as below.
struct Group: Decodable {
//...
let expiryDate: Date
//...
}
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(DateFormatter.iso8601WithFractionalSeconds)
let group = try! decoder.decode(Group.self, from: data)