Search code examples
swiftplistcodable

Universal PLIST reader with codable structure


My plist-reader works well with a specific structure, but crashes when I try to make it universal. Here is a specific structure edition (works well):

struct Auth0_: Codable {
    let domain: String
    let clientId: String
}

extension Bundle {
    
    func decodePlist(plistName: String) -> Auth0_ {
        guard let path = Bundle.main.url(forResource: plistName, withExtension: "plist") else {
            fatalError("There is no such plist-file \(plistName).")
        }
        
        guard
            let data = try? Data(contentsOf: path),
            let dict = try? PropertyListDecoder().decode(Auth0_.self, from: data)
        else {
            fatalError("Cannot convert plist \(plistName) to JSON.")
        }
                
        return dict
    }
}

let dict = Bundle.main.decodePlist(plistName: "Auth0")

Then I try to make function <T: Codable> and get the fatal error

Cannot convert plist...

struct Auth0_: Codable {
    let Domain: String
    let ClientId: String
}

extension Bundle {

    func decodePlist<T: Codable>(plistName: String) -> T {
        guard let path = Bundle.main.url(forResource: plistName, withExtension: "plist") else {
            fatalError("There is no such plist-file \(plistName).")
        }
        
        guard
            let data = try? Data(contentsOf: path),
            let dict = try? PropertyListDecoder().decode(T.self, from: data)
        else {
            fatalError("Cannot convert plist \(plistName) to JSON.")
        }
                
        return dict
    }
}

let dict: [Auth0_] = Bundle.main.decodePlist(plistName: "Auth0")

Where is the mistake?


Solution

  • I guess you haven't changed your plist content in the two codes.

    You have two lines of cause that can cause the fatalError.

    guard
        let data = try? Data(contentsOf: path),
        let dict = try? PropertyListDecoder().decode(T.self, from: data)
    else {
        fatalError("Cannot convert plist \(plistName) to JSON.")
    }
    

    Debugging start to find the reason, so let's split them:

    guard let data = try? Data(contentsOf: path) else {
        fatalError("Cannot convert read plist data \(plistName).")
    }
    guard let dict = try? PropertyListDecoder().decode(T.self, from: data) else {
        fatalError("Cannot convert plist \(plistName) to JSON.")
    }
    

    Now, we can see that's the second line that is causing the issue.

    Now, I'd strongly suggest to NEVER use try? (with a question mark) when you don't know how to debug it. Use a proper do/try/catch:

    do {
        let dict = try PropertyListDecoder().decode(T.self, from: data)
    } catch {
            fatalError("Cannot convert plist \(plistName) to JSON error: \(error)")
    }
    

    Now, you should get an error about decoding issue, with explicit info:

    typeMismatch(Swift.Array<Any>, 
                 Swift.DecodingError.Context(codingPath: [], 
                                             debugDescription: "Expected to decode Array<Any> but found a dictionary instead.", 
                                             underlyingError: nil))
    

    So it says that at the top level (codingPath being empty), it expects an array, but found a dictionary instead.

    Why is that happening?

    Because when you wrote let dict: [Auth0_] = Bundle.main.decodePlist(plistName: "Auth0"), it then did let dict = try? PropertyListDecoder().decode([Auth0_].self, from: data), which is different from what you used before when the code was specific and not generic.

    So it should be:

    let dict: Auth0_ = Bundle.main.decodePlist(plistName: "Auth0")
    

    Now, if we look at your code with error, you wrote let dict: [Auth0_] = ..., but [Auth0_], that's Array<Auth0_>, so you expected it to have an array (ie a list), of Auth0_, but you have one only, and that's not a list of one element. So the naming was misleading at start.

    Side note: You are just decoding, so you shouldn't need to restrict it to Codable, but only Decodable. Since Codable is Encodable + Decodable.