在 Swift 中创建自定义 CodingKey 对象
Creating custom CodingKey Object in Swift
我正在尝试对我的 Codable
对象进行一些定制。我的 JSON 对象使用多种类型的标记,所以我想让它们类型安全。为此,我创建了以下 Codable classes:
class Token: Codable {
let value: String
init(_ value: String = "") {
self.value = value
}
required init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
value = try container.decode(String.self)
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(value)
}
}
extension Token: Equatable { }
extension Token: Hashable { }
class UserToken: Token { }
class ProductToken: Token { }
// etc...
struct User: Codable {
let token: UserToken
let friends: [UserToken : User]
// ...
}
JSON 对象:
// User
{
"token":"12345",
...
}
这很好用,除了这些标记用作字典中的键的情况,如下所示:
// User
{
"token":"12345",
"friends":{
"56789":{ // User
"token":"56789",
...
},
"09876":{ // User
"token":"09876",
...
}
}
}
为了让它工作,我更新了我的 Token
class 以符合 CodingKey
(似乎是正确的做法):
class Token: Codable, CodingKey {
var stringValue: String {
return value
}
var intValue: Int? {
return Int(value)
}
required init?(stringValue: String) {
value = stringValue
}
required init?(intValue: Int) {
value = "\(intValue)"
}
// Plus above implementation
}
这似乎不能正常工作,失败并出现以下错误。看起来 JSONDecoder 认为它应该解码数组而不是字典...这是 Codable 中的错误吗?
typeMismatch(Swift.Array<Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Array<Any> but found a dictionary instead.", underlyingError: nil))
我最接近干净的东西如下:
首先扩展KeyedDecodingContainer
(Token
符合CodingKey
):
extension KeyedDecodingContainer {
func decodeTokenContainer<TokenKey, Value>(keyedBy tokenKeyType: TokenKey.Type,
valueType: Value.Type,
forKey key: KeyedDecodingContainer<K>.Key) throws -> [TokenKey : Value] where TokenKey: Token, Value: Decodable {
let tempDict = try nestedContainer(keyedBy: tokenKeyType, forKey: key)
var tokenDictionary = [TokenKey : Value]()
for key in tempDict.allKeys {
let value = try tempDict.decodeIfPresent(Value.self, forKey: key)
tokenDictionary[key] = value
}
return tokenDictionary
}
}
然后,您需要重写包含 class:
的 decode/encode 方法
struct User {
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
...
friends = container.decodeTokenContainer(keyedBy: UserToken.self,
valueType: User.self,
forKey: .friends)
}
}
如果有人有解决方案,我不需要在 User
对象上执行此操作,那就太好了。我有很多具有很多属性的对象,我必须手动实现 encode/decode 方法。
这有效。
struct User: Codable {
let token: Token
var friendsList: [Friend] {
get { return friends.friends }
set { friends.friends = newValue }
}
private var friends: FriendsList
private struct FriendsList: Codable {
var friends: [Friend]
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let dictionary = try container.decode([String: Friend].self)
var friends = [Friend]()
_ = dictionary.map { (_, value: Friend) in
friends.append(value)
}
self.friends = friends
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
var dectionary = [String: Friend]()
_ = friends.map { friend in
dectionary[friend.token.value] = friend
}
try container.encode(dectionary)
}
}
}
struct Friend: Codable {
let token: Token
}
class Token: Codable {
let value: String
init(_ value: String = "") {
self.value = value
}
required init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
value = try container.decode(String.self)
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(value)
}
}
我正在尝试对我的 Codable
对象进行一些定制。我的 JSON 对象使用多种类型的标记,所以我想让它们类型安全。为此,我创建了以下 Codable classes:
class Token: Codable {
let value: String
init(_ value: String = "") {
self.value = value
}
required init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
value = try container.decode(String.self)
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(value)
}
}
extension Token: Equatable { }
extension Token: Hashable { }
class UserToken: Token { }
class ProductToken: Token { }
// etc...
struct User: Codable {
let token: UserToken
let friends: [UserToken : User]
// ...
}
JSON 对象:
// User
{
"token":"12345",
...
}
这很好用,除了这些标记用作字典中的键的情况,如下所示:
// User
{
"token":"12345",
"friends":{
"56789":{ // User
"token":"56789",
...
},
"09876":{ // User
"token":"09876",
...
}
}
}
为了让它工作,我更新了我的 Token
class 以符合 CodingKey
(似乎是正确的做法):
class Token: Codable, CodingKey {
var stringValue: String {
return value
}
var intValue: Int? {
return Int(value)
}
required init?(stringValue: String) {
value = stringValue
}
required init?(intValue: Int) {
value = "\(intValue)"
}
// Plus above implementation
}
这似乎不能正常工作,失败并出现以下错误。看起来 JSONDecoder 认为它应该解码数组而不是字典...这是 Codable 中的错误吗?
typeMismatch(Swift.Array<Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Array<Any> but found a dictionary instead.", underlyingError: nil))
我最接近干净的东西如下:
首先扩展KeyedDecodingContainer
(Token
符合CodingKey
):
extension KeyedDecodingContainer {
func decodeTokenContainer<TokenKey, Value>(keyedBy tokenKeyType: TokenKey.Type,
valueType: Value.Type,
forKey key: KeyedDecodingContainer<K>.Key) throws -> [TokenKey : Value] where TokenKey: Token, Value: Decodable {
let tempDict = try nestedContainer(keyedBy: tokenKeyType, forKey: key)
var tokenDictionary = [TokenKey : Value]()
for key in tempDict.allKeys {
let value = try tempDict.decodeIfPresent(Value.self, forKey: key)
tokenDictionary[key] = value
}
return tokenDictionary
}
}
然后,您需要重写包含 class:
的 decode/encode 方法struct User {
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
...
friends = container.decodeTokenContainer(keyedBy: UserToken.self,
valueType: User.self,
forKey: .friends)
}
}
如果有人有解决方案,我不需要在 User
对象上执行此操作,那就太好了。我有很多具有很多属性的对象,我必须手动实现 encode/decode 方法。
这有效。
struct User: Codable {
let token: Token
var friendsList: [Friend] {
get { return friends.friends }
set { friends.friends = newValue }
}
private var friends: FriendsList
private struct FriendsList: Codable {
var friends: [Friend]
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let dictionary = try container.decode([String: Friend].self)
var friends = [Friend]()
_ = dictionary.map { (_, value: Friend) in
friends.append(value)
}
self.friends = friends
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
var dectionary = [String: Friend]()
_ = friends.map { friend in
dectionary[friend.token.value] = friend
}
try container.encode(dectionary)
}
}
}
struct Friend: Codable {
let token: Token
}
class Token: Codable {
let value: String
init(_ value: String = "") {
self.value = value
}
required init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
value = try container.decode(String.self)
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(value)
}
}