From 7b4b506e25068ee91faba5b41abe6d039a394dd1 Mon Sep 17 00:00:00 2001 From: Arie Peretz Date: Wed, 1 Jul 2020 22:58:11 +0300 Subject: [PATCH 1/3] Added support for default value to overcome decoding errors when we have missing fields, null fields or key-changed fields. This support is only for decode with string key. If you like my solution then I'll make it also for AnyDateFormatter. --- Sources/Codextended/Codextended.swift | 18 +++++++ Tests/CodextendedTests/CodextendedTests.swift | 52 ++++++++++++++++++- 2 files changed, 69 insertions(+), 1 deletion(-) 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..f887ecd 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 { From 0765724315b63dae1f3f0ffcd5e1efdc5909f413 Mon Sep 17 00:00:00 2001 From: Arie Peretz Date: Wed, 1 Jul 2020 23:49:34 +0300 Subject: [PATCH 2/3] added tests to allTests --- Tests/CodextendedTests/CodextendedTests.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Tests/CodextendedTests/CodextendedTests.swift b/Tests/CodextendedTests/CodextendedTests.swift index f887ecd..7deb816 100644 --- a/Tests/CodextendedTests/CodextendedTests.swift +++ b/Tests/CodextendedTests/CodextendedTests.swift @@ -272,6 +272,8 @@ extension CodextendedTests: LinuxTestable { ("testEncodingAndDecoding", testEncodingAndDecoding), ("testDecodeIfPresent", testDecodeIfPresent), ("testDecodeIfPresentTypeMismatch", testDecodeIfPresentTypeMismatch), + ("testDecodeUsingStringAsKeyWithDefaultValueOptional",testDecodeUsingStringAsKeyWithDefaultValueOptional) + ("testDecodeUsingStringAsKeyWithDefaultValue",testDecodeUsingStringAsKeyWithDefaultValue) ("testSingleValue", testSingleValue), ("testUsingStringAsKey", testUsingStringAsKey), ("testUsingCodingKey", testUsingCodingKey), From 38d2f212ab6055a1114a785b5047078e1e287461 Mon Sep 17 00:00:00 2001 From: Arie Peretz Date: Wed, 1 Jul 2020 23:51:40 +0300 Subject: [PATCH 3/3] It's so late.... I'm dizzy --- Tests/CodextendedTests/CodextendedTests.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/CodextendedTests/CodextendedTests.swift b/Tests/CodextendedTests/CodextendedTests.swift index 7deb816..8746ac5 100644 --- a/Tests/CodextendedTests/CodextendedTests.swift +++ b/Tests/CodextendedTests/CodextendedTests.swift @@ -272,8 +272,8 @@ extension CodextendedTests: LinuxTestable { ("testEncodingAndDecoding", testEncodingAndDecoding), ("testDecodeIfPresent", testDecodeIfPresent), ("testDecodeIfPresentTypeMismatch", testDecodeIfPresentTypeMismatch), - ("testDecodeUsingStringAsKeyWithDefaultValueOptional",testDecodeUsingStringAsKeyWithDefaultValueOptional) - ("testDecodeUsingStringAsKeyWithDefaultValue",testDecodeUsingStringAsKeyWithDefaultValue) + ("testDecodeUsingStringAsKeyWithDefaultValueOptional",testDecodeUsingStringAsKeyWithDefaultValueOptional), + ("testDecodeUsingStringAsKeyWithDefaultValue",testDecodeUsingStringAsKeyWithDefaultValue), ("testSingleValue", testSingleValue), ("testUsingStringAsKey", testUsingStringAsKey), ("testUsingCodingKey", testUsingCodingKey),