Decode PropertyList using Swift 4 Codable PropertyListDecoder()

SirCJ picture SirCJ · Aug 2, 2017 · Viewed 7.6k times · Source

Im trying to decode a plist using PropertyListDecoder() but when I get to the trying to access the keys, I get an error that says it's in the wrong format. I'm at a loss on what I'm doing wrong. Im under the assumption I can decode a Plist file the same way I can decode a JSON file right? I don't know, I'm still new to this.

//struct for PLists
struct AccessControl: Decodable {
    enum AccessControlKeys: String, CodingKey {
         case api
     }

    enum KeySecretKeys: String, CodingKey {
        case apiKey = "KEY"
        case apiSecret = "SECRET"
    }

    var KEYS: [KeySecrets]
//custom decoder
    init(from decoder: Decoder) throws {
        let accessContainer = try decoder.container(keyedBy: AccessControlKeys.self)
        let nestedContainer = try accessContainer.nestedContainer(keyedBy: KeySecretKeys.self, forKey: .api)
        self.KEYS = try nestedContainer([KeySecrets].self, forKey: .apiKey)
        self.KEYS = try nestedContainer.decode([KeySecrets].self, forKey: .apiSecret)
    }
}

struct KeySecrets: Decodable {
    var apiKey: String
    var apiSecret: String
}



func provideAccessKeys(for api: apis = .api, mode: modes = .dev) -> keysForApi? {
    switch api {
    case .api:
        print("Api")
    }
    switch mode {
    case .dev:
        print("mode - developers")
    case .test:
        print("mode - test")
    case .prod:
        print("mode - production")
    }
}

This was my first approach at it, but it would throw an error saying

'The data couldn't be read because it was the wrong format'

if let fileURL = Bundle.main.url(forResource: "Accesscontrol",   withExtension: "plist") {
    do {
        let data = try Data.init(contentsOf: fileURL, options: .mappedIfSafe)
        let decoder = PropertyListDecoder()
        let result = try decoder.decode(AccessControl.self, from: data)

    } catch {
        print(error.localizedDescription)
    }
}

Second approach, kinda just abandoned Codable all together, still couldn't pull out the values

guard let fileUrl = Bundle.main.url(forResource: "Accesscontrol", withExtension: "plist") else {return}
let key: String
let secret: String
do {
    let data = try Data.init(contentsOf: fileUrl, options: .mappedIfSafe)
    let plist = try! PropertyListSerialization.propertyList(from:data, options: [], format: nil) as! [Any]
  print(plist)
   let dictionary = plist[api.rawValue]

} catch {
    print(error.localizedDescription)
}

The plist file is structured like

<plist version="1.0">
<dict>
    <key>A_GROUP_OF_KEYS</key>
    <array>
        <dict>
            <key>KEY1</key>
            <string>KEY1_STRING</string>
            <key>SECRET1_KEY</key>
            <string>SECRET1_STRING</string>
        </dict>
        <dict>
        <key>KEY2</key>
        <string>KEY2_STRING</string>
        <key>SECRET2_KEY</key>
        <string>SECRET2_VALUE</string>
        </dict>
        <dict>
        <key>KEY</key>
        <string>KEY_STRING</string>
        <key>SECRET_KEY</key>
        <string>SECRET_VALUE</string>
        </dict>
    </array>
<key>ANOTHER_GROUP_OF_KEYS</key>
    <array>
        <dict>
            <key>KEY1</key>
            <string>KEY1_STRING</string>
            <key>SECRET1_KEY</key>
            <string>SECRET1_STRING</string>
        </dict>
        <dict>
        <key>KEY2</key>
        <string>KEY2_STRING</string>
        <key>SECRET2_KEY</key>
        <string>SECRET2_VALUE</string>
        </dict>
        <dict>
        <key>KEY</key>
        <string>KEY_STRING</string>
        <key>SECRET_KEY</key>
        <string>SECRET_VALUE</string>
        </dict>
    </array>
</dict>
</plist>

Any advice?

Answer

Code Different picture Code Different · Aug 19, 2017

Your plist file was badly formatted and hence undecodable. You shouldn't name every key with a different key name like KEY1, KEY2, KEY3, etc. Instead, you should use one name for the key key and put the actual name in the value field. The same goes for secret.

Here's a better plist file:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>A_GROUP_OF_KEYS</key>
    <array>
        <dict>
            <key>key</key>
            <string>KEY1_STRING</string>
            <key>secret</key>
            <string>SECRET1_STRING</string>
        </dict>
        <dict>
            <key>key</key>
            <string>KEY2_STRING</string>
            <key>secret</key>
            <string>SECRET2_VALUE</string>
        </dict>
        <dict>
            <key>key</key>
            <string>KEY3_STRING</string>
            <key>secret</key>
            <string>SECRET3_VALUE</string>
        </dict>
    </array>
    <key>ANOTHER_GROUP_OF_KEYS</key>
    <array>
        <dict>
            <key>key</key>
            <string>KEY1_STRING</string>
            <key>secret</key>
            <string>SECRET1_STRING</string>
        </dict>
        <dict>
            <key>key</key>
            <string>KEY2_STRING</string>
            <key>secret</key>
            <string>SECRET2_VALUE</string>
        </dict>
        <dict>
            <key>key</key>
            <string>KEY3_STRING</string>
            <key>secret</key>
            <string>SECRET3_VALUE</string>
        </dict>
    </array>
</dict>
</plist>

Decoding this is dead simple:

struct AccessControl: Decodable {
    struct Key: Decodable {
        var key: String
        var secret: String
    }

    var keyGroup1: [Key]
    var keyGroup2: [Key]

    enum CodingKeys: String, CodingKey {
        case keyGroup1 = "A_GROUP_OF_KEYS"
        case keyGroup2 = "ANOTHER_GROUP_OF_KEYS"
    }
}