This one really threw me off.
I was attempting to decode some JSON with Codable
and JSONDecoder
.
import XCTest
import Foundation
final class DecodingTests: XCTestCase {
struct TestObject: Decodable, Equatable {
let value: Int
}
func testGroupResponseDecodedWithoutError() throws {
let jsonString = "{\"value\":1}"
let implicitlyUnwrappedData: Data! = jsonString.data(using: .utf8)
let concreteData: Data = jsonString.data(using: .utf8)!
// The Data instances are equivalent
XCTAssertEqual(implicitlyUnwrappedData, concreteData)
let objectFromConcreteData = try JSONDecoder().decode(TestObject.self, from: concreteData)
let objectFromImplicitData = try JSONDecoder().decode(TestObject.self, from: implicitlyUnwrappedData) // key not found: "value"
XCTAssertEqual(objectFromConcreteData.value, 1)
XCTAssertEqual(objectFromImplicitData.value, 1)
XCTAssertEqual(objectFromConcreteData, objectFromImplicitData)
}
}
Attempting to decode the object from the implicitly unwrapped Data
instance throws an error:
caught error: "keyNotFound(CodingKeys(stringValue: "value", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"value\", intValue: nil) (\"value\").", underlyingError: nil))"
Despite the underlying data being exactly the same as the other Data
instance.
When stepping through a manually implemented decode
function, the container's allKeys
property is empty.
struct TestObject: Decodable, Equatable {
let value: Int
enum CodingKeys: String, CodingKey {
case value
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
value = try container.decode(Int.self, forKey: .value)
}
}
Especially if passing a nil value into the decode
function, I would expect the Thread 1: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value
error to be thrown.
But this doesn't happen even in the following scenario:
var nilData: Data!
_ = try JSONDecoder().decode(TestObject.self, from: nilData) // key not found: "value"
Is this expected behaviour?
Maybe my understanding of implicitly unwrapped optionals is incomplete.
I thought passing in an implicitly unwrapped optional where a concrete instance is expected would implicitly unwrap it at runtime, and the decoder would be able to decode the object correctly.