现场级自定义解码器

时间:2018-06-21 23:37:48

标签: swift codable

我正在尝试实现字段级别的自定义解码,以便可以提供解码器功能来映射值。最初旨在解决将“ Y”和“ N”的字符串值自动转换为true / false的问题。我有没有那么繁琐的方法?

这原本打算用于大小相当不错的唱片中的单个字段...但是有些失控了。

主要目标是不必手动实现对每个信号的解码 字段,但要枚举它们,并将默认解码器的结果用于没有自定义解码器的任何内容(可能不应称为“解码器”)。

当前尝试如下所示:

class Foo: Decodable {
    var bar: String
    var baz: String

    init(foo: String) {
        self.bar = foo
        self.baz = ""
    }

    enum CodingKeys: String, CodingKey {
        case bar
        case baz
    }

    static func customDecoder(for key: CodingKey) -> ((String) -> Any)? {
        switch key {
        case CodingKeys.baz: return { return $0 == "meow" ? "foo" : "bar" }
        default:
            return nil
        }
    }

    required init(from decoder: Decoder) throws {
        let values: KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self)

        if let cde = Foo.customDecoder(for: CodingKeys.bar) {
            self.bar = (try cde(values.decode(String.self, forKey: .bar)) as? String)!
        } else {
            self.bar = try values.decode(type(of: self.bar), forKey: .bar)
        }
        if let cde = Foo.customDecoder(for: CodingKeys.baz) {
            self.baz = (try cde(values.decode(String.self, forKey: .baz)) as? String)!
        } else {
            self.baz = try values.decode(type(of: self.baz), forKey: .baz)
        }
    }
}

使用示例:

func testFoo() {
    var foo: Foo?

    let jsonData = """
        {"bar": "foo", "baz": "meow"}
    """.data(using: .utf8)

    if let data = jsonData {
        foo = try? JSONDecoder().decode(Foo.self, from: data)
        if let bar = foo {
            XCTAssertEqual(bar.bar, "foo")
        } else {
            XCTFail("bar is not foo")
        }
    } else {
        XCTFail("Could not coerce string into JSON")
    }
}

1 个答案:

答案 0 :(得分:3)

例如,我们有一个示例json:

let json = """
{
"id": 1,
"title": "Title",
"thumbnail": "https://www.sample-videos.com/img/Sample-jpg-image-500kb.jpg",
"date": "2014-07-15"
}
""".data(using: .utf8)!

如果要解析,可以使用Codable协议和简单的NewsCodable结构:

public struct NewsCodable: Codable {
    public let id: Int
    public let title: String
    public let thumbnail: PercentEncodedUrl
    public let date: MyDate
}

PercentEncodedUrl是我们为URL定制的Codable包装器,它为url字符串添加了百分比编码。标准网址不支持该功能。

public struct PercentEncodedUrl: Codable {
    public let url: URL

    public init(from decoder: Decoder) throws {
        let urlString = try decoder.singleValueContainer().decode(String.self)

        guard
            let encodedUrlString = urlString.addingPercentEncoding(withAllowedCharacters: .urlFragmentAllowed),
            let url = URL.init(string: encodedUrlString) else {
                throw PercentEncodedUrlError.url(urlString)
        }

        self.url = url
    }

    public enum PercentEncodedUrlError: Error {
        case url(String)
    }
}

如果出于某些奇怪的原因,我们需要为日期字符串(Date decoding has plenty of support in JSONDecoder)自定义解码器,则可以提供类似PercentEncodedUrl的包装器。

public struct MyDate: Codable {
    public let date: Date

    public init(from decoder: Decoder) throws {
        let dateString = try decoder.singleValueContainer().decode(String.self)

        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "yyyy-MM-dd"
        guard let date = dateFormatter.date(from: dateString) else {
            throw MyDateError.date(dateString)
        }

        self.date = date
    }

    public enum MyDateError: Error {
        case date(String)
    }
}


let decoder = JSONDecoder()
let news = try! decoder.decode(NewsCodable.self, from: json)

因此,我们提供了场级自定义解码器。