创建动态可编码对象

问题描述 投票:0回答:1

我正在创建具有动态内容的消息对象。我设法使其工作,但我的解决方案似乎容易出错,并且有很多样板。

进行更改,例如:添加新的动态内容类型或添加删除字段似乎需要很多步骤。

想知道我的问题是否有更好的解决方案?

我的消息可能看起来像:

{
  "sentAt" : 1587022227,
  "canBeDeletedForAllUsers" : true,
  "content" : {
    "memberUserIDs" : [
      "7835BB24-2880-49E0-AEB1-8FEE74FE6569",
      "478D6BDD-4921-4166-8ED2-9FB6194450E6",
      "8D0B0684-EDE9-47C8-8318-D96BE91E0879"
    ]
  },
  "id" : "9D4A2D5E-F316-43D7-880E-D6793A11F9C8",
  "senderUserID" : "71F1CDB9-D4D0-4C99-8B87-8E09A06259C8",
  "channelID" : "group-241B1EED-3704-452D-8D9F-24F75E2AB76E",
  "canBeDeletedOnlyForSelf" : true,
  "canBeForwarded" : true,
  "views" : 0,
  "type" : "addMembers",
  "canBeEdited" : true
}

OR

{
  "sentAt" : 1587023808,
  "canBeDeletedForAllUsers" : true,
  "content" : {
    "text" : "hello"
  },
  "id" : "3DA4810C-C6FF-456E-8431-A61674BF6967",
  "senderUserID" : "30FD4396-CDD8-47BD-AD26-896F6F33AC9F",
  "channelID" : "group-31EA8B8B-B39D-4B49-9D8C-F66D3E11A8F5",
  "canBeDeletedOnlyForSelf" : true,
  "canBeForwarded" : true,
  "views" : 0,
  "type" : "text",
  "canBeEdited" : true
}

并且根据我要添加的内容可能会有更多差异。

这是我的解决方案:

public enum MessageContentType: String, Codable {
    case addMembers
    case text
}

public protocol MessageContent {}

public struct Message: Encodable {
    let id: String
    let senderUserID: String
    let channelID: String
    let canBeEdited: Bool
    let canBeForwarded: Bool
    let canBeDeletedOnlyForSelf: Bool
    let canBeDeletedForAllUsers: Bool
    let views: Int
    let content: MessageContent
    var sentAt: Int64
    var type: MessageContentType

    enum CodingKeys: CodingKey {
        case id
        case senderUserID
        case channelID
        case canBeEdited
        case canBeForwarded
        case canBeDeletedOnlyForSelf
        case canBeDeletedForAllUsers
        case views
        case content
        case sentAt
        case type
    }

    public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)

        try container.encode(id, forKey: .id)
        try container.encode(senderUserID, forKey: .senderUserID)
        try container.encode(channelID, forKey: .channelID)
        try container.encode(canBeEdited, forKey: .canBeEdited)
        try container.encode(canBeForwarded, forKey: .canBeForwarded)
        try container.encode(canBeDeletedOnlyForSelf, forKey: .canBeDeletedOnlyForSelf)
        try container.encode(canBeDeletedForAllUsers, forKey: .canBeDeletedForAllUsers)
        try container.encode(views, forKey: .views)
        try container.encode(sentAt, forKey: .sentAt)
        try container.encode(type, forKey: .type)

        if let content = content as? MessageChatAddMembers {
            try container.encode(content, forKey: .content)
        } else if let content = content as? MessageText {
            try container.encode(content, forKey: .content)
        } else {
            let context = DecodingError.Context(codingPath: encoder.codingPath, debugDescription: "Invalid content!")
            throw DecodingError.dataCorrupted(context)
        }
    }
}

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

        id = try container.decode(String.self, forKey: .id)
        senderUserID = try container.decode(String.self, forKey: .senderUserID)
        channelID = try container.decode(String.self, forKey: .channelID)
        canBeEdited = try container.decode(Bool.self, forKey: .canBeEdited)
        canBeForwarded = try container.decode(Bool.self, forKey: .canBeForwarded)
        canBeDeletedOnlyForSelf = try container.decode(Bool.self, forKey: .canBeDeletedOnlyForSelf)
        canBeDeletedForAllUsers = try container.decode(Bool.self, forKey: .canBeDeletedForAllUsers)
        views = try container.decode(Int.self, forKey: .views)
        sentAt = try container.decode(Int64.self, forKey: .sentAt)
        type = try container.decode(MessageContentType.self, forKey: .type)

        switch type {
            case .addMembers:
                content = try container.decode(MessageChatAddMembers.self, forKey: .content)

            case .text:
                content = try container.decode(MessageText.self, forKey: .content)
        }
    }
}

public struct MessageChatAddMembers: Codable, MessageContent {
    let memberUserIDs: [String]

    init() {
        memberUserIDs = [UUID().uuidString, UUID().uuidString, UUID().uuidString]
    }
}

public struct MessageText: Codable, MessageContent {
    let text: String

    init() {
        text = "hello"
    }
}
json swift codable
1个回答
0
投票

如果将type字段移至content对象,则可以简单地为content数据使用枚举。

{
  "sentAt" : 1587023808,
  "canBeDeletedForAllUsers" : true,
  "content" : {
    "type" : "text",
    "text" : "hello"
  },
  "id" : "3DA4810C-C6FF-456E-8431-A61674BF6967",
  "senderUserID" : "30FD4396-CDD8-47BD-AD26-896F6F33AC9F",
  "channelID" : "group-31EA8B8B-B39D-4B49-9D8C-F66D3E11A8F5",
  "canBeDeletedOnlyForSelf" : true,
  "canBeForwarded" : true,
  "views" : 0,
  "canBeEdited" : true
}

仍然有些工作,但是您的MessageContent看起来会像这样:

struct MessageContent: Codable {

    enum CodingKeys: String, CodingKey {
        case type
        case text
        case memberUserIDs
    }

    enum Data {
        case text(String)
        case memberUserIDs([String])
        case unknown
    }

    let data: Data

    init(data: Data) {
        self.data = data
    }

    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 "text":
            let text = try container.decode(String.self, forKey: .text)
            self.data = .text(text)
        case "memberUserIDs":
            let memberUserIDs = try container.decode([String].self, forKey: .memberUserIDs)
            self.data = .memberUserIDs(memberUserIDs)
        default:
            self.data = .unknown
        }
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        switch data {
        case .text(let text):
            try container.encode(text, forKey: .text)
            try container.encode("text", forKey: .type)
        case .memberUserIDs(let memberUserIDs):
            try container.encode(memberUserIDs, forKey: .memberUserIDs)
            try container.encode("memberUserIDs", forKey: .type)
        case .unknown:
            break
        }
    }
}

假设JSON中的字段与Message属性具有相同的名称,则可以摆脱CodingKeysCodable协议方法。

public struct Message: Codable {
    let id: String
    let senderUserID: String
    let channelID: String
    let canBeEdited: Bool
    let canBeForwarded: Bool
    let canBeDeletedOnlyForSelf: Bool
    let canBeDeletedForAllUsers: Bool
    let views: Int
    let content: MessageContent
    var sentAt: Int64
}
© www.soinside.com 2019 - 2024. All rights reserved.