Как закодировать словарь Codables в Swift? - PullRequest
0 голосов
/ 12 октября 2018

У меня есть словарь [String: Codable] в Swift, который я хочу сохранить в пользовательских настройках по умолчанию, но я изо всех сил пытаюсь это сделать.

Я пытался преобразовать его в Data, используя

try! JSONSerialization.data(withJSONObject: dictionary, options: .init(rawValue: 0))

Но это дает сбой («Недопустимый тип в записи JSON (_SwiftValue)»)

Я пытался использовать JSONEncoder:

JSONEncoder().encode(dictionary)

но это не скомпилируется («Общий параметр T не может быть выведен»).

Конечно, я мог бы вручную преобразовать все мои Codables в [String: Any] и затем записать его впользователь по умолчанию, но поскольку весь смысл Codable состоит в том, чтобы упростить декодирование и кодирование, я не совсем уверен, почему два вышеупомянутых решения не возможны (особенно второе)?

Пример :

Для воспроизводимости вы можете использовать этот код на игровой площадке:

import Foundation

struct A: Codable {}
struct B: Codable {}

let dict = [ "a": A(), "b": B() ] as [String : Codable]
let data = try JSONEncoder().encode(dict)

Ответы [ 2 ]

0 голосов
/ 24 августа 2019

UserDefaults позволяет сохранить словари [String: Any]:

let myDictionary: [String: Any] = ["a": "one", "b": 2]
UserDefaults.standard.set(myDictionary, forKey: "key")
let retrievedDictionary: [String: Any] = UserDefaults.standard.dictionary(forKey: "key")!
print(retrievedDictionary)      // prints ["a": one, "b": 2]

Однако, если ваш словарь является свойством объекта, который вы хотите сохранить в UserDefaults, вам необходимо реализоватьCodable протокол для вашего объекта.Самый простой способ, который я знаю, - преобразовать словарь в объект Data, используя JSONSerialization.Следующий код работает для меня:

class MyObject: Codable {

    let dictionary: [String: Any]

    init(dictionary: [String: Any]) {
        self.dictionary = dictionary
    }

    enum CodingKeys: String, CodingKey {
        case dictionary
    }

    public required init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        if values.contains(.dictionary), let jsonData = try? values.decode(Data.self, forKey: .dictionary) {
            dictionary = (try? JSONSerialization.jsonObject(with: jsonData) as? [String: Any]) ??  [String: Any]()
        } else {
            dictionary = [String: Any]()
        }
    }

    public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        if !dictionary.isEmpty, let jsonData = try? JSONSerialization.data(withJSONObject: dictionary) {
            try container.encode(jsonData, forKey: .dictionary)
        }
    }
}

Чтобы сохранить и извлечь MyObject из UserDefaults, вы можете сделать это:

extension UserDefaults {

    func set(_ value: MyObject, forKey defaultName: String) {
        guard let data = try? PropertyListEncoder().encode(value) else { return }
        set(data, forKey: defaultName)
    }

    func myObject(forKey defaultName: String) -> MyObject? {
        guard let data = data(forKey: defaultName) else { return nil }
        return try? PropertyListDecoder().decode(MyObject.self, from: data)
    }
}
0 голосов
/ 12 октября 2018

Codable, как общее ограничение, и Any не кодируются.Используйте структуру вместо словаря:

struct A: Codable {
    let a = 0
}
struct B: Codable {
    let b = "hi"
}
struct C: Codable {
    let a: A
    let b: B
}

let d = C(a: A(), b: B())
let data = try JSONEncoder().encode(d)
...