Создание динамического c кодируемого объекта - PullRequest
0 голосов
/ 16 апреля 2020

Я создаю объект сообщения с динамическим содержимым c. Мне удалось заставить это работать, но мое решение кажется склонным к ошибкам и имеет много шаблонного.

Внесение изменений, например: добавление нового динамического c типа контента или добавление удаления полей, по-видимому, требует много шагов.

Интересно, есть ли лучшее решение для моей проблемы?

Мое сообщение может выглядеть следующим образом:

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

ИЛИ

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

1 Ответ

0 голосов
/ 16 апреля 2020

Если вы переместите поле 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
}

Это все еще немного, но ваша * Тогда 1007 * будет выглядеть примерно так:

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, вы можете избавиться от CodingKeys и протокола Codable методы.

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