import Foundation /// A JSON value representation. This is a bit more useful than the naïve `[String:Any]` type /// for JSON values, since it makes sure only valid JSON values are present & supports `Equatable` /// and `Codable`, so that you can compare values for equality and code and decode them into data /// or strings. @dynamicMemberLookup public enum JSON: Equatable { case string(String) case number(Double) case object([String:JSON]) case array([JSON]) case bool(Bool) case null } extension JSON: Codable { public func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() switch self { case let .array(array): try container.encode(array) case let .object(object): try container.encode(object) case let .string(string): try container.encode(string) case let .number(number): try container.encode(number) case let .bool(bool): try container.encode(bool) case .null: try container.encodeNil() } } public init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() if let object = try? container.decode([String: JSON].self) { self = .object(object) } else if let array = try? container.decode([JSON].self) { self = .array(array) } else if let string = try? container.decode(String.self) { self = .string(string) } else if let bool = try? container.decode(Bool.self) { self = .bool(bool) } else if let number = try? container.decode(Double.self) { self = .number(number) } else if container.decodeNil() { self = .null } else { throw DecodingError.dataCorrupted( .init(codingPath: decoder.codingPath, debugDescription: "Invalid JSON value.") ) } } } extension JSON: CustomDebugStringConvertible { public var debugDescription: String { switch self { case .string(let str): return str.debugDescription case .number(let num): return num.debugDescription case .bool(let bool): return bool.description case .null: return "null" default: let encoder = JSONEncoder() encoder.outputFormatting = [.prettyPrinted] return try! String(data: encoder.encode(self), encoding: .utf8)! } } } extension JSON: Hashable {}