First, PHP doesn't need to do this, so if possible the PHP should be corrected. The way to express "empty object" in PHP is new \stdClass(). Elastic has a nice explanation.
That said, if you cannot correct the server, you can fix it on the client side. Many of the answers here are based on trying to decode the value, and if failing assuming it's an empty array. That works, but it means that unexpected JSON will not generate good errors. Instead, I'd extract this problem into a function:
/// Some PHP developers emit [] to indicate empty object rather than using stdClass().
/// This returns a default value in that case.
extension KeyedDecodingContainer {
func decodePHPObject<T>(_ type: T.Type, forKey key: Key, defaultValue: T) throws -> T
where T : Decodable
{
// Sadly neither Void nor Never conform to Decodable, so we have to pick a random type here, String.
// The nicest would be to decode EmptyCollection, but that also doesn't conform to Decodable.
if let emptyArray = try? decode([String].self, forKey: key), emptyArray.isEmpty {
return defaultValue
}
return try decode(T.self, forKey: key)
}
// Special case the common dictionary situation.
func decodePHPObject<K, V>(_ type: [K: V].Type, forKey key: Key) throws -> [K: V]
where K: Codable, V: Codable
{
return try decodePHPObject([K:V].self, forKey: key, defaultValue: [:])
}
}
This provides a .decodePHPObject(_:forKey:) method that you can the use in a custom decoder.
public struct ErrorValue: Codable {
public let code: String
public let message: String
public let params: [String: String]
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.code = try container.decode(String.self, forKey: .code)
self.message = try container.decode(String.self, forKey: .message)
// And use our new decodePHPObject.
self.params = try container.decodePHPObject([String: String].self, forKey: .params)
}
}
(I've renamed this ErrorValue to remove the conflict with the stdlib type Error, and I've made params non-optional since you generally should not have optional collections unless "empty" is going to be treated differently than nil.)
paramsas dictionary or array?