Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift 4 Codable decode URL from String

Tags:

ios

swift

I am currently trying to decode Property list using PropertyListEncoder and Swift 4 Codable protocol.

I tried some basic types (Strings, Ints, ..) and these all works just fine, but I am not able decode URL. I read multiple articles on this topic and I am pretty sure this should just work. However, the following example fails the decoding with this error:

Expected to decode Dictionary<String, Any> but found a string/data instead.

I think this works correctly with .json files and I didn't find any informations about different support for codable types in JSONDecoder and PropertyListDecoder. Could this be caused by parser incompatibility?

I am using Xcode 9.1 and Swift 4.0.2.

Sample .plist:

<?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>web</key>
        <string>https://link.to</string>
    </dict>
</plist>

Sample Swift code:

struct Info: Codable {
   let web: URL
}

func loadInfo() {
    let propertiesDecoder = PropertyListDecoder()
    let data = try! Data(contentsOf:
        Bundle.main.url(forResource: "web", withExtension: "plist")!)

    try! propertiesDecoder.decode(Info.self, from: data)
}

Thanks for any help!

like image 808
josefdolezal Avatar asked Nov 28 '22 13:11

josefdolezal


2 Answers

You can store URLs in Property Lists, and use the default Decodable implementation.

Based on the implementation of the Decodable protocol init in the Swift standard library, you must store the URL as a dictionary in the Plist, in a key called "relative". Optionally, you can also include a nested URL dictionary named "base" in the dictionary. These two values will get passed to the URL(string: relative, relativeTo: base) constructor during decoding.

For example:

<?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">
<array>
    <dict>
        <key>apiHost</key>
        <dict>
            <key>relative</key>
            <string>https://example.com/api/v0</string>
        </dict>
        <key>otherKey</key>
        <string>SomeValue</string>
       </dict>
 </array>
 </plist>

This Plist will decode using the following:

struct PlistItem: Codable {
    let apiHost: URL
    let otherKey: String
}
guard let filePath = Bundle.main.url(forResource: "Data", withExtension: "plist") else { return }
do {
    let data = try Data(contentsOf: filePath)
    let decoder = PropertyListDecoder()
    let values = try decoder.decode([PlistItem].self, data)
}
catch { }
like image 129
Eric Yanush Avatar answered Dec 01 '22 01:12

Eric Yanush


As I can see the key web has a value of type String so we need to keep a matching type

But to get the string as a URL then we need to add a computed variable and maybe to optimize it a little bit we can make the variable lazy i.e it will be calculated once when it's needed

The edited strut would look like this:

struct Info: Codable {
    let web: String

    lazy var url: URL? = { return URL(string: web) }()
}
like image 30
zombie Avatar answered Dec 01 '22 02:12

zombie