Search code examples
iosswiftswift5codablejsonencoder

How to use Codable encode with object inside object


Im working on a JSON payload with my object payload. but im having trouble encoding object inside object.

My Payload class was this

class ConversationPayload :BaseObject {
    var title : String? = ""
    var messageDict: MessagePayload = MessagePayload()
    var participants: [Int32] = []
    var type: String = ""
    
    enum CodingKeys: String, CodingKey {
        case title = "title"
        case messageDict = "message"
        case participants = "participants"
        case type = "type"
    }

    override func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        if title != nil {
            try container.encode(title, forKey: .title)
        }
        try container.encode(messageDict, forKey: .messageDict)
        try container.encode(participants, forKey: .participants)
        try container.encode(type, forKey: .type)
    }
    
}

class MessagePayload: BaseObject {
    var body : String = ""
    var isVideocallInvite: Bool = false
    var attachmentsPayload: MessageAttachmentPayload? = nil
    
    enum CodingKeys: String, CodingKey {
        case body = "body"
        case isVideocallInvite = "is_videocall_invite"
        case attachmentsPayload = "attachment"
    }

    override func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(body, forKey: .body)
        try container.encode(isVideocallInvite, forKey: .isVideocallInvite)
        if attachmentsPayload != nil {
            try container.encode(attachmentsPayload, forKey: .attachmentsPayload)
        }
        
    }
}

class MessageAttachmentPayload: BaseObject {
    var photo : String = ""
    var photoType : String = "jpg"
}

BaseObject was this

class BaseObject:Codable{}

What i want to get in json payload was something like this

{"message": {"body": "body_string", "is_videocall_invite": 1}, "participants" : [user-id], "type" : "converstation_type","title":"title"}

anyone know whats wrong with my payload class? not that familiar yet on codable. thanks in advance.


Solution

  • I'm not sure what constrains you have here, but I would simplify all of this down. Keep the JSON data models as close to the JSON as you can get.

    struct ConversationJsonModel: Codable {
        var title: String?
        var message: MessageJsonModel
        var participants: [Int]
        var type: String
    }
    
    struct MessageJsonModel: Codable {
        var body: String
        var is_videocall_invite: Int
        var attachment: AttachmentJsonModel?
    }
    
    struct AttachmentJsonModel: Codable {
        var photo: String
        var photo_type: String // <-- assuming photo_type is the JSON member name.
    }
    

    If you need a view model or some other kind of local data model, then the two parts are separate.

    class BaseObject {}
    
    class ConversationPayload: BaseObject {
        var title : String? = ""
        var messageDict: MessagePayload = MessagePayload()
        var participants: [Int32] = []
        var type: String = ""
    
        func makeConversationJsonModel() -> ConversationJsonModel {
            ConversationJsonModel(title: title,
                                  message: messageDict.makeMessageJsonModel(),
                                  participants: participants.map { Int($0) },
                                  type: type)
        }
    }
    
    class MessagePayload: BaseObject {
        var body : String = ""
        var isVideocallInvite: Bool = false
        var attachmentsPayload: MessageAttachmentPayload? = nil
    
        func makeMessageJsonModel() -> MessageJsonModel {
            MessageJsonModel(body: body,
                             is_videocall_invite: isVideocallInvite ? 1 : 0,
                             attachment: attachmentsPayload?.makeAttachmentJsonModel())
        }
    }
    
    class MessageAttachmentPayload: BaseObject {
        var photo : String = ""
        var photoType : String = "jpg"
    
        func makeAttachmentJsonModel() -> AttachmentJsonModel {
            AttachmentJsonModel(photo: photo, photo_type: photoType)
        }
    }
    

    Finally, encoding your JSON

    let conversationPayload = ConversationPayload()
    let json = try? JSONEncoder().encode(conversationPayload.makeConversationJsonModel())
    

    This allows for clean separation between the JSON representation and the payload model. For example, in the JSON, is_videocall_invite is an Int (0 or 1); meanwhile, in the payload model, isVideocallInvite is a Bool.