import Foundation /// A JSON value exactly as it appears on the wire. /// /// The hub protocol carries several free-form JSON payloads (tool inputs, /// agent event data, metadata blobs). `JSONValue` preserves them losslessly /// without committing to a schema. Numbers are stored as `Double`, matching /// JavaScript semantics on the hub side. public enum JSONValue: Equatable, Hashable, Sendable { case null case bool(Bool) case number(Double) case string(String) case array([JSONValue]) case object([String: JSONValue]) } // MARK: - Codable extension JSONValue: Codable { public init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() if container.decodeNil() { self = .null } else if let value = try? container.decode(Bool.self) { // Foundation's JSON decoder does not coerce 0/1 to Bool (nor // true/false to Double), so the ordering here is not lossy. self = .bool(value) } else if let value = try? container.decode(Double.self) { self = .number(value) } else if let value = try? container.decode(String.self) { self = .string(value) } else if let value = try? container.decode([JSONValue].self) { self = .array(value) } else if let value = try? container.decode([String: JSONValue].self) { self = .object(value) } else { throw DecodingError.dataCorruptedError( in: container, debugDescription: "Value is not a representable JSON type" ) } } public func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() switch self { case .null: try container.encodeNil() case .bool(let value): try container.encode(value) case .number(let value): try container.encode(value) case .string(let value): try container.encode(value) case .array(let value): try container.encode(value) case .object(let value): try container.encode(value) } } } // MARK: - Literal conveniences (used heavily by tests and fixtures) extension JSONValue: ExpressibleByNilLiteral { public init(nilLiteral: ()) { self = .null } } extension JSONValue: ExpressibleByBooleanLiteral { public init(booleanLiteral value: Bool) { self = .bool(value) } } extension JSONValue: ExpressibleByIntegerLiteral { public init(integerLiteral value: Int) { self = .number(Double(value)) } } extension JSONValue: ExpressibleByFloatLiteral { public init(floatLiteral value: Double) { self = .number(value) } } extension JSONValue: ExpressibleByStringLiteral { public init(stringLiteral value: String) { self = .string(value) } } extension JSONValue: ExpressibleByArrayLiteral { public init(arrayLiteral elements: JSONValue...) { self = .array(elements) } } extension JSONValue: ExpressibleByDictionaryLiteral { public init(dictionaryLiteral elements: (String, JSONValue)...) { self = .object(Dictionary(uniqueKeysWithValues: elements)) } }