Until recently, I have been able to decode both lower case ("fooBar") JSON and pascal case ("FooBar") JSON using the Decodable protocol by simply including a CodingKeys enum like this...
enum CodingKeys: String, CodingKey {
case bars = "Bars"
}
That has allowed me to decode JSON in either of these forms: {"bars":[]} or {"Bars":[]}
But that no longer works for me.
The complete example is below. When I run this code, only the JSON that has the Pascal case fieldnames are decoded. The only way to decode the lower case JSON is to either change the CodingKeys to lower case (which simply matches the defined fieldnames) or remove them completely.
Example:
import UIKit
struct Foo: Codable {
var bars: [Bar]
enum CodingKeys: String, CodingKey {
case bars = "Bars"
}
struct Bar: Codable {
var barBaz: String
enum CodingKeys: String, CodingKey {
case barBaz = "BarBaz"
}
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let fooBars = [
"{\"bars\": [{\"barBaz\": \"abc\"}]}",
"{\"Bars\": [{\"BarBaz\": \"abc\"}]}",
"{\"Bars\": [{\"BarBaz\": \"abc\"},{\"BarBaz\": \"def\"}]}",
"{\"Bars\": []}",
"{\"bars\": []}"
]
fooBars.forEach {
if let fooBar = $0.data(using: .utf8) {
do {
let data = try JSONDecoder().decode(Foo.self, from: fooBar)
print("Success:\nFound \(data.bars.count) bar(s).\n")
} catch {
print("Fail:\n\(error)\n")
}
}
}
}
}
Output:
Fail:
keyNotFound(CodingKeys(stringValue: "Bars", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"Bars\", intValue: nil) (\"Bars\").", underlyingError: nil))
Success:
Found 1 bar(s).
Success:
Found 2 bar(s).
Success:
Found 0 bar(s).
Fail:
keyNotFound(CodingKeys(stringValue: "Bars", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"Bars\", intValue: nil) (\"Bars\").", underlyingError: nil))
My apps that still reference old Codable classes with this CodingKey approach do still work. So, I suspect I must be doing something wrong in this particular example.
Can anybody please explain what I am doing wrong?
I was able to accomplish this by implementing a custom initializer like this...
This allows me to capture the values as they are being decoded and parse them to their appropriate properties. Now I can read
{"bars":[]}and"{Bars:[]}"into thebarsproperty.