Skip to content

Instantly share code, notes, and snippets.

@glukianets
Last active March 23, 2025 18:38
Show Gist options
  • Save glukianets/f6d121f11f2d2c4b82677610a7979535 to your computer and use it in GitHub Desktop.
Save glukianets/f6d121f11f2d2c4b82677610a7979535 to your computer and use it in GitHub Desktop.
Unstructured JSON Codable
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