如何在 CodingKeys 枚举(可编码协议)中使用通用值
How to use generic value in CodingKeys enum (Codable Protocol)
编译器抱怨:“枚举大小写的原始值必须是文字”
在 struct/class 中我们可以使用通用值,但是如何在 enum
中使用通用值。
下面是我想做一个大体结构的服务器响应
{
"status": true,
"message": "message",
"error" : "error if any"
"any key" : "Any kind of data"
}
在上面的示例中,“任意键”部分比较棘手。 “任意键”对于不同的服务调用会有所不同。
用户城市列表:
{
"status": true,
"message": "message",
"error" : ""
"cities" : "city list"
}
用户状态列表:
{
"status": true,
"message": "message",
"error" : ""
"states" : "state list"
}
用户帖子:
{
"status": true,
"message": "message",
"error" : ""
"posts" : "list of posts"
}
你可以看到每个服务调用都有相同的“状态”、“消息”和“错误”键,数据有不同的键“城市”、“州” 、“帖子”等
所以我想创建一个通用结构,将所有这些都包含在一个结构中。
我按照以下方式做了,但卡在了不同的键上。
struct Response<T>: Codable {
let message : String? // common in every service call
let status : Bool? // common in every service call
let errors: String? // common in every service call
let data : T? // it will be different for every call
enum CodingKeys: String, CodingKey {
case message = "message"
case status = "status"
case data = <key> //Here what to use?
case errors = "errors"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
message = try values.decodeIfPresent(String.self, forKey: .message)
status = try values.decodeIfPresent(Bool.self, forKey: .status)
errors = try values.decodeIfPresent(String.self, forKey: .errors)
data = try T(from: decoder)
}
}
这可能是我正在尝试做的事情吗?
如果是,如何实现?
任何帮助将不胜感激!!
我在尝试时遇到以下错误..
Raw value for enum case must be a literal
Non-nominal type 'T' does not support explicit initialization
更新
因为 enum
需要文字:
protocol ResponseData: Decodable {
static var attributeName: String { get }
}
struct Response<T>: Decodable where T: ResponseData {
let message : String
let status : Bool
let errors: String
let data: T
编码键只是 CodingKey
-class 认为可接受的字符串或整数的集合。
struct MyCodingKeys: CodingKey {
var stringValue: String
var intValue: Int?
init?(intValue: Int) {
return nil
}
init?(stringValue: String) {
if let _ = DefaultCodingKeys.init(rawValue: stringValue){
// OK
} else if stringValue == T.attributeName {
// OK
} else {
// NOT OK
return nil
}
self.stringValue = stringValue
self.intValue = nil
}
enum DefaultCodingKeys: String, CodingKey {
case message = "message"
case status = "status"
case errors = "errors"
}
必须手动创建这些,因为它是结构而不是枚举。
static var data: MyCodingKeys {
return MyCodingKeys(stringValue: T.attributeName)!
}
static var message: MyCodingKeys {
return MyCodingKeys(stringValue: "message")!
}
static var errors: MyCodingKeys {
return MyCodingKeys(stringValue: "errors")!
}
static var status: MyCodingKeys {
return MyCodingKeys(stringValue: "status")!
}
}
必要,因为即使 MyCodingKeys
被命名为 CodingKeys
,Swift
也不会使用它,因为 Swift
需要 enums
。
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: MyCodingKeys.self)
message = try values.decode(String.self, forKey: .message)
status = try values.decode(Bool.self, forKey: .status)
errors = try values.decode(String.self, forKey: .errors)
data = try values.decode(T.self, forKey: .data)
}
}
一个选项是(实际上不起作用,请参阅下面的评论):
struct Response<T>: Codable {
let message : String? // if these exist in every call do they have to be optional?
let status : Bool?
let errors: String?
let data: T
}
struct Users: Codable {
let users: [User]
}
struct Cities: Codable {
let cities: [City]
}
let response = JSONDecoder().decode(Response<Users>.self, from: data)
几乎相同但没有泛型:
class Response: Codable {
let message : String? // if these exist in every call do they have to be optional?
let status : Bool?
let errors: String?
}
class UserResponse: Response, Codable {
let users: [User]
}
class CitiesResponse: Response, Codable {
let cities: [City]
}
你的主要问题是 Codable 需要知道解码和映射到什么类型,所以你要么需要使用泛型(Response<Users>
)指定类型,要么使用继承来制作特定类型。
更新:
通用选项不起作用,因为 Codable 仍然需要知道密钥,所以您最终需要做大量的密钥检查并知道什么密钥与什么类型匹配,这使它变得毫无意义。
我确实设法解码了一个示例用户,它应该可重复用于城市和其他类型,但它似乎也有点冗长。
import PlaygroundSupport
let userJsonData = """
{
"status": true,
"message": "message",
"error" : "error if any",
"users" : [{
"id": 1,
"name": "John"
}, {
"id": 2,
"name": "Steve"
}]
}
""".data(using: .utf8)!
class Response: Codable {
let error: String
let status: Bool
let message: String
}
struct User: Codable {
let id: Int
let name: String
}
class Users: Response {
var users: [User]
enum CodingKeys: String, CodingKey {
case users
}
required init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
users = try values.decode([User].self, forKey: .users)
try super.init(from: decoder)
}
}
do {
let response = try JSONDecoder().decode(Users.self, from: userJsonData)
print(response.users)
} catch {
print(error)
}
输出:
[__lldb_expr_22.User(id: 1, name: "John"), __lldb_expr_22.User(id: 2, name: "Steve")]
编译器抱怨:“枚举大小写的原始值必须是文字”
在 struct/class 中我们可以使用通用值,但是如何在 enum
中使用通用值。
下面是我想做一个大体结构的服务器响应
{
"status": true,
"message": "message",
"error" : "error if any"
"any key" : "Any kind of data"
}
在上面的示例中,“任意键”部分比较棘手。 “任意键”对于不同的服务调用会有所不同。
用户城市列表:
{
"status": true,
"message": "message",
"error" : ""
"cities" : "city list"
}
用户状态列表:
{
"status": true,
"message": "message",
"error" : ""
"states" : "state list"
}
用户帖子:
{
"status": true,
"message": "message",
"error" : ""
"posts" : "list of posts"
}
你可以看到每个服务调用都有相同的“状态”、“消息”和“错误”键,数据有不同的键“城市”、“州” 、“帖子”等
所以我想创建一个通用结构,将所有这些都包含在一个结构中。
我按照以下方式做了,但卡在了不同的键上。
struct Response<T>: Codable {
let message : String? // common in every service call
let status : Bool? // common in every service call
let errors: String? // common in every service call
let data : T? // it will be different for every call
enum CodingKeys: String, CodingKey {
case message = "message"
case status = "status"
case data = <key> //Here what to use?
case errors = "errors"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
message = try values.decodeIfPresent(String.self, forKey: .message)
status = try values.decodeIfPresent(Bool.self, forKey: .status)
errors = try values.decodeIfPresent(String.self, forKey: .errors)
data = try T(from: decoder)
}
}
这可能是我正在尝试做的事情吗?
如果是,如何实现?
任何帮助将不胜感激!!
我在尝试时遇到以下错误..
Raw value for enum case must be a literal
Non-nominal type 'T' does not support explicit initialization
更新
因为 enum
需要文字:
protocol ResponseData: Decodable {
static var attributeName: String { get }
}
struct Response<T>: Decodable where T: ResponseData {
let message : String
let status : Bool
let errors: String
let data: T
编码键只是 CodingKey
-class 认为可接受的字符串或整数的集合。
struct MyCodingKeys: CodingKey {
var stringValue: String
var intValue: Int?
init?(intValue: Int) {
return nil
}
init?(stringValue: String) {
if let _ = DefaultCodingKeys.init(rawValue: stringValue){
// OK
} else if stringValue == T.attributeName {
// OK
} else {
// NOT OK
return nil
}
self.stringValue = stringValue
self.intValue = nil
}
enum DefaultCodingKeys: String, CodingKey {
case message = "message"
case status = "status"
case errors = "errors"
}
必须手动创建这些,因为它是结构而不是枚举。
static var data: MyCodingKeys {
return MyCodingKeys(stringValue: T.attributeName)!
}
static var message: MyCodingKeys {
return MyCodingKeys(stringValue: "message")!
}
static var errors: MyCodingKeys {
return MyCodingKeys(stringValue: "errors")!
}
static var status: MyCodingKeys {
return MyCodingKeys(stringValue: "status")!
}
}
必要,因为即使 MyCodingKeys
被命名为 CodingKeys
,Swift
也不会使用它,因为 Swift
需要 enums
。
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: MyCodingKeys.self)
message = try values.decode(String.self, forKey: .message)
status = try values.decode(Bool.self, forKey: .status)
errors = try values.decode(String.self, forKey: .errors)
data = try values.decode(T.self, forKey: .data)
}
}
一个选项是(实际上不起作用,请参阅下面的评论):
struct Response<T>: Codable {
let message : String? // if these exist in every call do they have to be optional?
let status : Bool?
let errors: String?
let data: T
}
struct Users: Codable {
let users: [User]
}
struct Cities: Codable {
let cities: [City]
}
let response = JSONDecoder().decode(Response<Users>.self, from: data)
几乎相同但没有泛型:
class Response: Codable {
let message : String? // if these exist in every call do they have to be optional?
let status : Bool?
let errors: String?
}
class UserResponse: Response, Codable {
let users: [User]
}
class CitiesResponse: Response, Codable {
let cities: [City]
}
你的主要问题是 Codable 需要知道解码和映射到什么类型,所以你要么需要使用泛型(Response<Users>
)指定类型,要么使用继承来制作特定类型。
更新:
通用选项不起作用,因为 Codable 仍然需要知道密钥,所以您最终需要做大量的密钥检查并知道什么密钥与什么类型匹配,这使它变得毫无意义。
我确实设法解码了一个示例用户,它应该可重复用于城市和其他类型,但它似乎也有点冗长。
import PlaygroundSupport
let userJsonData = """
{
"status": true,
"message": "message",
"error" : "error if any",
"users" : [{
"id": 1,
"name": "John"
}, {
"id": 2,
"name": "Steve"
}]
}
""".data(using: .utf8)!
class Response: Codable {
let error: String
let status: Bool
let message: String
}
struct User: Codable {
let id: Int
let name: String
}
class Users: Response {
var users: [User]
enum CodingKeys: String, CodingKey {
case users
}
required init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
users = try values.decode([User].self, forKey: .users)
try super.init(from: decoder)
}
}
do {
let response = try JSONDecoder().decode(Users.self, from: userJsonData)
print(response.users)
} catch {
print(error)
}
输出:
[__lldb_expr_22.User(id: 1, name: "John"), __lldb_expr_22.User(id: 2, name: "Steve")]