Из-за [AnyHashable: Any]
(мне это нужно) я должен реализовать init(from:)
и encode(to:)
. Но когда я запускаю его, ему не удается декодировать свойство со значением массива:
typeMismatch(Swift.String, Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "items", intValue: nil)], debugDescription: "Ожидается декодирование строки, но вместо этого найден массив.", baseError: nil))
Это код, который вы можете запустить в Playground:
struct ServerResponse: Codable {
var headers: [AnyHashable: Any]?
var items: [Item]?
enum CodingKeys: String, CodingKey {
case items, headers
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: ServerResponse.CodingKeys.self)
items = try container.decode([Item].self, forKey: ServerResponse.CodingKeys.items)
let singleValueContainer = try decoder.singleValueContainer()
let stringDictionary = try singleValueContainer.decode([String: String].self)
headers = [:]
for (key, value) in stringDictionary {
headers?[key] = value
}
}
public func encode(to encoder: Encoder) throws {
let stringDictionary: [String: String] = Dictionary(
uniqueKeysWithValues: headers?.map {("\($0)", "\($1)")} ?? []
)
var singleValueContainer = encoder.singleValueContainer()
try singleValueContainer.encode(stringDictionary)
var container = encoder.container(keyedBy: ServerResponse.CodingKeys.self)
try container.encode(items, forKey: ServerResponse.CodingKeys.items)
}
struct Item: Codable {
let name: String
}
}
let testData = """
{
"items": [
{"name": "John"},
{"name": "Duo"}
]
}
""".data(using: .utf8)!
let decoder = JSONDecoder()
do {
let response = try decoder.decode(ServerResponse.self, from: testData)
print(response)
} catch {
print(error)
}
Что с этим не так? Почему он жалуется на получение String
, когда я поставил массив?
Если я удаляю заголовки из структуры и соответствую Codable
, все работает нормально.
Проблема не в заголовках, если данные для заголовков существуют, проблема будет работать, проблема в элементах.
@ItaiFerber Да, комментирование этих строк исправит ошибку
Не имеет отношения к этому, но почему вы конвертируете сильно типизированный [String:String]
в слабо типизированный [AnyHashable: Any]
?
Это исходит из другого протокола, которому я должен соответствовать.
Проблема здесь заключается в том, как вы пытаетесь извлечь элементы из этого словаря верхнего уровня, пытаясь декодировать его как словарь. Конкретно,
let singleValueContainer = try decoder.singleValueContainer()
let stringDictionary = try singleValueContainer.decode([String: String].self)
является проблемным фрагментом. С вашей конкретной полезной нагрузкой JSON singleValueContainer
здесь завершается
{
"items": [ ... ],
"..." // <- assuming there are other actual keys and values
}
Это допустимо, но когда вы пытаетесь декодировать содержимое контейнера как [String: String]
, вы утверждаете, что ожидаете, что контейнер будет содержать конкретно словарь с ключами String
и значениями String
; значение для ключа items
, однако, является не строкой, а массивом.
Если у вас есть коллекция с произвольными значениями, правильный подход к извлечению ее содержимого заключается в использовании контейнера с ключом. В частности, вы можете использовать контейнер с ключом, тип ключа которого может принимать любое значение String
или Int
, например:
struct AnyCodingKey: CodingKey {
let intValue: Int?
let stringValue: String
init?(intValue: Int) {
self.intValue = intValue
self.stringValue = "\(intValue)"
}
init?(stringValue: String) {
intValue = Int(stringValue)
self.stringValue = stringValue
}
}
С помощью этого типа ключа кодирования вы можете запросить decoder
в качестве другого контейнера с ключом — и на этот раз ключи могут быть произвольными:
let untypedContainer = try decoder.container(keyedBy: AnyCodingKey.self)
Хитрость в том, что теперь значения в untypedContainer
не считаются принадлежащими какому-либо типу, пока вы не попытаетесь их декодировать. Затем вы можете перебирать untypedContainer.allKeys
(так же, как вы перебираете stringDictionary
в данный момент), и для каждого ключа вы можете решить, как вы хотите decode(_:forKey:)
выйти из контейнера. Вы могли бы:
String
, и если вы получите ошибку DecodinerError.typeMismatch
, просто пропустите пару ключ-значениеНапример:
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: ServerResponse.CodingKeys.self)
items = try container.decode([Item].self, forKey: ServerResponse.CodingKeys.items)
headers = [:]
let untypedContainer = try decoder.container(keyedBy: AnyCodingKey.self)
for key in untypedContainer.allKeys {
do {
let value = try untypedContainer.decode(String.self, forKey: key)
headers[key.stringValue] = value
} catch DecodingError.typeMismatch { /* skip this key-value pair */ }
}
}
Ваш контейнер с одним значением обертывает
{ "items": [ ... ] }
, и вы пытаетесь декодировать его как[String: String]
, но значением ключаitems
является массив, а неString
. Можете ли вы подтвердить, что закомментирование строк послеitems = try container...
приводит к тому, что ошибка не возникает? (Если это так, есть еще способы декодировать элементы заголовка, которые вы ищете.)