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?
2
Answers
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 thebars
property.Expanding on Joel own answer. Your code will fail silently in case it doesn’t decode your json. Don’t ignore the errors using try?. You should always catch them. Btw
data(using: .utf8)
will never fail. You can safely force unwrap the result or use Data non falible initializerData($0.utf8)
A proper implementation of your custom decoder should be something like this: