Search code examples
swiftdictionarycodablejsondecoder

Dictionary with Coding keys in Swift, Codale to String


I have valid, working code, but I want to find out if there is a way to make it simpler and smaller.

I have a custom class Response which can be initialised from Json or text (depends on response from server)

public class Response: Codable {

let responseP1: String?
let responseP2: String?
let responseP3: String?

enum CodingKeys: String, CodingKey {
    case responseP1 = "someResponseCode1"
    case responseP2 = "someResponseCode2"
    case responseP3 = "someResponseCode3"
}

required init(_ response: [String: String]) {
    self.responseP1 = response["someResponseCode1"]
    self.responseP3 = response["someResponseCode2"]
    self.responseP2 = response["someResponseCode3"]
}

required public init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    self.responseP1 = try container.decode(String.self, forKey: .responseP2)
    self.responseP3 = try container.decode(String.self, forKey: .responseP1)
    self.responseP2 = try container.decode(String.self, forKey: .responseP3)
}
}

can I combine Coding keys and initialisation with Dictionary somehow?

e.g. self.responseP1 = response[.responseP1] but self.responseP1 = response[CodingKeys.responseP1.rawValue] is working but looks like I am winning nothing in this case

Also I need to parse it all to String, but

public func encodeAsString() -> String? {
    do {
        let encodedResponse = try self.encoded()
        return String(decoding: encodedResponse, as: UTF8.self)
    } catch {
        return nil
    }
}

does not work for me (returns "{}" even when was initialised from Json not Text), can you give advise why?


Solution

  • If your goal is just to be able to write self.responseP1 = response[.responseP1], then you need to convert the Dictionary to the right type [CodingKeys: String].

    private static func convertKeys(from response: [String: String]) -> [CodingKeys: String] {
        Dictionary(uniqueKeysWithValues: response.compactMap {
            guard let key = CodingKeys.init(stringValue: $0) else { return nil }
            return (key: key, value: $1)
        })
    }
    
    required init(_ response: [String: String]) {
        let response = Self.convertKeys(from: response)
        self.responseP1 = response[.responseP1]
        self.responseP3 = response[.responseP2]
        self.responseP2 = response[.responseP3]
    }