Last active
March 23, 2025 18:38
-
-
Save glukianets/f6d121f11f2d2c4b82677610a7979535 to your computer and use it in GitHub Desktop.
Unstructured JSON Codable
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 JSONValue: Codable & Hashable & Sendable & LosslessStringConvertible { | |
private struct ParseError: Error {} | |
static let decoder: JSONDecoder = { | |
let decoder = JSONDecoder() | |
decoder.allowsJSON5 = true | |
return decoder | |
}() | |
static let encoder: JSONEncoder = { | |
let encoder = JSONEncoder() | |
encoder.outputFormatting = .prettyPrinted | |
return encoder | |
}() | |
case object([String: JSONValue]) | |
case array([JSONValue]) | |
case string(String) | |
case number(Double) | |
case boolean(Bool) | |
case null | |
public init(from decoder: any Decoder) throws { | |
let container = try decoder.singleValueContainer() | |
if let array = try? container.decode([JSONValue].self) { | |
self = .array(array) | |
} else if let dictionary = try? container.decode([String: JSONValue].self) { | |
self = .object(dictionary) | |
} else if let number = try? container.decode(Double.self) { | |
self = .number(number) | |
} else if let boolean = try? container.decode(Bool.self) { | |
self = .boolean(boolean) | |
} else if let string = try? container.decode(String.self) { | |
self = .string(string) | |
} else if container.decodeNil() { | |
self = .null | |
} else { | |
throw ParseError() | |
} | |
} | |
public init?(_ description: String) { | |
guard | |
let data = description.data(using: .utf8), | |
let object = try? Self.decoder.decode(Self.self, from: data) | |
else { return nil } | |
self = object | |
} | |
public func encode(to encoder: any Encoder) throws { | |
switch self { | |
case .object(let dictionary): | |
try dictionary.encode(to: encoder) | |
case .array(let array): | |
try array.encode(to: encoder) | |
case .string(let value): | |
try value.encode(to: encoder) | |
case .number(let value): | |
try value.encode(to: encoder) | |
case .boolean(let value): | |
try value.encode(to: encoder) | |
case .null: | |
var container = encoder.singleValueContainer() | |
try container.encodeNil() | |
} | |
} | |
public var description: String { | |
try! String(data: Self.encoder.encode(self), encoding: .utf8)! | |
} | |
} | |
extension String: @retroactive CodingKey { | |
public var stringValue: String { | |
self | |
} | |
public var intValue: Int? { | |
Int(self) | |
} | |
public init?(intValue: Int) { | |
self.init(describing: intValue) | |
} | |
public init?(stringValue: String) { | |
self = stringValue | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment