我正在创建具有动态内容的消息对象。我设法使其工作,但我的解决方案似乎容易出错,并且有很多样板。
进行更改,例如:添加新的动态内容类型或添加删除字段似乎需要很多步骤。
想知道我的问题是否有更好的解决方案?
我的消息可能看起来像:
{
"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"
}
}
如果将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
属性具有相同的名称,则可以摆脱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
}