словари swift: карта внутри карты

#swift #dictionary #enums

#swift #словарь #перечисления

Вопрос:

Я хотел бы преобразовать значение, которое я получаю из API, в определенный формат.

 [String:Any] // format received
[Int:[ContentType:Int]] // required format
  

ContentType — это перечисление

Пример данных может выглядеть следующим образом:

 ["123":["Tables":"25","Chairs":"14"]] // input
[123:[.Tables:25,.Chairs:14]] // output
  

Я думаю, что мне нужно иметь карту внутри карты, чтобы это работало, но я изо всех сил пытаюсь выработать способ продвижения вперед. Хотя, возможно, я лаю совершенно не на то дерево. На самом деле я не хочу вручную перебирать и добавлять каждый элемент по одному; я ищу что-то более интеллектуальное, чем это, если это возможно.

 enum ContentType: String  {
    case Tables,Chairs
}

let original_values: [String:Any]
    =  ["1234":["Tables":"5","Chairs":"2"]]
let values: [Int:[ContentType:Int]]
    = Dictionary(uniqueKeysWithValues: original_values.map {
        (
            Int($0.key)!,
            (($0.value as? [String:String]).map { // Error on this line - expects 1 argument but two were used
                (
                    ContentType(rawValue: $1.key)!, // $1 is presumably wrong here?
                    Int($1.value)
                )
            }) as? [ContentType:Int]
        )
    })
  

У кого-нибудь есть идеи?

Ответ №1:

Я хотел бы преобразовать значение, которое я получаю из API, в определенный формат.

Вы можете создать свое перечисление Decodable

 enum ContentType: String, Decodable {
    case tables, chairs
    enum CodingKeys: String, CodingKey {
        case Tables = "Tables"
        case Chairs = "Chairs"
    }
}
  

Затем вы можете декодировать полученное, Data а затем compactMap его отформатировать (Int, [ContentType: Int]) . Эти кортежи вы можете преобразовать в Dictionary с помощью разработанного инициализатора

 do {
    let decoded = try JSONDecoder().decode([String: [ContentType: Int]].self, from: data)
    let mapped = Dictionary(uniqueKeysWithValues: decoded.compactMap { (key,value) -> (Int, [ContentType: Int])? in
        if let int = Int(key) {
            return (int, value)
        } else {
            return nil
        }
    })
} catch {
    print(error)
}
  

Комментарии:

1. Это не ответ на вопрос OP. Но огромный плюс для декодируемости. Но почему вы [String: ...] вместо [Int: ...] декодируете? Проблема с decodable заключается в том, что когда хотя бы одно целое число где-то внутри будет неправильным, тогда полное декодирование завершится ошибкой.

2. @ManWithBear Я декодирую [String: ...] и переназначаю его, потому что ключи в словаре из ответа String нет Int . Также для вашего последнего предложения: этого не произойдет, потому что я использую compactMap

3. в таком случае вам нужно расшифровать в [String: [ContentType: String]] , а затем также отобразить значение внутри значения.

Ответ №2:

В этой строке:

 (($0.value as? [String:String]).map {
  

Вы используете не Sequence.map , а Optional.map .

Рабочее решение:

 /// First let's map plain types to our types
let resultArray = original_values
    .compactMap { (key, value) -> (Int, [ContentType: Int])? in
        guard let iKey = Int(key), let dValue = value as? [String: String] else { return nil }
        let contentValue = dValue.compactMap { (key, value) -> (ContentType, Int)? in
            guard let cKey = ContentType(rawValue: key), let iValue = Int(value) else { return nil }
            return (cKey, iValue)
        }
        let contentDict = Dictionary(uniqueKeysWithValues: contentValue)
        return (iKey, contentDict)
    }
let result = Dictionary(uniqueKeysWithValues: resultArray)
  

Для улучшения вывода на печать добавьте соответствие CustomStringConvertible :

 extension ContentType: CustomStringConvertible {
    var description: String {
        switch self {
        case .Tables:
            return "Tables"
        case .Chairs:
            return "Chairs"
        }
    }
}
  

Комментарии:

1. Мне действительно нравится этот подход, спасибо! К сожалению, я получаю [1234: [__lldb_expr_19.ContentType.Chairs: 2, __lldb_expr_19.ContentType.Tables: 5]] на игровой площадке — что бы это ни значило…

2. @Ben, по-моему, все в порядке

3. @Ben это правильно. Игровая площадка скомпилирует ваш код во внутреннем модуле, чтобы вы получили эти дополнительные префиксы для перечисления

Ответ №3:

Это правильный синтаксис Swift 5

 enum ContentType: String  {
    case tables = "Tables"
    case chairs = "Chairs"
}

let originalValues: [String: [String: String]]
    =  ["1234": ["Tables": "5", "Chairs": "2"]]

    let values: [Int: [ContentType: Int]] = Dictionary(uniqueKeysWithValues:
        originalValues.map { arg in
            let (key, innerDict) = arg
            let outMap: [ContentType: Int] = Dictionary(uniqueKeysWithValues:
                innerDict.map { innerArg in
                let (innerKey, innerValue) = innerArg
                return (ContentType.init(rawValue: innerKey)!, Int(innerValue)!)
            }
            )
            return (Int(key)!, outMap)
        }
    )
    print(values)
  

[1234: [__lldb_expr_5.ContentType.tables: 5, __lldb_expr_5.ContentType.chairs: 2]]

Комментарии:

1. Вы никогда не должны использовать явно развернутые опции при работе с внутренними данными

2. Это всего лишь пример игровой площадки