Last active
April 21, 2025 10:16
-
-
Save shawnthroop/e2da452b31cab830f02694da0ab6bad6 to your computer and use it in GitHub Desktop.
JSON expressed as an Codable enum
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public enum JSON : Sendable { | |
case null | |
case value(any Codable & Sendable) | |
case list([JSON]) | |
case object([String: JSON]) | |
} | |
extension JSON : Codable, CustomStringConvertible { | |
public func encode(to encoder: any Encoder) throws { | |
switch self { | |
case .null: | |
var container = encoder.singleValueContainer() | |
try container.encodeNil() | |
case .value(let value): | |
var container = encoder.singleValueContainer() | |
try container.encode(value) | |
case .list(let values): | |
var container = encoder.unkeyedContainer() | |
for value in values { | |
try container.encode(value) | |
} | |
case .object(let object): | |
var container = encoder.container(keyedBy: AnyCodingKey.self) | |
for (key, value) in object { | |
try container.encode(value, forKey: .string(key)) | |
} | |
} | |
} | |
public init(from decoder: any Decoder) throws { | |
if let container = try ignoringTypeMismatch(decoder.container(keyedBy: AnyCodingKey.self)) { | |
try self.init(container: container) | |
} else if var container = try ignoringTypeMismatch(decoder.unkeyedContainer()) { | |
try self.init(container: &container) | |
} else { | |
try self.init(container: decoder.singleValueContainer()) | |
} | |
} | |
public var description: String { | |
switch self { | |
case .null: | |
"null" | |
case .value(let value): | |
"\(value)" | |
case .list(let values): | |
values.description | |
case .object(let content): | |
content.description | |
} | |
} | |
} | |
public enum AnyCodingKey : Hashable, Sendable { | |
case string(String) | |
case int(Int) | |
} | |
extension AnyCodingKey: CodingKey { | |
public var stringValue: String { | |
switch self { | |
case .string(let value): | |
value | |
case .int(let value): | |
String(value) | |
} | |
} | |
public var intValue: Int? { | |
switch self { | |
case .string: | |
nil | |
case .int(let value): | |
value | |
} | |
} | |
public init(intValue: Int) { | |
self = .int(intValue) | |
} | |
public init(stringValue: String) { | |
self = .string(stringValue) | |
} | |
} | |
private extension JSON { | |
init<Container: KeyedDecodingContainerProtocol>(container: Container) throws { | |
self = try .object(container.allKeys.reduce(into: [:]) { object, key in | |
object[key.stringValue] = try container.decode(JSON.self, forKey: key) | |
}) | |
} | |
init(container: inout UnkeyedDecodingContainer) throws { | |
var values = [JSON]() | |
while !container.isAtEnd { | |
try values.append(container.decode(JSON.self)) | |
} | |
self = .list(values) | |
} | |
init(container: SingleValueDecodingContainer) throws { | |
if container.decodeNil() { | |
self = .null | |
} else if let bool = try ignoringTypeMismatch(container.decode(Bool.self)) { | |
self = .value(bool) | |
} else if let int = try ignoringTypeMismatch(container.decode(Int.self)) { | |
self = .value(int) | |
} else if let double = try ignoringTypeMismatch(container.decode(Double.self)) { | |
self = .value(double) | |
} else if let string = try ignoringTypeMismatch(container.decode(String.self)) { | |
self = .value(string) | |
} else if let list = try ignoringTypeMismatch(container.decode([JSON].self)) { | |
self = .list(list) | |
} else if let object = try ignoringTypeMismatch(container.decode([String: JSON].self)) { | |
self = .object(object) | |
} else { | |
throw DecodingError.dataCorruptedError(in: container, debugDescription: "Unsupported type") | |
} | |
} | |
} | |
/// Perform body while catching and ignoring DecodingError.typeMismatch error. | |
func ignoringTypeMismatch<T>(_ body: @autoclosure () throws -> T) throws -> T? { | |
do { | |
return try body() | |
} catch DecodingError.typeMismatch { | |
return nil | |
} catch { | |
throw error | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment