简体   繁体   English

Swift 4 Codable 从字符串解码 URL

[英]Swift 4 Codable decode URL from String

I am currently trying to decode Property list using PropertyListEncoder and Swift 4 Codable protocol.我目前正在尝试使用PropertyListEncoder和 Swift 4 Codable协议解码属性列表。

I tried some basic types (Strings, Ints, ..) and these all works just fine, but I am not able decode URL.我尝试了一些基本类型(字符串、整数、..),这些都很好用,但我无法解码 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 .我正确地认为这个作品与.json文件,我没有找到有关可编码类型的不同支持任何信息JSONDecoderPropertyListDecoder Could this be caused by parser incompatibility?这可能是由解析器不兼容引起的吗?

I am using Xcode 9.1 and Swift 4.0.2.我正在使用 Xcode 9.1 和 Swift 4.0.2。

Sample .plist :示例.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:示例 Swift 代码:

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!谢谢你的帮助!

You can store URLs in Property Lists, and use the default Decodable implementation.您可以将 URL 存储在属性列表中,并使用默认的 Decodable 实现。

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".基于 Swift 标准库中Decodable 协议 init的实现,您必须将 URL 作为字典存储在 Plist 中,名为“relative”的键中。 Optionally, you can also include a nested URL dictionary named "base" in the dictionary.或者,您还可以在字典中包含一个名为“base”的嵌套 URL 字典。 These two values will get passed to the URL(string: relative, relativeTo: base) constructor during decoding.这两个值将在解码期间传递给URL(string: relative, relativeTo: base)构造函数。

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:此 Plist 将使用以下内容进行解码:

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 { }

As I can see the key web has a value of type String so we need to keep a matching type正如我所看到的,关键web有一个String类型的值,所以我们需要保持一个匹配的类型

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 ie it will be calculated once when it's needed但是要将字符串作为URL获取,那么我们需要添加一个计算变量,也许可以稍微优化一下,我们可以使变量惰性,即它会在需要时计算一次

The edited strut would look like this:编辑后的支柱看起来像这样:

struct Info: Codable {
    let web: String

    lazy var url: URL? = { return URL(string: web) }()
}

As you can in the raw xml, this is stored as a string. 正如您在原始xml中所做的那样,它存储为字符串。 AFAIK plists don't support URLs. AFAIK plists不支持URL。 It should work if you simply change web to a String. 如果您只是将web更改为String,它应该可以工作。

If you need to use the property as a URL, I would suggest using a computed property: 如果您需要将该属性用作URL,我建议使用计算属性:

var webURL: URL? {
    //return your url
}

Assuming the URL in your JSON is properly formatted, you should be able to manually decode from String to URL like this with Swift 5:假设您的 JSON 中的 URL 格式正确,您应该能够使用 Swift 5 手动从字符串解码为 URL:

let json = """
    {
        "name": "Dissent",
        "url": "https://www.dissentmagazine.org"
    }
"""

func hexAConversion(from hex: String)->Int {
    return Int(Int32(bitPattern: (UInt32(hex, radix: 16)!)))
}

let data = json.data(using: String.Encoding.utf8)!

let decoder = JSONDecoder()

struct CodeMe: Decodable {
  var name: String
  var url: URL?
    
  enum CodingKeys: String, CodingKey {
    case name, url
  }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.name = try container.decode(String.self, forKey: .name)
        self.url = URL(string: try container.decode(String.self, forKey: .url))
    }
  }

let codeMe = try! decoder.decode(CodeMe.self, from: data)

print(codeMe.url) // prints: Optional(https://www.dissentmagazine.org)

Using a similar technique you could also have a custom encoding strategy to make your object conform to Codable instead of Decodable.使用类似的技术,您还可以使用自定义编码策略使您的对象符合 Codable 而不是 Decodable。 See https://stackoverflow.com/a/65315622/713077 for more details.有关更多详细信息,请参阅https://stackoverflow.com/a/65315622/713077

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM