First, I assume you'd really like the final result to be [Product] where Product looks like this:
struct Product {
var name: String = ""
var quantity: Int = 0
}
So if any keys are missing, they'll get the defaults. You could change this solution to throw an error in that case, but I think this approach is much nicer than Optionals.
Given that, here's how you decode it:
extension Product: Decodable {
init(from decoder: Decoder) throws {
// This is a very uniform type (all Strings). Swift can decode that
// without much help.
let container = try decoder
.singleValueContainer()
.decode([String: [[String: String]]].self)
// Extract the Products. Throwing an error here might be nicer.
let keyValues = container["Product"] ?? []
// This loops over every Key-Value element, and then loops through
// each one. We only expect one element in the second loop, though.
for keyValue in keyValues {
for (key, value) in keyValue {
switch key {
case "Name": self.name = value
case "Quantity": self.quantity = Int(value) ?? 0
default: break // Ignore unknown keys
}
}
}
// This solution just assigns defaults for missing keys, but
// you could validate that everything was found, and throw an
// error here if desired.
}
}
let data = try JSONDecoder().decode([Product].self, from: jsonData)
// [{name "exampleName", quantity 1}]
data.first!.name
// "exampleName"
In most cases the above is probably fine, but it's also very sloppy about malformed data. It just returns a default object, which could make an error very hard to track down. This example goes in the other direction, and does all the error checking you would expect from a normal Decodable.
First, we'll need a CodingKey that can accept any String. I really don't understand why this isn't built into stdlib:
struct AnyStringKey: CodingKey, Hashable, ExpressibleByStringLiteral {
var stringValue: String
init(stringValue: String) { self.stringValue = stringValue }
init(_ stringValue: String) { self.init(stringValue: stringValue) }
var intValue: Int?
init?(intValue: Int) { return nil }
init(stringLiteral value: String) { self.init(value) }
}
I also find DecodingErrors very cumbersome to build, so I often build little helper functions:
func keyNotFound(_ key: String, codingPath: [CodingKey]) -> Error {
DecodingError.keyNotFound(AnyStringKey(key),
.init(codingPath: [],
debugDescription: "\(key) key not found"))
}
func typeMismatch(_ key: String, expected: Any.Type, codingPath: [CodingKey]) -> Error {
DecodingError.typeMismatch(expected, .init(codingPath: codingPath + [AnyStringKey(key)],
debugDescription: "Expected \(expected)."))
}
With those in place, here's a more strict decoder:
extension Product: Decodable {
init(from decoder: Decoder) throws {
// This is a very uniform type (all Strings). Swift can decode that
// without much help.
let container = try decoder
.singleValueContainer()
.decode([String: [[String: String]]].self)
var codingPath: [AnyStringKey] = []
// Extract the Products. Throwing an error here might be nicer.
guard let keyValues = container["Product"] else {
throw keyNotFound("Product", codingPath: codingPath)
}
codingPath.append("Product")
var name: String?
var quantity: Int?
// This loops over every Key-Value element, and then loops through
// each one. We only expect one element in the second loop, though.
for keyValue in keyValues {
for (key, value) in keyValue {
switch key {
case "Name":
name = value
case "Quantity":
guard let intValue = Int(value) else {
throw typeMismatch("Quantity",
expected: Int.self,
codingPath: codingPath)
}
quantity = intValue
default: break // Ignore unknown keys
}
}
}
guard let name = name else {
throw keyNotFound("Name", codingPath: codingPath)
}
self.name = name
guard let quantity = quantity else {
throw keyNotFound("Quantity", codingPath: codingPath)
}
self.quantity = quantity
}
}