Swift 4 JSON Decodable simplest way to decode type change

后端 未结 8 2010
天涯浪人
天涯浪人 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:14

    I used Suran's version, but updated it to return non-optional value for decode(). To me this is the most elegant version. Swift 5.2.

    extension KeyedDecodingContainer {
    
    func decodeIfPresent(_ type: Float.Type, forKey key: K, transformFrom: String.Type) throws -> Float? {
        guard let value = try decodeIfPresent(transformFrom, forKey: key) else {
            return nil
        }
        return Float(value)
    }
    
    func decode(_ type: Float.Type, forKey key: K, transformFrom: String.Type) throws -> Float {
        guard let str = try? decode(transformFrom, forKey: key),
            let value = Float(str) else {
                throw DecodingError.typeMismatch(Int.self, DecodingError.Context(codingPath: codingPath, debugDescription: "Decoding of \(type) from \(transformFrom) failed"))
        }
        return value
    }
    }
    
    0 讨论(0)
  • 2020-12-01 06:18

    Using Swift 5.1, you may choose one of the three following ways in order to solve your problem.


    #1. Using Decodable init(from:) initializer

    Use this strategy when you need to convert from String to Float for a single struct, enum or class.

    import Foundation
    
    struct ExampleJson: Decodable {
    
        var name: String
        var age: Int
        var taxRate: Float
    
        enum CodingKeys: String, CodingKey {
            case name, age, taxRate = "tax_rate"
        }
    
        init(from decoder: Decoder) throws {
            let container = try decoder.container(keyedBy: CodingKeys.self)
    
            name = try container.decode(String.self, forKey: CodingKeys.name)
            age = try container.decode(Int.self, forKey: CodingKeys.age)
            let taxRateString = try container.decode(String.self, forKey: CodingKeys.taxRate)
            guard let taxRateFloat = Float(taxRateString) else {
                let context = DecodingError.Context(codingPath: container.codingPath + [CodingKeys.taxRate], debugDescription: "Could not parse json key to a Float object")
                throw DecodingError.dataCorrupted(context)
            }
            taxRate = taxRateFloat
        }
    
    }
    

    Usage:

    import Foundation
    
    let jsonString = """
    {
      "name": "Bob",
      "age": 25,
      "tax_rate": "4.25"
    }
    """
    
    let data = jsonString.data(using: String.Encoding.utf8)!
    let decoder = JSONDecoder()
    let exampleJson = try! decoder.decode(ExampleJson.self, from: data)
    dump(exampleJson)
    /*
     prints:
     ▿ __lldb_expr_126.ExampleJson
       - name: "Bob"
       - age: 25
       - taxRate: 4.25
     */
    

    #2. Using an intermediate model

    Use this strategy when you have many nested keys in your JSON or when you need to convert many keys (e.g. from String to Float) from your JSON.

    import Foundation
    
    fileprivate struct PrivateExampleJson: Decodable {
    
        var name: String
        var age: Int
        var taxRate: String
    
        enum CodingKeys: String, CodingKey {
            case name, age, taxRate = "tax_rate"
        }
    
    }
    
    struct ExampleJson: Decodable {
    
        var name: String
        var age: Int
        var taxRate: Float
    
        init(from decoder: Decoder) throws {
            let privateExampleJson = try PrivateExampleJson(from: decoder)
    
            name = privateExampleJson.name
            age = privateExampleJson.age
            guard let convertedTaxRate = Float(privateExampleJson.taxRate) else {
                let context = DecodingError.Context(codingPath: [], debugDescription: "Could not parse json key to a Float object")
                throw DecodingError.dataCorrupted(context)
            }
            taxRate = convertedTaxRate
        }
    
    }
    

    Usage:

    import Foundation
    
    let jsonString = """
    {
      "name": "Bob",
      "age": 25,
      "tax_rate": "4.25"
    }
    """
    
    let data = jsonString.data(using: String.Encoding.utf8)!
    let decoder = JSONDecoder()
    let exampleJson = try! decoder.decode(ExampleJson.self, from: data)
    dump(exampleJson)
    /*
     prints:
     ▿ __lldb_expr_126.ExampleJson
       - name: "Bob"
       - age: 25
       - taxRate: 4.25
     */
    

    #3. Using a KeyedDecodingContainer extension method

    Use this strategy when converting from some JSON keys' types to your model's property types (e.g. String to Float) is a common pattern in your application.

    import Foundation
    
    extension KeyedDecodingContainer  {
    
        func decode(_ type: Float.Type, forKey key: Key) throws -> Float {
            if let stringValue = try? self.decode(String.self, forKey: key) {
                guard let floatValue = Float(stringValue) else {
                    let context = DecodingError.Context(codingPath: codingPath, debugDescription: "Could not parse json key to a Float object")
                    throw DecodingError.dataCorrupted(context)
                }
                return floatValue
            } else {
                let doubleValue = try self.decode(Double.self, forKey: key)
                return Float(doubleValue)
            }
        }
    
    }
    
    struct ExampleJson: Decodable {
    
        var name: String
        var age: Int
        var taxRate: Float
    
        enum CodingKeys: String, CodingKey {
            case name, age, taxRate = "tax_rate"
        }
    
    }
    

    Usage:

    import Foundation
    
    let jsonString = """
    {
        "name": "Bob",
        "age": 25,
        "tax_rate": "4.25"
    }
    """
    
    let data = jsonString.data(using: String.Encoding.utf8)!
    let decoder = JSONDecoder()
    let exampleJson = try! decoder.decode(ExampleJson.self, from: data)
    dump(exampleJson)
    /*
     prints:
     ▿ __lldb_expr_126.ExampleJson
     - name: "Bob"
     - age: 25
     - taxRate: 4.25
     */
    
    0 讨论(0)
提交回复
热议问题