代码之家  ›  专栏  ›  技术社区  ›  Tibrogargan

字段级自定义解码器

  •  1
  • Tibrogargan  · 技术社区  · 6 年前

    主要的目标是不必手动实现每一个

    当前尝试如下所示:

    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 回复  |  直到 6 年前
        1
  •  3
  •   shio    6 年前

    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)!
    

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

    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

    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)