Swift 4 JSON Decodable simplest way to decode type change

后端 未结 8 2046
天涯浪人
天涯浪人 2020-12-01 05:47

With Swift 4\'s Codable protocol there\'s a great level of under the hood date and data conversion strategies.

Given the JSON:

{
    \"name\": \"Bob\         


        
8条回答
  •  孤独总比滥情好
    2020-12-01 06:09

    Unfortunately, I don't believe such an option exists in the current JSONDecoder API. There only exists an option in order to convert exceptional floating-point values to and from a string representation.

    Another possible solution to decoding manually is to define a Codable wrapper type for any LosslessStringConvertible that can encode to and decode from its String representation:

    struct StringCodableMap : Codable {
    
        var decoded: Decoded
    
        init(_ decoded: Decoded) {
            self.decoded = decoded
        }
    
        init(from decoder: Decoder) throws {
    
            let container = try decoder.singleValueContainer()
            let decodedString = try container.decode(String.self)
    
            guard let decoded = Decoded(decodedString) else {
                throw DecodingError.dataCorruptedError(
                    in: container, debugDescription: """
                    The string \(decodedString) is not representable as a \(Decoded.self)
                    """
                )
            }
    
            self.decoded = decoded
        }
    
        func encode(to encoder: Encoder) throws {
            var container = encoder.singleValueContainer()
            try container.encode(decoded.description)
        }
    }
    

    Then you can just have a property of this type and use the auto-generated Codable conformance:

    struct Example : Codable {
    
        var name: String
        var age: Int
        var taxRate: StringCodableMap
    
        private enum CodingKeys: String, CodingKey {
            case name, age
            case taxRate = "tax_rate"
        }
    }
    

    Although unfortunately, now you have to talk in terms of taxRate.decoded in order to interact with the Float value.

    However you could always define a simple forwarding computed property in order to alleviate this:

    struct Example : Codable {
    
        var name: String
        var age: Int
    
        private var _taxRate: StringCodableMap
    
        var taxRate: Float {
            get { return _taxRate.decoded }
            set { _taxRate.decoded = newValue }
        }
    
        private enum CodingKeys: String, CodingKey {
            case name, age
            case _taxRate = "tax_rate"
        }
    }
    

    Although this still isn't as a slick as it really should be – hopefully a later version of the JSONDecoder API will include more custom decoding options, or else have the ability to express type conversions within the Codable API itself.

    However one advantage of creating the wrapper type is that it can also be used in order to make manual decoding and encoding simpler. For example, with manual decoding:

    struct Example : Decodable {
    
        var name: String
        var age: Int
        var taxRate: Float
    
        private enum CodingKeys: String, CodingKey {
            case name, age
            case taxRate = "tax_rate"
        }
    
        init(from decoder: Decoder) throws {
            let container = try decoder.container(keyedBy: CodingKeys.self)
    
            self.name = try container.decode(String.self, forKey: .name)
            self.age = try container.decode(Int.self, forKey: .age)
            self.taxRate = try container.decode(StringCodableMap.self,
                                                forKey: .taxRate).decoded
        }
    }
    

提交回复
热议问题