mirror of
https://github.com/wu736139669/hapi.git
synced 2026-10-08 19:19:42 +00:00
107 lines
3.2 KiB
Swift
107 lines
3.2 KiB
Swift
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))
|
|
}
|
|
}
|