Nabeel
Nabeel

Reputation: 11

parsing json any type with Codable

I am sorry if this question is answered many times but am looking for my own specific json issue that's why am posting this, This is my json its an array list of notifications and in the extra object you can see it has contract object, Now this object is changing it can be campaign or feedback in the 2nd index of the json array how do I make Codable struct for this to decode this type of jsonHere is my struct, I cannot do Any type in Codable

struct Alert: Decodable { var id: Int? var isUnread: Bool? var userId: Int? var message: String? var notificationType: String? var extra: Any?

 "value": [
        {
            "id": 153,
            "is_unread": true,
            "user_id": 3,
            "message": "Contract offered from JohnWick. Click here to see the details.",
            "notification_type": "Contract",
            "extra": {
                "contract": {
                    "id": 477,
                    "likes": 0,
                    "shares": 0,
                    "account_reach": 0.0,
                    "followers": 0,
                    "impressions": 0.0,
                    "description": "Fsafasd",
                    "budget": 0.0,
                    "start_date": null,
                    "end_date": null,
                    "status": "pending",
                    "is_accepted": false,
                    "brand_id": 443,
                    "influencer_id": 3,
                    "proposal_id": 947,
                    "created_at": "2019-11-09T17:40:57.646Z",
                    "updated_at": "2019-11-09T17:40:57.646Z",
                    "contract_fee": 435345.0,
                    "base_fee": 5000.0,
                    "transaction_fee": 43534.5,
                    "total_fee": 483879.5,
                    "infuencer_completed": false,
                    "brand_completed": false,
                    "comments": 0
                }
            }
        },
{
            "id": 152,
            "is_unread": true,
            "user_id": 3,
            "message": "Message from JohnWick. Click here to check your inbox.",
            "notification_type": "Message",
            "extra": {
                "message": {
                    "id": 495,
                    "body": "Uuhvh",
                    "read": false,
                    "conversation_id": 42,
                    "user_id": 3,
                    "created_at": "2019-11-08T13:44:02.055Z",
                    "updated_at": "2019-11-08T13:44:02.055Z"
                }
            }
        },
    ]

As you can see it can be message, or campaign , or contract or feedback so how do I parse this or make model for this with CodingKeys Codable

Upvotes: 0

Views: 511

Answers (4)

vadian
vadian

Reputation: 285039

It's not Any type, you got four different but predictable types.

With reference to Rob's answer you can get rid of the decoding attempts in the if - else if chain if you decode notificationType as enum and decode the subtypes according to its value

enum NotificationType : String, Decodable {
    case contract = "Contract", message = "Message",  campaign = "Campaign", feedback = "Feedback"
}

enum Extra {
    case contract(Contract)
    case campaign(Campaign)
    case message(Message)
    case feedback(Feedback)
}

struct Alert: Decodable {
    let id: Int
    let isUnread: Bool
    let userId: Int
    let message: String
    let notificationType: NotificationType
    let extra: Extra
    
    private enum CodingKeys : String, CodingKey { case id, isUnread, userId, message, notificationType, extra}
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        id = try container.decode(Int.self, forKey: .id)
        isUnread = try container.decode(Bool.self, forKey: .isUnread)
        userId = try container.decode(Int.self, forKey: .userId)
        message = try container.decode(String.self, forKey: .message)
        notificationType = try container.decode(NotificationType.self, forKey: .notificationType)
        switch notificationType {
            case .contract:
                let contractData = try container.decode([String:Contract].self, forKey: .extra)
                extra = .contract(contractData[notificationType.rawValue.lowercased()]!)
            case .campaign:
                let campaignData = try container.decode([String:Campaign].self, forKey: .extra)
                extra = .campaign(campaignData[notificationType.rawValue.lowercased()]!)
            case .message:
                let messageData = try container.decode([String:Message].self, forKey: .extra)
                extra = .message(messageData[notificationType.rawValue.lowercased()]!)
            case .feedback:
                let feedbackData = try container.decode([String:Feedback].self, forKey: .extra)
                extra = .feedback(feedbackData[notificationType.rawValue.lowercased()]!)
        }
    }
}

Upvotes: 0

Sajjad Sarkoobi
Sajjad Sarkoobi

Reputation: 1064

in this article, you can find a way that support any type with the codable, so it will not matter if JSON returns String or Int:

anycodableValue

Upvotes: 1

Rob Napier
Rob Napier

Reputation: 299265

You don't mean Any here. As you said, extra can be "message, or campaign, or contract or feedback." It can't be a UIViewController or a CBPeripheral. It's not "anything." It's one of 4 things.

"One of X things" is an enum:

enum Extra {
    case contract(Contract)
    case campaign(Campaign)
    case message(Message)
    case feedback(Feedback)
}

To make it Decodable, we just need to look for the right key:

extension Extra: Decodable {
    enum CodingKeys: CodingKey {
        case contract, campaign, message, feedback
    }

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

        // Try to decode each thing it could be; throw if nothing matches.
        if let contract = try? container.decode(Contract.self, forKey: .contract) {
            self = .contract(contract)
        } else if let campaign = try? container.decode(Campaign.self, forKey: .campaign) {
            self = .campaign(campaign)
        } else if let message = try? container.decode(Message.self, forKey: .message) {
            self = .message(message)
        } else if let feedback = try? container.decode(Feedback.self, forKey: .feedback) {
            self = .feedback(feedback)
        } else {
            throw DecodingError.valueNotFound(Self.self,
                                              DecodingError.Context(codingPath: container.codingPath,
                                                                    debugDescription: "Could not find extra"))
        }
    }
}

Upvotes: 1

Andrew
Andrew

Reputation: 28539

A simple solution would be to make a struct called Extra that has four optional properties for each of the cases that you have.

struct Extra: Codable {
    let contract: Contract?
    let message: Message?
    let feedback: Feedback?
    let campaign: Campaign?
}

As long as each of message, campaign, contract, and feedback have fixed responses then you should be able to make structs for them that conform to Codable.

Upvotes: 1

Related Questions