0

I am loading a users notifications, there are different notification types (like, comment, follow) and I need to decode each based on their "type" property but I am new to taking this approach and have spent all day trying to figure out how to do this...

Right now I keep getting the decoding error in my NotificationsEnum and I think its because I don't know how to access the type property inside each object returned in my array.

I tried using this SO question & answer Decode JSON from the nested container and check its type dynamically for typecasting in swift but it is with classes and I am using structs.

How can I decode each of these objects based on their type property?? I feel like I need to figure out how to implement nestedUnkeyedContainer and nestedContainer somehow but I am having a hard time getting this to work.

Also, after getting the decoding to work, how can I put all of these objects in the same array? Would it be a generics since I would switch on their type to route the notifications to different notification views...??

THANK YOU

Different JSON activity I need to decode depending on type

[
 {
   _id: 61dc9bde82d0715,
   type: 'like',
   username: 'testuser1',
   likeId: 61dcb2d0712
 },
 { 
   _id: 61dc9bde82d0715,
   type: 'follow',
   username: 'testuser2',
   followId: 61dc9bd2d0712
 },
 {
  _id: 61dc9b2d0715,
   type: 'comment',
   username: 'testuser3',
   commentId: 61dc9bb2d0712
 }
]

PROFILE/PARENT DATA MODEL

struct ProfileData: Decodable {
    **otherProperties**
    var notifications: [NotificationsEnum] 
    // ^ Is this correct type? Was thinking I might need some kind of generic array??
}

// HOW CAN I IMPLEMENT nestedUnkeyedContainer and nestedContainer here???

enum NotificationsEnum: Decodable {
    enum DecodingError: Error {
        case wrongJSON
    }
  
    case like(LikeActivity)
    case comment(CommentActivity)
    case follow(FollowActivity)
    
    enum CodingKeys: String, CodingKey {
        case like = "like"
        case comment = "comment"
        case follow = "follow"
    }
    
   init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)

        switch container.allKeys.first {
        case .like:
            let value = try container.decode(LikeActivity.self, forKey: .like)
            self = .like(value)
        case .comment:
            let value = try container.decode(CommentActivity.self, forKey: .comment)
            self = .comment(value)
         case .follow:
            let value = try container.decode(FollowActivity.self, forKey: .follow)
            self = .follow(value)
        case .none:
             throw DecodingError.wrongJSON
        }
    }
}

MODELS

struct LikeActivity: Codable {
    let id: String
    let type: String
    let username: String
    let likeId: String
}

struct CommentActivity: Codable {
    let id: String
    let type: String
    let username: String
    let commentId: String
}

struct FollowActivity: Codable {
    let id: String
    let type: String
    let username: String
    let followId: String
}

Jake Smith
  • 580
  • 3
  • 11

1 Answers1

2

What you want to do is to first decode type then use a switch over the decoded value and init the corresponding type using the same decoder object.

You can also shorten the CodingKeys enum because of this since we are only using one key.

enum CodingKeys: String, CodingKey {
    case type
}

init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)

    let type = try container.decode(String.self, forKey: .type)
    switch type {
    case "like":
        self = try .like(LikeActivity(from: decoder))
    case "comment":
        self = try .comment(CommentActivity(from: decoder))
     case "follow":
        self = try .follow(FollowActivity(from: decoder))
    default:
         throw DecodingError.wrongJSON
    }
}

You could also create an enum for the type values

enum ActivityType: String {
    case like, comment, follow
}

Then the switch in the init(from:) becomes

switch ActivityType(rawValue: type) {
case .like:
    self = try .like(LikeActivity(from: decoder))
case .comment:
    self = try .comment(CommentActivity(from: decoder))
case .follow:
    self = try .follow(FollowActivity(from: decoder))
default:
     throw DecodingError.wrongJSON
}
Joakim Danielson
  • 43,251
  • 5
  • 22
  • 52