refactor(swift): dedupe AnyCodable

This commit is contained in:
Peter Steinberger
2026-02-15 20:00:40 +00:00
parent 8ccbd00e1b
commit a3419e48ab
3 changed files with 47 additions and 111 deletions

View File

@@ -1,8 +1,9 @@
import Foundation
/// Lightweight `Codable` wrapper that round-trips heterogeneous JSON payloads.
///
/// Marked `@unchecked Sendable` because it can hold reference types.
public struct AnyCodable: Codable, @unchecked Sendable {
public struct AnyCodable: Codable, @unchecked Sendable, Hashable {
public let value: Any
public init(_ value: Any) { self.value = value }
@@ -16,9 +17,7 @@ public struct AnyCodable: Codable, @unchecked Sendable {
if container.decodeNil() { self.value = NSNull(); return }
if let dict = try? container.decode([String: AnyCodable].self) { self.value = dict; return }
if let array = try? container.decode([AnyCodable].self) { self.value = array; return }
throw DecodingError.dataCorruptedError(
in: container,
debugDescription: "Unsupported type")
throw DecodingError.dataCorruptedError(in: container, debugDescription: "Unsupported type")
}
public func encode(to encoder: Encoder) throws {
@@ -51,4 +50,46 @@ public struct AnyCodable: Codable, @unchecked Sendable {
throw EncodingError.invalidValue(self.value, context)
}
}
public static func == (lhs: AnyCodable, rhs: AnyCodable) -> Bool {
switch (lhs.value, rhs.value) {
case let (l as Int, r as Int): l == r
case let (l as Double, r as Double): l == r
case let (l as Bool, r as Bool): l == r
case let (l as String, r as String): l == r
case (_ as NSNull, _ as NSNull): true
case let (l as [String: AnyCodable], r as [String: AnyCodable]): l == r
case let (l as [AnyCodable], r as [AnyCodable]): l == r
default:
false
}
}
public func hash(into hasher: inout Hasher) {
switch self.value {
case let v as Int:
hasher.combine(0); hasher.combine(v)
case let v as Double:
hasher.combine(1); hasher.combine(v)
case let v as Bool:
hasher.combine(2); hasher.combine(v)
case let v as String:
hasher.combine(3); hasher.combine(v)
case _ as NSNull:
hasher.combine(4)
case let v as [String: AnyCodable]:
hasher.combine(5)
for (k, val) in v.sorted(by: { $0.key < $1.key }) {
hasher.combine(k)
hasher.combine(val)
}
case let v as [AnyCodable]:
hasher.combine(6)
for item in v {
hasher.combine(item)
}
default:
hasher.combine(999)
}
}
}