diff --git a/Sources/Codextended/Codextended.swift b/Sources/Codextended/Codextended.swift index 36efad0..32016a6 100644 --- a/Sources/Codextended/Codextended.swift +++ b/Sources/Codextended/Codextended.swift @@ -85,6 +85,13 @@ public extension Data { } } +public extension KeyedDecodingContainer { + func decodeWrapper(key: K, defaultValue: T) throws -> T + where T : Decodable { + return try decodeIfPresent(T.self, forKey: key) ?? defaultValue + } +} + public extension Decoder { /// Decode a singular value from the underlying data. func decodeSingleValue(as type: T.Type = T.self) throws -> T { @@ -97,12 +104,23 @@ public extension Decoder { return try decode(AnyCodingKey(key), as: type) } + /// Decode a value for a given key, specified as a string with default value. + func decode(_ key: String, as type: T.Type = T.self, defaultValue: T) throws -> T { + return try decode(AnyCodingKey(key), as: type, defaultValue: defaultValue) + } + /// Decode a value for a given key, specified as a `CodingKey`. func decode(_ key: K, as type: T.Type = T.self) throws -> T { let container = try self.container(keyedBy: K.self) return try container.decode(type, forKey: key) } + /// Decode a value for a given key, specified as a `CodingKey` with a default value. + func decode(_ key: K, as type: T.Type = T.self, defaultValue: T) throws -> T { + let container = try self.container(keyedBy: K.self) + return try container.decodeWrapper(key: key, defaultValue: defaultValue) + } + /// Decode an optional value for a given key, specified as a string. Throws an error if the /// specified key exists but is not able to be decoded as the inferred type. func decodeIfPresent(_ key: String, as type: T.Type = T.self) throws -> T? { diff --git a/Tests/CodextendedTests/CodextendedTests.swift b/Tests/CodextendedTests/CodextendedTests.swift index c371a61..8746ac5 100644 --- a/Tests/CodextendedTests/CodextendedTests.swift +++ b/Tests/CodextendedTests/CodextendedTests.swift @@ -54,6 +54,56 @@ final class CodextendedTests: XCTestCase { } } + func testDecodeUsingStringAsKeyWithDefaultValueOptional() { + struct Value: Codable { + var string: String? + + init(string: String) { + self.string = string + } + + init(from decoder: Decoder) throws { + string = try decoder.decode("key", defaultValue: self.string) + } + + func encode(to encoder: Encoder) throws { + try encoder.encode(string, for: "key") + } + } + + let empty = "{}".data(using: .utf8)! + XCTAssertNoThrow(try empty.decoded() as Value) + let dataChangedKey = "{\"changedKey\":\"Hello, world!\"}".data(using: .utf8)! + XCTAssertNoThrow(try dataChangedKey.decoded() as Value) + let dataNull = "{\"string\":null}".data(using: .utf8)! + XCTAssertNoThrow(try dataNull.decoded() as Value) + } + + func testDecodeUsingStringAsKeyWithDefaultValue() { + struct Value: Codable { + var string: String = "Hello, world!" + + init(string: String) { + self.string = string + } + + init(from decoder: Decoder) throws { + string = try decoder.decode("key", defaultValue: self.string) + } + + func encode(to encoder: Encoder) throws { + try encoder.encode(string, for: "key") + } + } + + let empty = "{}".data(using: .utf8)! + XCTAssertNoThrow(try empty.decoded() as Value) + let dataChangedKey = "{\"changedKey\":\"Hello, world!\"}".data(using: .utf8)! + XCTAssertNoThrow(try dataChangedKey.decoded() as Value) + let dataNull = "{\"string\":null}".data(using: .utf8)! + XCTAssertNoThrow(try dataNull.decoded() as Value) + } + func testSingleValue() throws { struct Value: Codable, Equatable { let string: String @@ -99,7 +149,7 @@ final class CodextendedTests: XCTestCase { let valueB = try data.decoded() as Value XCTAssertEqual(valueA, valueB) } - + func testUsingCodingKey() throws { struct Value: Codable, Equatable { enum CodingKeys: CodingKey { @@ -222,6 +272,8 @@ extension CodextendedTests: LinuxTestable { ("testEncodingAndDecoding", testEncodingAndDecoding), ("testDecodeIfPresent", testDecodeIfPresent), ("testDecodeIfPresentTypeMismatch", testDecodeIfPresentTypeMismatch), + ("testDecodeUsingStringAsKeyWithDefaultValueOptional",testDecodeUsingStringAsKeyWithDefaultValueOptional), + ("testDecodeUsingStringAsKeyWithDefaultValue",testDecodeUsingStringAsKeyWithDefaultValue), ("testSingleValue", testSingleValue), ("testUsingStringAsKey", testUsingStringAsKey), ("testUsingCodingKey", testUsingCodingKey),