internal import CPlaydate extension JSON { /// A streaming JSON encoder into a string. Wraps `json_encoder`. /// Does not validate: the caller must emit well-formed JSON. public final class Encoder { /// A class so the write callback's userdata pointer stays stable. private final class Output { var bytes: [UInt8] = [] } private var encoder = json_encoder() private let output = Output() /// `pretty` adds human-readable formatting. public init(pretty: Bool = false) { jsonAPI.pointee.initEncoder.unsafelyUnwrapped(&encoder, { userdata, string, length in guard let userdata, let string else { return } let output = Unmanaged.fromOpaque(userdata).takeUnretainedValue() output.bytes.append(contentsOf: UnsafeRawBufferPointer(start: string, count: Int(length))) }, Unmanaged.passUnretained(output).toOpaque(), pretty ? 1 : 0) } /// The JSON produced so far. public var json: String { String(decoding: output.bytes, as: UTF8.self) } public func startArray() { withUnsafeMutablePointer(to: &encoder) { $0.pointee.startArray.unsafelyUnwrapped($0) } } /// Call before writing each array element. public func addArrayMember() { withUnsafeMutablePointer(to: &encoder) { $0.pointee.addArrayMember.unsafelyUnwrapped($0) } } public func endArray() { withUnsafeMutablePointer(to: &encoder) { $0.pointee.endArray.unsafelyUnwrapped($0) } } /// Starts a JSON object. public func startTable() { withUnsafeMutablePointer(to: &encoder) { $0.pointee.startTable.unsafelyUnwrapped($0) } } /// Call before writing member `name`'s value. public func addTableMember(name: String) { name.withCString { cString in withUnsafeMutablePointer(to: &encoder) { $0.pointee.addTableMember.unsafelyUnwrapped( $0, cString, Int32(name.utf8.count)) } } } public func endTable() { withUnsafeMutablePointer(to: &encoder) { $0.pointee.endTable.unsafelyUnwrapped($0) } } public func writeNull() { withUnsafeMutablePointer(to: &encoder) { $0.pointee.writeNull.unsafelyUnwrapped($0) } } public func writeBool(_ value: Bool) { withUnsafeMutablePointer(to: &encoder) { (value ? $0.pointee.writeTrue : $0.pointee.writeFalse).unsafelyUnwrapped($0) } } /// `value` must fit in `Int32`. public func writeInt(_ value: Int) { withUnsafeMutablePointer(to: &encoder) { $0.pointee.writeInt.unsafelyUnwrapped($0, Int32(value)) } } public func writeDouble(_ value: Double) { withUnsafeMutablePointer(to: &encoder) { $0.pointee.writeDouble.unsafelyUnwrapped($0, value) } } public func writeString(_ value: String) { value.withCString { cString in withUnsafeMutablePointer(to: &encoder) { $0.pointee.writeString.unsafelyUnwrapped( $0, cString, Int32(value.utf8.count)) } } } /// Writes a whole `Value` tree; `.float` as `Double`, keys in `Dictionary` order. public func write(_ value: Value) { switch value { case .null: writeNull() case .bool(let bool): writeBool(bool) case .int(let int): writeInt(int) case .float(let float): writeDouble(Double(float)) case .string(let string): writeString(string) case .array(let items): startArray() for item in items { addArrayMember() write(item) } endArray() case .table(let entries): startTable() for (key, entry) in entries { addTableMember(name: key) write(entry) } endTable() } } } }