From 6f821a3f780a99f5635e2e1bd38a13709489cbdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8B=E1=85=B5=E1=84=87=E1=85=A7=E1=86=BC=E1=84=8E?= =?UTF-8?q?=E1=85=A1=E1=86=AB?= Date: Fri, 6 Nov 2020 18:33:34 +0900 Subject: [PATCH] Add `PropertyWrapper` and there's test codes --- AnyCodable.xcodeproj/project.pbxproj | 24 +++ .../DecodingContainer+AnyCollection.swift | 4 +- .../EncodingContainer+AnyCollection.swift | 4 +- AnyCodable/PropertyWrapper.swift | 47 +++++ AnyCodable/PropertyWrapperIfPresent.swift | 63 +++++++ ...leIfPresentObjectWithPropertyWrapper.swift | 20 +++ ...MockCodableObjectWithPropertyWrapper.swift | 20 +++ AnyCodableTests/PropertyWrapper+Decode.swift | 162 +++++++++++++++++ AnyCodableTests/PropertyWrapper+Encode.swift | 165 ++++++++++++++++++ 9 files changed, 505 insertions(+), 4 deletions(-) create mode 100644 AnyCodable/PropertyWrapper.swift create mode 100644 AnyCodable/PropertyWrapperIfPresent.swift create mode 100644 AnyCodableTests/MockCodableIfPresentObjectWithPropertyWrapper.swift create mode 100644 AnyCodableTests/MockCodableObjectWithPropertyWrapper.swift create mode 100644 AnyCodableTests/PropertyWrapper+Decode.swift create mode 100644 AnyCodableTests/PropertyWrapper+Encode.swift diff --git a/AnyCodable.xcodeproj/project.pbxproj b/AnyCodable.xcodeproj/project.pbxproj index eff71f6..fb0a7ae 100644 --- a/AnyCodable.xcodeproj/project.pbxproj +++ b/AnyCodable.xcodeproj/project.pbxproj @@ -16,6 +16,12 @@ 8D746F0121F2F2A900C0A07C /* MockCodableIfPresentObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D746EFC21F2F2A900C0A07C /* MockCodableIfPresentObject.swift */; }; 8D746F0221F2F2A900C0A07C /* MockCodableObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D746EFD21F2F2A900C0A07C /* MockCodableObject.swift */; }; 8D746F0321F2F2A900C0A07C /* DecodingContainer+AnyCollectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D746EFE21F2F2A900C0A07C /* DecodingContainer+AnyCollectionTests.swift */; }; + C931FCEF255533A9008474BB /* PropertyWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C931FCEE255533A9008474BB /* PropertyWrapper.swift */; }; + C931FCF7255537E7008474BB /* PropertyWrapper+Decode.swift in Sources */ = {isa = PBXBuildFile; fileRef = C931FCF6255537E7008474BB /* PropertyWrapper+Decode.swift */; }; + C931FCFD25553867008474BB /* MockCodableObjectWithPropertyWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C931FCFC25553867008474BB /* MockCodableObjectWithPropertyWrapper.swift */; }; + C931FD0925553BD7008474BB /* PropertyWrapper+Encode.swift in Sources */ = {isa = PBXBuildFile; fileRef = C931FD0825553BD7008474BB /* PropertyWrapper+Encode.swift */; }; + C931FD0F25553C7E008474BB /* MockCodableIfPresentObjectWithPropertyWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C931FD0E25553C7E008474BB /* MockCodableIfPresentObjectWithPropertyWrapper.swift */; }; + C931FD1325553D17008474BB /* PropertyWrapperIfPresent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C931FD1225553D17008474BB /* PropertyWrapperIfPresent.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -41,6 +47,12 @@ 8D746EFC21F2F2A900C0A07C /* MockCodableIfPresentObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockCodableIfPresentObject.swift; sourceTree = ""; }; 8D746EFD21F2F2A900C0A07C /* MockCodableObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockCodableObject.swift; sourceTree = ""; }; 8D746EFE21F2F2A900C0A07C /* DecodingContainer+AnyCollectionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DecodingContainer+AnyCollectionTests.swift"; sourceTree = ""; }; + C931FCEE255533A9008474BB /* PropertyWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PropertyWrapper.swift; sourceTree = ""; }; + C931FCF6255537E7008474BB /* PropertyWrapper+Decode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PropertyWrapper+Decode.swift"; sourceTree = ""; }; + C931FCFC25553867008474BB /* MockCodableObjectWithPropertyWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockCodableObjectWithPropertyWrapper.swift; sourceTree = ""; }; + C931FD0825553BD7008474BB /* PropertyWrapper+Encode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PropertyWrapper+Encode.swift"; sourceTree = ""; }; + C931FD0E25553C7E008474BB /* MockCodableIfPresentObjectWithPropertyWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockCodableIfPresentObjectWithPropertyWrapper.swift; sourceTree = ""; }; + C931FD1225553D17008474BB /* PropertyWrapperIfPresent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PropertyWrapperIfPresent.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -86,6 +98,8 @@ 8D746EDF21F2F28C00C0A07C /* AnyCodable.h */, 8D746EF721F2F29B00C0A07C /* DecodingContainer+AnyCollection.swift */, 8D746EF621F2F29B00C0A07C /* EncodingContainer+AnyCollection.swift */, + C931FCEE255533A9008474BB /* PropertyWrapper.swift */, + C931FD1225553D17008474BB /* PropertyWrapperIfPresent.swift */, 8D746EE021F2F28C00C0A07C /* Info.plist */, ); path = AnyCodable; @@ -97,8 +111,12 @@ 8D746EFA21F2F2A900C0A07C /* AnyCodingKeyTests.swift */, 8D746EFE21F2F2A900C0A07C /* DecodingContainer+AnyCollectionTests.swift */, 8D746EFB21F2F2A900C0A07C /* EncodingContainer+AnyCollectionTests.swift */, + C931FCF6255537E7008474BB /* PropertyWrapper+Decode.swift */, + C931FD0825553BD7008474BB /* PropertyWrapper+Encode.swift */, 8D746EFC21F2F2A900C0A07C /* MockCodableIfPresentObject.swift */, 8D746EFD21F2F2A900C0A07C /* MockCodableObject.swift */, + C931FD0E25553C7E008474BB /* MockCodableIfPresentObjectWithPropertyWrapper.swift */, + C931FCFC25553867008474BB /* MockCodableObjectWithPropertyWrapper.swift */, 8D746EEC21F2F28D00C0A07C /* Info.plist */, ); path = AnyCodableTests; @@ -214,7 +232,9 @@ buildActionMask = 2147483647; files = ( 8D746EF821F2F29C00C0A07C /* EncodingContainer+AnyCollection.swift in Sources */, + C931FCEF255533A9008474BB /* PropertyWrapper.swift in Sources */, 8D746EF921F2F29C00C0A07C /* DecodingContainer+AnyCollection.swift in Sources */, + C931FD1325553D17008474BB /* PropertyWrapperIfPresent.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -224,8 +244,12 @@ files = ( 8D746F0221F2F2A900C0A07C /* MockCodableObject.swift in Sources */, 8D746F0121F2F2A900C0A07C /* MockCodableIfPresentObject.swift in Sources */, + C931FCFD25553867008474BB /* MockCodableObjectWithPropertyWrapper.swift in Sources */, + C931FCF7255537E7008474BB /* PropertyWrapper+Decode.swift in Sources */, + C931FD0F25553C7E008474BB /* MockCodableIfPresentObjectWithPropertyWrapper.swift in Sources */, 8D746EFF21F2F2A900C0A07C /* AnyCodingKeyTests.swift in Sources */, 8D746F0021F2F2A900C0A07C /* EncodingContainer+AnyCollectionTests.swift in Sources */, + C931FD0925553BD7008474BB /* PropertyWrapper+Encode.swift in Sources */, 8D746F0321F2F2A900C0A07C /* DecodingContainer+AnyCollectionTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/AnyCodable/DecodingContainer+AnyCollection.swift b/AnyCodable/DecodingContainer+AnyCollection.swift index 129a938..0e3cb34 100644 --- a/AnyCodable/DecodingContainer+AnyCollection.swift +++ b/AnyCodable/DecodingContainer+AnyCollection.swift @@ -96,7 +96,7 @@ extension KeyedDecodingContainer { } } -private extension KeyedDecodingContainer { +internal extension KeyedDecodingContainer { func decode(_ type: [String: Any].Type) throws -> [String: Any] { var dictionary: [String: Any] = [:] for key in allKeys { @@ -120,7 +120,7 @@ private extension KeyedDecodingContainer { } } -private extension UnkeyedDecodingContainer { +internal extension UnkeyedDecodingContainer { mutating func decode(_ type: [Any].Type) throws -> [Any] { var elements: [Any] = [] while !isAtEnd { diff --git a/AnyCodable/EncodingContainer+AnyCollection.swift b/AnyCodable/EncodingContainer+AnyCollection.swift index b08f5bd..d170e87 100644 --- a/AnyCodable/EncodingContainer+AnyCollection.swift +++ b/AnyCodable/EncodingContainer+AnyCollection.swift @@ -62,7 +62,7 @@ extension KeyedEncodingContainer { } } -private extension KeyedEncodingContainer where K == AnyCodingKey { +internal extension KeyedEncodingContainer where K == AnyCodingKey { mutating func encode(_ value: [String: Any]) throws { for (k, v) in value { let key = AnyCodingKey(stringValue: k)! @@ -89,7 +89,7 @@ private extension KeyedEncodingContainer where K == AnyCodingKey { } } -private extension UnkeyedEncodingContainer { +internal extension UnkeyedEncodingContainer { /// Encodes the given value. /// /// - parameter value: The value to encode. diff --git a/AnyCodable/PropertyWrapper.swift b/AnyCodable/PropertyWrapper.swift new file mode 100644 index 0000000..9078236 --- /dev/null +++ b/AnyCodable/PropertyWrapper.swift @@ -0,0 +1,47 @@ +// +// PropertyWrapper.swift +// AnyCodable +// +// Created by 이병찬 on 2020/11/06. +// Copyright © 2020 levantAJ. All rights reserved. +// + +import Foundation + +@propertyWrapper +public struct CodedAnyArray: Codable { + public var wrappedValue: [Any] + + public init(wrappedValue: [Any]) { + self.wrappedValue = wrappedValue + } + + public init(from decoder: Decoder) throws { + var values = try decoder.unkeyedContainer() + self.wrappedValue = try values.decode([Any].self) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.unkeyedContainer() + try container.encode(wrappedValue) + } +} + +@propertyWrapper +public struct CodedAnyDictonary: Codable { + public var wrappedValue: [String: Any] + + public init(wrappedValue: [String: Any]) { + self.wrappedValue = wrappedValue + } + + public init(from decoder: Decoder) throws { + let values = try decoder.container(keyedBy: AnyCodingKey.self) + self.wrappedValue = try values.decode([String: Any].self) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: AnyCodingKey.self) + try container.encode(wrappedValue) + } +} diff --git a/AnyCodable/PropertyWrapperIfPresent.swift b/AnyCodable/PropertyWrapperIfPresent.swift new file mode 100644 index 0000000..1f3c26f --- /dev/null +++ b/AnyCodable/PropertyWrapperIfPresent.swift @@ -0,0 +1,63 @@ +// +// PropertyWrapperIfPresent.swift +// AnyCodable +// +// Created by 이병찬 on 2020/11/06. +// Copyright © 2020 levantAJ. All rights reserved. +// + +import Foundation + +@propertyWrapper +public struct CodedIfPresentAnyArray: Codable { + public var wrappedValue: [Any]? + + public init(wrappedValue: [Any]?) { + self.wrappedValue = wrappedValue + } + + public init(from decoder: Decoder) throws { + if var values = try? decoder.unkeyedContainer() { + self.wrappedValue = try values.decode([Any].self) + } else { + self.wrappedValue = nil + } + } + + public func encode(to encoder: Encoder) throws { + if let value = wrappedValue { + var container = encoder.unkeyedContainer() + try container.encode(value) + } else { + var container = encoder.singleValueContainer() + try container.encodeNil() + } + } +} + +@propertyWrapper +public struct CodedIfPresentAnyDictonary: Codable { + public var wrappedValue: [String: Any]? + + public init(wrappedValue: [String: Any]?) { + self.wrappedValue = wrappedValue + } + + public init(from decoder: Decoder) throws { + if let values = try? decoder.container(keyedBy: AnyCodingKey.self) { + self.wrappedValue = try values.decode([String: Any].self) + } else { + self.wrappedValue = nil + } + } + + public func encode(to encoder: Encoder) throws { + if let value = wrappedValue { + var container = encoder.container(keyedBy: AnyCodingKey.self) + try container.encode(value) + } else { + var container = encoder.singleValueContainer() + try container.encodeNil() + } + } +} diff --git a/AnyCodableTests/MockCodableIfPresentObjectWithPropertyWrapper.swift b/AnyCodableTests/MockCodableIfPresentObjectWithPropertyWrapper.swift new file mode 100644 index 0000000..5107a8c --- /dev/null +++ b/AnyCodableTests/MockCodableIfPresentObjectWithPropertyWrapper.swift @@ -0,0 +1,20 @@ +// +// MockCodableIfPresentObjectWithPropertyWrapper.swift +// AnyCodableTests +// +// Created by 이병찬 on 2020/11/06. +// Copyright © 2020 levantAJ. All rights reserved. +// + +import Foundation +@testable import AnyCodable + +struct MockCodableIfPresentObjectWithPropertyWrapper: Codable { + @CodedIfPresentAnyDictonary var dict: [String: Any]? + @CodedIfPresentAnyArray var array: [Any]? + + init(dict: [String: Any]?, array: [Any]?) { + self.dict = dict + self.array = array + } +} diff --git a/AnyCodableTests/MockCodableObjectWithPropertyWrapper.swift b/AnyCodableTests/MockCodableObjectWithPropertyWrapper.swift new file mode 100644 index 0000000..566aba2 --- /dev/null +++ b/AnyCodableTests/MockCodableObjectWithPropertyWrapper.swift @@ -0,0 +1,20 @@ +// +// MockCodableObjectWithPropertyWrapper.swift +// AnyCodableTests +// +// Created by 이병찬 on 2020/11/06. +// Copyright © 2020 levantAJ. All rights reserved. +// + +import Foundation +@testable import AnyCodable + +struct MockCodableObjectWithPropertyWrapper: Codable { + @CodedAnyDictonary private(set) var dict: [String: Any] + @CodedAnyArray private(set) var array: [Any] + + init(dict: [String: Any], array: [Any]) { + self.dict = dict + self.array = array + } +} diff --git a/AnyCodableTests/PropertyWrapper+Decode.swift b/AnyCodableTests/PropertyWrapper+Decode.swift new file mode 100644 index 0000000..cdb3cae --- /dev/null +++ b/AnyCodableTests/PropertyWrapper+Decode.swift @@ -0,0 +1,162 @@ +// +// PropertyWrapper+Decode.swift +// AnyCodableTests +// +// Created by 이병찬 on 2020/11/06. +// Copyright © 2020 levantAJ. All rights reserved. +// + +import XCTest + +class PropertyWrapper_DecodeTests: XCTestCase { + func testDecodeDict() { + let data = """ + {"dict": {"int": 123, "string": "string", "bool": true, "double": 873.436, "null":null, "array": [1, 2, 3, 4], "dict": {"string": "ok lah", "null": null}}, "array": [1, null, false, "string", 345.2346, ["1", "2", "4"], {"key": null}]} + """.data(using: .utf8)! + + var sut: MockCodableObjectWithPropertyWrapper! + XCTAssertNoThrow(sut = try JSONDecoder().decode(MockCodableObjectWithPropertyWrapper.self, from: data)) + XCTAssertEqual(sut.dict["int"] as? Int, 123) + XCTAssertEqual(sut.dict["string"] as? String, "string") + XCTAssertEqual(sut.dict["bool"] as? Bool, true) + XCTAssertEqual(sut.dict["double"] as? Double, 873.436) + XCTAssertTrue(sut.dict["null"] is NSNull) + XCTAssertEqual(sut.dict["array"] as? [Int], [1, 2, 3, 4]) + XCTAssertEqual((sut.dict["dict"] as? [String: Any])?["string"] as? String, "ok lah") + XCTAssertEqual((sut.dict["dict"] as? [String: Any])?["null"] is NSNull, true) + + let array = sut.array + XCTAssertNotNil(array) + XCTAssertEqual(array.count, 7) + XCTAssertTrue(array.contains { $0 is NSNull }) + XCTAssertTrue(array.contains { $0 as? Int == 1 }) + XCTAssertTrue(array.contains { $0 as? String == "string" }) + XCTAssertTrue(array.contains { $0 as? Bool == false }) + XCTAssertTrue(array.contains { $0 as? Double == 345.2346 }) + XCTAssertTrue(array.contains { $0 as? [String] == ["1", "2", "4"] }) + XCTAssertTrue(array.contains(where: { dict -> Bool in + let dict = dict as? [String: Any] + return dict?["key"] is NSNull + })) + } + + func testDecodeDictContainsDict() { + let data = """ + {"dict": {"nestedDict": {"array":[123, null, true, 456.789, "string", [1, 2, 3], {"string":"string", "int": 456, "array": ["9", "8", "7"]}]}}, "array": [1, null, false, "string", 345.2346, ["1", "2", "4"], {"key": null}]} +""".data(using: .utf8)! + var sut: MockCodableObjectWithPropertyWrapper! + XCTAssertNoThrow(sut = try JSONDecoder().decode(MockCodableObjectWithPropertyWrapper.self, from: data)) + let nestedDict = sut.dict["nestedDict"] as? [String: Any] + let nestedArray = nestedDict?["array"] as? [Any] ?? [] + XCTAssertEqual(nestedArray.count, 7) + XCTAssertTrue(nestedArray.contains { $0 is NSNull }) + XCTAssertTrue(nestedArray.contains { $0 as? Int == 123 }) + XCTAssertTrue(nestedArray.contains { $0 as? String == "string" }) + XCTAssertTrue(nestedArray.contains { $0 as? Bool == true }) + XCTAssertTrue(nestedArray.contains { $0 as? Double == 456.789 }) + XCTAssertTrue(nestedArray.contains { $0 as? [Int] == [1, 2, 3] }) + XCTAssertTrue(nestedArray.contains(where: { dict -> Bool in + let dict = dict as? [String: Any] + return dict?["string"] as? String == "string" + && dict?["int"] as? Int == 456 + && dict?["array"] as? [String] == ["9", "8", "7"] + })) + + let array = sut.array + XCTAssertNotNil(array) + XCTAssertEqual(array.count, 7) + XCTAssertTrue(array.contains { $0 is NSNull }) + XCTAssertTrue(array.contains { $0 as? Int == 1 }) + XCTAssertTrue(array.contains { $0 as? String == "string" }) + XCTAssertTrue(array.contains { $0 as? Bool == false }) + XCTAssertTrue(array.contains { $0 as? Double == 345.2346 }) + XCTAssertTrue(array.contains { $0 as? [String] == ["1", "2", "4"] }) + XCTAssertTrue(array.contains(where: { dict -> Bool in + let dict = dict as? [String: Any] + return dict?["key"] is NSNull + })) + } + + func testDecodeDictContainsArray() { + let data = """ + {"dict": {"array": [123, null, true, 456.789, "string", [1, 2, 3], {"string":"string", "int": 456, "array": ["9", "8", "7"]}]}, "array": [1, null, false, "string", 345.2346, ["1", "2", "4"], {"key": null}]} +""".data(using: .utf8)! + var sut: MockCodableObjectWithPropertyWrapper! + XCTAssertNoThrow(sut = try JSONDecoder().decode(MockCodableObjectWithPropertyWrapper.self, from: data)) + let nestedArray = sut.dict["array"] as? [Any] ?? [] + XCTAssertEqual(nestedArray.count, 7) + XCTAssertTrue(nestedArray.contains { $0 is NSNull }) + XCTAssertTrue(nestedArray.contains { $0 as? Int == 123 }) + XCTAssertTrue(nestedArray.contains { $0 as? String == "string" }) + XCTAssertTrue(nestedArray.contains { $0 as? Bool == true }) + XCTAssertTrue(nestedArray.contains { $0 as? Double == 456.789 }) + XCTAssertTrue(nestedArray.contains { $0 as? [Int] == [1, 2, 3] }) + XCTAssertTrue(nestedArray.contains(where: { dict -> Bool in + let dict = dict as? [String: Any] + return dict?["string"] as? String == "string" + && dict?["int"] as? Int == 456 + && dict?["array"] as? [String] == ["9", "8", "7"] + })) + + let array = sut.array + XCTAssertNotNil(array) + XCTAssertEqual(array.count, 7) + XCTAssertTrue(array.contains { $0 is NSNull }) + XCTAssertTrue(array.contains { $0 as? Int == 1 }) + XCTAssertTrue(array.contains { $0 as? String == "string" }) + XCTAssertTrue(array.contains { $0 as? Bool == false }) + XCTAssertTrue(array.contains { $0 as? Double == 345.2346 }) + XCTAssertTrue(array.contains { $0 as? [String] == ["1", "2", "4"] }) + XCTAssertTrue(array.contains(where: { dict -> Bool in + let dict = dict as? [String: Any] + return dict?["key"] is NSNull + })) + } + + func testDecodeIfPresentDict() { + let data = """ +{"dict": {"int": 123, "string": "cool", "bool": true, "double": 873.436, "null": null, "array": [1, 2, 3, 4], "dict": {"string": "ok lah", "null": null}}, \"array\": null} +""".data(using: .utf8)! + + var sut: MockCodableIfPresentObjectWithPropertyWrapper! + XCTAssertNoThrow(sut = try JSONDecoder().decode(MockCodableIfPresentObjectWithPropertyWrapper.self, from: data)) + XCTAssertEqual(sut.dict?["int"] as? Int, 123) + XCTAssertEqual(sut.dict?["string"] as? String, "cool") + XCTAssertEqual(sut.dict?["bool"] as? Bool, true) + XCTAssertEqual(sut.dict?["double"] as? Double, 873.436) + XCTAssertTrue(sut.dict?["null"] is NSNull) + XCTAssertEqual(sut.dict?["array"] as? [Int], [1, 2, 3, 4]) + XCTAssertEqual((sut.dict?["dict"] as? [String: Any])?["string"] as? String, "ok lah") + } + + func testDecodeIfPresentDictIsNull() { + let data = "{\"dict\": null, \"array\": null}".data(using: .utf8)! + var sut: MockCodableIfPresentObjectWithPropertyWrapper! + XCTAssertNoThrow(sut = try JSONDecoder().decode(MockCodableIfPresentObjectWithPropertyWrapper.self, from: data)) + XCTAssertNil(sut.dict) + XCTAssertNil(sut.array) + } + + func testDecodeIfPresentArray() { + let data = """ +{\"array\": [123, null, true, 456.789, "string", [1, 2, 3], {"string":"string", "int": 456, "array": ["9", "8", "7"]}], \"dict\": null} +""".data(using: .utf8)! + + var sut: MockCodableIfPresentObjectWithPropertyWrapper! + XCTAssertNoThrow(sut = try JSONDecoder().decode(MockCodableIfPresentObjectWithPropertyWrapper.self, from: data)) + let array = sut.array + XCTAssertEqual(array?.count, 7) + XCTAssertEqual(array?.contains { $0 is NSNull }, true) + XCTAssertEqual(array?.contains { $0 as? Int == 123 }, true) + XCTAssertEqual(array?.contains { $0 as? String == "string" }, true) + XCTAssertEqual(array?.contains { $0 as? Bool == true }, true) + XCTAssertEqual(array?.contains { $0 as? Double == 456.789 }, true) + XCTAssertEqual(array?.contains { $0 as? [Int] == [1, 2, 3] }, true) + XCTAssertEqual(array?.contains(where: { dict -> Bool in + let dict = dict as? [String: Any] + return dict?["string"] as? String == "string" + && dict?["int"] as? Int == 456 + && dict?["array"] as? [String] == ["9", "8", "7"] + }), true) + } +} diff --git a/AnyCodableTests/PropertyWrapper+Encode.swift b/AnyCodableTests/PropertyWrapper+Encode.swift new file mode 100644 index 0000000..ad4b654 --- /dev/null +++ b/AnyCodableTests/PropertyWrapper+Encode.swift @@ -0,0 +1,165 @@ +// +// PropertyWrapper+Encode.swift +// AnyCodableTests +// +// Created by 이병찬 on 2020/11/06. +// Copyright © 2020 levantAJ. All rights reserved. +// + +import XCTest + +class PropertyWrapper_EncodeTests: XCTestCase { + func testEncodeDict() { + let expectedSut = MockCodableObjectWithPropertyWrapper(dict: ["int": 123, "string": "string", "bool": true, "double": 873.436, "null": NSNull(), "array": [1, 2, 3, 4], "dict": ["string": "ok lah", "null": NSNull()]], + array: [1, NSNull(), false, "string", 345.2346, ["1", "2", "4"], ["key": NSNull()]]) + var data: Data! + var sut: MockCodableObjectWithPropertyWrapper! + XCTAssertNoThrow(data = try JSONEncoder().encode(expectedSut)) + XCTAssertNoThrow(sut = try JSONDecoder().decode(MockCodableObjectWithPropertyWrapper.self, from: data)) + XCTAssertEqual(sut.dict["int"] as? Int, 123) + XCTAssertEqual(sut.dict["string"] as? String, "string") + XCTAssertEqual(sut.dict["bool"] as? Bool, true) + XCTAssertEqual(sut.dict["double"] as? Double, 873.436) + XCTAssertTrue(sut.dict["null"] is NSNull) + XCTAssertEqual(sut.dict["array"] as? [Int], [1, 2, 3, 4]) + XCTAssertEqual((sut.dict["dict"] as? [String: Any])?["string"] as? String, "ok lah") + XCTAssertEqual((sut.dict["dict"] as? [String: Any])?["null"] is NSNull, true) + + let array = sut.array + XCTAssertNotNil(array) + XCTAssertEqual(array.count, 7) + XCTAssertTrue(array.contains { $0 is NSNull }) + XCTAssertTrue(array.contains { $0 as? Int == 1 }) + XCTAssertTrue(array.contains { $0 as? String == "string" }) + XCTAssertTrue(array.contains { $0 as? Bool == false }) + XCTAssertTrue(array.contains { $0 as? Double == 345.2346 }) + XCTAssertTrue(array.contains { $0 as? [String] == ["1", "2", "4"] }) + XCTAssertTrue(array.contains(where: { dict -> Bool in + let dict = dict as? [String: Any] + return dict?["key"] is NSNull + })) + } + + func testEncodeDictContainsDict() { + let expectedSut = MockCodableObjectWithPropertyWrapper(dict: ["nestedDict": ["array":[123, NSNull(), true, 456.789, "string", [1, 2, 3], ["string": "string", "int": 456, "array": ["9", "8", "7"]]]]], + array: [1, NSNull(), false, "string", 345.2346, ["1", "2", "4"], ["key": NSNull()]]) + var data: Data! + var sut: MockCodableObjectWithPropertyWrapper! + XCTAssertNoThrow(data = try JSONEncoder().encode(expectedSut)) + XCTAssertNoThrow(sut = try JSONDecoder().decode(MockCodableObjectWithPropertyWrapper.self, from: data)) + let nestedDict = sut.dict["nestedDict"] as? [String: Any] + let nestedArray = nestedDict?["array"] as? [Any] ?? [] + XCTAssertEqual(nestedArray.count, 7) + XCTAssertTrue(nestedArray.contains { $0 is NSNull }) + XCTAssertTrue(nestedArray.contains { $0 as? Int == 123 }) + XCTAssertTrue(nestedArray.contains { $0 as? String == "string" }) + XCTAssertTrue(nestedArray.contains { $0 as? Bool == true }) + XCTAssertTrue(nestedArray.contains { $0 as? Double == 456.789 }) + XCTAssertTrue(nestedArray.contains { $0 as? [Int] == [1, 2, 3] }) + XCTAssertTrue(nestedArray.contains(where: { dict -> Bool in + let dict = dict as? [String: Any] + return dict?["string"] as? String == "string" + && dict?["int"] as? Int == 456 + && dict?["array"] as? [String] == ["9", "8", "7"] + })) + + let array = sut.array + XCTAssertNotNil(array) + XCTAssertEqual(array.count, 7) + XCTAssertTrue(array.contains { $0 is NSNull }) + XCTAssertTrue(array.contains { $0 as? Int == 1 }) + XCTAssertTrue(array.contains { $0 as? String == "string" }) + XCTAssertTrue(array.contains { $0 as? Bool == false }) + XCTAssertTrue(array.contains { $0 as? Double == 345.2346 }) + XCTAssertTrue(array.contains { $0 as? [String] == ["1", "2", "4"] }) + XCTAssertTrue(array.contains(where: { dict -> Bool in + let dict = dict as? [String: Any] + return dict?["key"] is NSNull + })) + } + + func testEncodeDictContainsArray() { + let expectedSut = MockCodableObjectWithPropertyWrapper(dict: ["array": [123, NSNull(), true, 456.789, "string", [1, 2, 3], ["string": "string", "int": 456, "array": ["9", "8", "7"]]]], + array: [1, NSNull(), false, "string", 345.2346, ["1", "2", "4"], ["key": NSNull()]]) + var data: Data! + var sut: MockCodableObjectWithPropertyWrapper! + XCTAssertNoThrow(data = try JSONEncoder().encode(expectedSut)) + XCTAssertNoThrow(sut = try JSONDecoder().decode(MockCodableObjectWithPropertyWrapper.self, from: data)) + let nestedArray = sut.dict["array"] as? [Any] ?? [] + XCTAssertEqual(nestedArray.count, 7) + XCTAssertTrue(nestedArray.contains { $0 is NSNull }) + XCTAssertTrue(nestedArray.contains { $0 as? Int == 123 }) + XCTAssertTrue(nestedArray.contains { $0 as? String == "string" }) + XCTAssertTrue(nestedArray.contains { $0 as? Bool == true }) + XCTAssertTrue(nestedArray.contains { $0 as? Double == 456.789 }) + XCTAssertTrue(nestedArray.contains { $0 as? [Int] == [1, 2, 3] }) + XCTAssertTrue(nestedArray.contains(where: { dict -> Bool in + let dict = dict as? [String: Any] + return dict?["string"] as? String == "string" + && dict?["int"] as? Int == 456 + && dict?["array"] as? [String] == ["9", "8", "7"] + })) + + let array = sut.array + XCTAssertNotNil(array) + XCTAssertEqual(array.count, 7) + XCTAssertTrue(array.contains { $0 is NSNull }) + XCTAssertTrue(array.contains { $0 as? Int == 1 }) + XCTAssertTrue(array.contains { $0 as? String == "string" }) + XCTAssertTrue(array.contains { $0 as? Bool == false }) + XCTAssertTrue(array.contains { $0 as? Double == 345.2346 }) + XCTAssertTrue(array.contains { $0 as? [String] == ["1", "2", "4"] }) + XCTAssertTrue(array.contains(where: { dict -> Bool in + let dict = dict as? [String: Any] + return dict?["key"] is NSNull + })) + } + + func testEncodeIfPresentDict() { + let expectedSut = MockCodableIfPresentObjectWithPropertyWrapper(dict: ["int": 123, "string": "cool", "bool": true, "double": 873.436, "null": NSNull(), "array": [1, 2, 3, 4], "dict": ["string": "ok lah", "null": NSNull()]], + array: nil) + var data: Data! + var sut: MockCodableIfPresentObjectWithPropertyWrapper! + XCTAssertNoThrow(data = try JSONEncoder().encode(expectedSut)) + XCTAssertNoThrow(sut = try JSONDecoder().decode(MockCodableIfPresentObjectWithPropertyWrapper.self, from: data)) + XCTAssertEqual(sut.dict?["int"] as? Int, 123) + XCTAssertEqual(sut.dict?["string"] as? String, "cool") + XCTAssertEqual(sut.dict?["bool"] as? Bool, true) + XCTAssertEqual(sut.dict?["double"] as? Double, 873.436) + XCTAssertTrue(sut.dict?["null"] is NSNull) + XCTAssertEqual(sut.dict?["array"] as? [Int], [1, 2, 3, 4]) + XCTAssertEqual((sut.dict?["dict"] as? [String: Any])?["string"] as? String, "ok lah") + } + + func testEncodeIfPresentNil() { + let expectedSut = MockCodableIfPresentObjectWithPropertyWrapper(dict: nil, array: nil) + var data: Data! + var sut: MockCodableIfPresentObjectWithPropertyWrapper! + XCTAssertNoThrow(data = try JSONEncoder().encode(expectedSut)) + XCTAssertNoThrow(sut = try JSONDecoder().decode(MockCodableIfPresentObjectWithPropertyWrapper.self, from: data)) + XCTAssertNil(sut.dict) + XCTAssertNil(sut.array) + } + + func testDecodeIfPresentArray() { + let expectedSut = MockCodableIfPresentObjectWithPropertyWrapper(dict: nil, array: [123, NSNull(), true, 456.789, "string", [1, 2, 3], ["string": "string", "int": 456, "array": ["9", "8", "7"]]]) + var data: Data! + var sut: MockCodableIfPresentObjectWithPropertyWrapper! + XCTAssertNoThrow(data = try JSONEncoder().encode(expectedSut)) + XCTAssertNoThrow(sut = try JSONDecoder().decode(MockCodableIfPresentObjectWithPropertyWrapper.self, from: data)) + let array = sut.array + XCTAssertEqual(array?.count, 7) + XCTAssertEqual(array?.contains { $0 is NSNull }, true) + XCTAssertEqual(array?.contains { $0 as? Int == 123 }, true) + XCTAssertEqual(array?.contains { $0 as? String == "string" }, true) + XCTAssertEqual(array?.contains { $0 as? Bool == true }, true) + XCTAssertEqual(array?.contains { $0 as? Double == 456.789 }, true) + XCTAssertEqual(array?.contains { $0 as? [Int] == [1, 2, 3] }, true) + XCTAssertEqual(array?.contains(where: { dict -> Bool in + let dict = dict as? [String: Any] + return dict?["string"] as? String == "string" + && dict?["int"] as? Int == 456 + && dict?["array"] as? [String] == ["9", "8", "7"] + }), true) + } +}