在 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))

我最接近干净的东西如下:

首先扩展KeyedDecodingContainerToken符合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)
    }
}