1

I've been experimenting with customized Decodable properties for handling JSON in Swift 4 and I'm really impressed with the ease of mapping tricky type and format conversions.

However in the JSON data structures that the server exposes to me, only a handful of properties need this treatment. The rest are simple integers and strings. Is there some way to mix the customized decoder with the standard one?

Here's a simplified example showing what I'd like to get rid of:

struct mystruct : Decodable {
    var myBool: Bool
    var myDecimal: Decimal
    var myDate: Date
    var myString: String
    var myInt: Int
}

extension mystruct {
    private struct JSONsource: Decodable {
        var my_Bool: Int
        var my_Decimal: String
        var my_Date: String

        // These seem redundant, how can I remove them?
        var myString: String
        var myInt: Int
    }

    private enum CodingKeys: String, CodingKey {
        case item
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let item = try container.decode(JSONsource.self, forKey: .item)
        myBool = item.my_Bool == 1 ? true : false
        myDecimal = Decimal(string: item.my_Decimal)!
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSSSSSZZZZZ"
        dateFormatter.locale = Locale(identifier: "en_US_POSIX")
        myDate = dateFormatter.date(from: item.my_Date)!

        // Can I somehow get rid of this redundant-looking code?
        myString = item.myString
        myInt = item.myInt
    }
}


let myJSON = """
{
    "item": {
        "my_Decimal": "123.456",
        "my_Bool" : 1,
        "my_Date" : "2019-02-08T11:14:31.4547774-05:00",
        "myInt" : 148727,
        "myString" : "Hello there!"
    }
}
""".data(using: .utf8)

let x = try JSONDecoder().decode(mystruct.self, from: myJSON!)

print("My decimal: \(x.myDecimal)")
print("My bool: \(x.myBool)")
print("My date: \(x.myDate)")
print("My int: \(x.myInt)")
print("My string: \(x.myString)")
hocker
  • 688
  • 6
  • 18

1 Answers1

1

JSONDecoder has a dateDecodingStrategy. No need to decode it manually. To simplify decoding your coding keys you can set decoders property .keyDecodingStrategy to . convertFromSnakeCase. You have also some type mismatches you can handle adding computed properties. Btw this might help creating a custom formatter for your ISO8601 date string with fractional seconds. How to create a date time stamp and format as ISO 8601, RFC 3339, UTC time zone? . Last but not least, it is Swift convention to name your structures using UpperCamelCase

struct Root: Codable {
    let item: Item
}

struct Item : Codable {
    var myBool: Int
    var myDecimal: String
    var myDate: Date
    var myString: String
    var myInt: Int
}

extension Item {
    var bool: Bool {
        return myBool == 1
    }
    var decimal: Decimal? {
        return Decimal(string: myDecimal)
    }
}

extension Item: CustomStringConvertible {
    var description: String {
        return "Iten(bool: \(bool), decimal: \(decimal ?? 0), date: \(myDate), string: \(myString), int: \(myInt))"
    }
}

let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase

let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSSSSSZZZZZ"
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
decoder.dateDecodingStrategy = .formatted(dateFormatter)

do {
    let item = try decoder.decode(Root.self, from: myJSON).item
    print(item)  // Iten(bool: true, decimal: 123.456, date: 2019-02-08 16:14:31 +0000, string: Hello there!, int: 148727)
}
catch {
    print(error)
}
Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
  • OK checking on that. I have about 40 properties that I need to go through so I'll post my results after I work through them. Thanks. – hocker Feb 10 '19 at 19:42
  • 1
    It took me a while but I went through and worked through all of my properties. The dateDecodingStrategy worked. Thanks! – hocker Feb 17 '19 at 19:38