Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

How to decode a JSON Dictionary?

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:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

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"
}

>Solution :

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 iso8601WithSeconds: DateFormatter = {
      let formatter = DateFormatter()
      formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
      formatter.calendar = Calendar(identifier: .iso8601)
      formatter.timeZone = TimeZone(secondsFromGMT: 0)
      formatter.locale = Locale(identifier: "en_US_POSIX")
      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.iso8601WithSeconds)
let group = try! decoder.decode(Group.self, from: data)

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading