From df8cca5a1d36cd02fc454aadc134ae415f3cd379 Mon Sep 17 00:00:00 2001 From: Dusan Tadic Date: Tue, 25 Jun 2019 22:53:45 +0200 Subject: [PATCH 1/2] Add nested decoding --- Sources/Codextended/Codextended.swift | 39 +++++++++++++ Tests/CodextendedTests/CodextendedTests.swift | 57 ++++++++++++++++++- 2 files changed, 95 insertions(+), 1 deletion(-) diff --git a/Sources/Codextended/Codextended.swift b/Sources/Codextended/Codextended.swift index 36efad0..4722c09 100644 --- a/Sources/Codextended/Codextended.swift +++ b/Sources/Codextended/Codextended.swift @@ -103,6 +103,32 @@ public extension Decoder { return try container.decode(type, forKey: key) } + /// Decode a nested value for array of keys, specified as a `CodingKey`. + /// Throws an error if keys array is empty + func decode(_ keys: [CodingKey], as type: T.Type = T.self) throws -> T { + + // Throw an error here? + guard !keys.isEmpty else { + throw CodextendedDecodingError.emptyCodingKey + } + assert(!keys.isEmpty, "Can't decode with empty keys") + + let keys = keys.map({AnyCodingKey($0.stringValue)}) + + var container = try self.container(keyedBy: AnyCodingKey.self) + for key in keys.dropLast() { + container = try container.nestedContainer(keyedBy: AnyCodingKey.self, forKey: key) + } + return try container.decode(type, forKey: keys.last!) + } + + /// Decode a nested value for array of keys, specified as a string. + /// Throws an error if keys array is empty + func decode(_ keys: [String], as type: T.Type = T.self) throws -> T { + return try decode(keys.map({AnyCodingKey($0)})) + } + + /// 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? { @@ -142,6 +168,19 @@ public extension Decoder { } } +// MARK: - Errors + +public enum CodextendedDecodingError: Error, LocalizedError { + case emptyCodingKey + + public var errorDescription: String? { + switch self { + case .emptyCodingKey: + return "Coding keys array was empty" + } + } +} + // MARK: - Date formatters /// Protocol acting as a common API for all types of date formatters, diff --git a/Tests/CodextendedTests/CodextendedTests.swift b/Tests/CodextendedTests/CodextendedTests.swift index c371a61..35fa8c4 100644 --- a/Tests/CodextendedTests/CodextendedTests.swift +++ b/Tests/CodextendedTests/CodextendedTests.swift @@ -212,6 +212,58 @@ final class CodextendedTests: XCTestCase { } } + func testDecodingNested() throws { + struct Value: Decodable, Equatable { + let string: String + + init(from decoder: Decoder) throws { + string = try decoder.decode(["a","b","c"]) + } + } + + let jsonString = #""" + { + "a": { + "b": { + "c": "hello world" + } + } + } + """# + + let value = try Data(jsonString.utf8).decoded() as Value + XCTAssertEqual(value.string, "hello world") + } + + func testDecodingSingleValueNested() throws { + struct Value: Decodable, Equatable { + let string: String + + init(from decoder: Decoder) throws { + string = try decoder.decode(["a"]) + } + } + + let value = try Data(#"{"a": "hello world"}"#.utf8).decoded() as Value + XCTAssertEqual(value.string, "hello world") + } + + func testDecodingNestedWithEmptyKeysThrows() { + struct Value: Decodable, Equatable { + let string: String + + init(from decoder: Decoder) throws { + string = try decoder.decode([String]()) + } + } + + let data = Data(#"{"a": "hello world"}"#.utf8) + XCTAssertThrowsError(try data.decoded() as Value) { error in + XCTAssertEqual(error as? CodextendedDecodingError, CodextendedDecodingError.emptyCodingKey, + "Expected CodextendedDecodingError.emptyCodingKey but got \(error)") + } + } + func testAllTestsRunOnLinux() { verifyAllTestsRunOnLinux(excluding: ["testDateWithISO8601Formatter"]) } @@ -226,6 +278,9 @@ extension CodextendedTests: LinuxTestable { ("testUsingStringAsKey", testUsingStringAsKey), ("testUsingCodingKey", testUsingCodingKey), ("testDateWithCustomFormatter", testDateWithCustomFormatter), - ("testDecodingErrorThrownForInvalidDateString", testDecodingErrorThrownForInvalidDateString) + ("testDecodingErrorThrownForInvalidDateString", testDecodingErrorThrownForInvalidDateString), + ("testDecodingNested", testDecodingNested), + ("testDecodingSingleValueNested", testDecodingSingleValueNested), + ("testDecodingNestedWithEmptyKeysThrows", testDecodingNestedWithEmptyKeysThrows) ] } From f0adb0b26f57e6f078572bd619accc667de01a68 Mon Sep 17 00:00:00 2001 From: Dusan Tadic Date: Tue, 25 Jun 2019 23:27:36 +0200 Subject: [PATCH 2/2] Remove unnecessary assert --- Sources/Codextended/Codextended.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Sources/Codextended/Codextended.swift b/Sources/Codextended/Codextended.swift index 4722c09..33ad535 100644 --- a/Sources/Codextended/Codextended.swift +++ b/Sources/Codextended/Codextended.swift @@ -111,7 +111,6 @@ public extension Decoder { guard !keys.isEmpty else { throw CodextendedDecodingError.emptyCodingKey } - assert(!keys.isEmpty, "Can't decode with empty keys") let keys = keys.map({AnyCodingKey($0.stringValue)})