Создание собственного объекта CodingKey в Swift - PullRequest
0 голосов
/ 04 июня 2019

Я пытаюсь сделать что-то нестандартное с моими Codable объектами.Мои объекты JSON используют несколько типов токенов, поэтому я хотел бы сделать их безопасными.Для этого я создал следующие классы Codable:

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, чтобы он соответствовал 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))

1 Ответ

0 голосов
/ 05 июня 2019

Самое близкое, что я могу получить к чему-то чистому, это:

Сначала добавьте 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
    }
}

Затем необходимо переопределить методы декодирования / кодирования содержащего класса:

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, это было бы замечательно. У меня есть много объектов с множеством свойств, для которых мне пришлось бы вручную реализовать методы кодирования / декодирования.

...