From 0c1bbacc94aab9e699b7f297b80126e6e690cc9d Mon Sep 17 00:00:00 2001 From: Morten Bjerg Gregersen Date: Wed, 10 Nov 2021 23:43:19 +0100 Subject: [PATCH 1/2] Better equality for AnyCodable collections --- Sources/AnyCodable/AnyCodable.swift | 52 +++++++++++++++++-- .../AnyCodable+EquatableTests.swift | 37 ++++++++++--- 2 files changed, 78 insertions(+), 11 deletions(-) diff --git a/Sources/AnyCodable/AnyCodable.swift b/Sources/AnyCodable/AnyCodable.swift index 08c560a..26ac8ed 100644 --- a/Sources/AnyCodable/AnyCodable.swift +++ b/Sources/AnyCodable/AnyCodable.swift @@ -18,7 +18,13 @@ public struct AnyCodable { public let value: Any public init(_ value: T?) { - self.value = value ?? () + if let dictionary = value as? [String: Any] { + self.value = dictionary.mapValues(AnyCodable.init) as [AnyHashable: AnyCodable] + } else if let array = value as? [Any] { + self.value = array.map(AnyCodable.init) + } else { + self.value = value ?? () + } } } @@ -89,9 +95,9 @@ extension AnyCodable: Decodable { } else if let string = try? container.decode(String.self) { self.init(string) } else if let array = try? container.decode([AnyCodable].self) { - self.init(array.map { $0.value }) + self.init(array) } else if let dictionary = try? container.decode([String: AnyCodable].self) { - self.init(dictionary.mapValues { $0.value }) + self.init(dictionary) } else { throw DecodingError.dataCorruptedError(in: container, debugDescription: "Value cannot be decoded") } @@ -132,7 +138,7 @@ extension AnyCodable: Equatable { return lhs == rhs case let (lhs as String, rhs as String): return lhs == rhs - case let (lhs as [String: AnyCodable], rhs as [String: AnyCodable]): + case let (lhs as [AnyHashable: AnyCodable], rhs as [AnyHashable: AnyCodable]): return lhs == rhs case let (lhs as [AnyCodable], rhs as [AnyCodable]): return lhs == rhs @@ -165,3 +171,41 @@ extension AnyCodable: CustomDebugStringConvertible { } } } + +extension AnyCodable: ExpressibleByNilLiteral {} +extension AnyCodable: ExpressibleByBooleanLiteral {} +extension AnyCodable: ExpressibleByIntegerLiteral {} +extension AnyCodable: ExpressibleByFloatLiteral {} +extension AnyCodable: ExpressibleByExtendedGraphemeClusterLiteral {} +extension AnyCodable: ExpressibleByStringLiteral {} +extension AnyCodable: ExpressibleByArrayLiteral {} +extension AnyCodable: ExpressibleByDictionaryLiteral {} +public extension AnyCodable { + init(nilLiteral _: ()) { + self.init(nil as Any?) + } + + init(booleanLiteral value: Bool) { + self.init(value) + } + + init(integerLiteral value: Int) { + self.init(value) + } + + init(floatLiteral value: Double) { + self.init(value) + } + + init(stringLiteral value: String) { + self.init(value) + } + + init(arrayLiteral elements: Any...) { + self.init(elements) + } + + init(dictionaryLiteral elements: (AnyHashable, Any)...) { + self.init([AnyHashable: Any](elements, uniquingKeysWith: { first, _ in first })) + } +} diff --git a/Tests/AnyCodableTests/AnyCodable+EquatableTests.swift b/Tests/AnyCodableTests/AnyCodable+EquatableTests.swift index 91b79be..562bfd7 100644 --- a/Tests/AnyCodableTests/AnyCodable+EquatableTests.swift +++ b/Tests/AnyCodableTests/AnyCodable+EquatableTests.swift @@ -11,15 +11,38 @@ class AnyCodableEquatableTests: XCTestCase { /// Can all simple types be compared? func testEquality() throws { // Given - let array = [1, 2, 3].map(AnyCodable.init) - let dictionary = ["a": 1, "b": 2, "c": 3].mapValues(AnyCodable.init) - let values = [nil, true, - 1 as Int, 2 as Int8, 3 as Int16, 4 as Int32, 5 as Int64, - 1 as UInt, 2 as UInt8, 3 as UInt16, 4 as UInt32, 5 as UInt64, - 3.14159265358979323846 as Float, 3.14159265358979323846 as Double, - "string", array, dictionary].map(AnyCodable.init) + let array: AnyCodable = [1, 2, 3] + let dictionary: AnyCodable = ["a": 1, "b": 2, "c": 3] + let values: [AnyCodable] = [ + nil, + true, + AnyCodable(1 as Int), + AnyCodable(2 as Int8), + AnyCodable(3 as Int16), + AnyCodable(4 as Int32), + AnyCodable(5 as Int64), + AnyCodable(1 as UInt), + AnyCodable(2 as UInt8), + AnyCodable(3 as UInt16), + AnyCodable(4 as UInt32), + AnyCodable(5 as UInt64), + AnyCodable(3.14159265358979323846 as Float), + AnyCodable(3.14159265358979323846 as Double), + "string", + array, + dictionary, + ] // Then values.forEach { XCTAssertEqual($0, $0) } XCTAssertNotEqual(values[3], values[10]) } + + /// Can all nested dictionary and arrays be compared? + func testNestedEquality() throws { + // Given + let dictionaryLiteral: AnyCodable = ["userInfo": ["things": ["nothing", "something", "anything"]], "userInfo": 42] + let dictionary = AnyCodable(["userInfo": ["things": ["nothing", "something", "anything"]]]) + // Then + XCTAssertEqual(dictionaryLiteral, dictionary) + } } From 2bb66d4ed19c2b974226435e573da830385798c8 Mon Sep 17 00:00:00 2001 From: Morten Bjerg Gregersen Date: Wed, 10 Nov 2021 23:44:03 +0100 Subject: [PATCH 2/2] Update AnyCodable+EquatableTests.swift --- Tests/AnyCodableTests/AnyCodable+EquatableTests.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Tests/AnyCodableTests/AnyCodable+EquatableTests.swift b/Tests/AnyCodableTests/AnyCodable+EquatableTests.swift index 562bfd7..23af853 100644 --- a/Tests/AnyCodableTests/AnyCodable+EquatableTests.swift +++ b/Tests/AnyCodableTests/AnyCodable+EquatableTests.swift @@ -30,7 +30,7 @@ class AnyCodableEquatableTests: XCTestCase { AnyCodable(3.14159265358979323846 as Double), "string", array, - dictionary, + dictionary ] // Then values.forEach { XCTAssertEqual($0, $0) } @@ -40,7 +40,8 @@ class AnyCodableEquatableTests: XCTestCase { /// Can all nested dictionary and arrays be compared? func testNestedEquality() throws { // Given - let dictionaryLiteral: AnyCodable = ["userInfo": ["things": ["nothing", "something", "anything"]], "userInfo": 42] + let dictionaryLiteral: AnyCodable = ["userInfo": ["things": ["nothing", "something", "anything"]], + "userInfo": 42] let dictionary = AnyCodable(["userInfo": ["things": ["nothing", "something", "anything"]]]) // Then XCTAssertEqual(dictionaryLiteral, dictionary)