From d65874da36551f4c948711fae164820a3175bc5d Mon Sep 17 00:00:00 2001 From: Tal Date: Sat, 29 Aug 2026 08:37:33 +0900 Subject: [PATCH] Avoid escaping slashes in JSON output (#2205) Fixes #2204. Swift's default JSONEncoder output escapes forward slashes, which makes CLI JSON paths and URLs harder to read and copy. This change makes the shared CLI JSON renderer always use .withoutEscapingSlashes and routes machine list --format json through that renderer while preserving its ISO-8601 date encoding. OpenAI Codex assisted with investigation, implementation, and verification. The final change is limited to JSON rendering behavior and focused regression coverage. Co-authored-by: taljeon <169621860+taljeon@users.noreply.github.com> --- Sources/ContainerCommands/Machine/MachineList.swift | 6 ++---- Sources/ContainerCommands/OutputRendering.swift | 2 +- Tests/ContainerCommandsTests/ListFormattingTests.swift | 9 +++++++++ 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/Sources/ContainerCommands/Machine/MachineList.swift b/Sources/ContainerCommands/Machine/MachineList.swift index 36770644..38b5b09e 100644 --- a/Sources/ContainerCommands/Machine/MachineList.swift +++ b/Sources/ContainerCommands/Machine/MachineList.swift @@ -61,10 +61,8 @@ extension Application { let printables = machines.map { PrintableMachine($0, isDefault: $0.id == defaultMachine) } - let encoder = JSONEncoder() - encoder.dateEncodingStrategy = .iso8601 - let data = try encoder.encode(printables) - print(String(decoding: data, as: UTF8.self)) + let options = JSONOptions(dateEncodingStrategy: .iso8601) + try Output.emit(Output.renderJSON(printables, options: options)) return } diff --git a/Sources/ContainerCommands/OutputRendering.swift b/Sources/ContainerCommands/OutputRendering.swift index 2f085dc9..91daadd9 100644 --- a/Sources/ContainerCommands/OutputRendering.swift +++ b/Sources/ContainerCommands/OutputRendering.swift @@ -44,7 +44,7 @@ public enum Output { /// Renders an `Encodable` value as a JSON string. public static func renderJSON(_ value: T, options: JSONOptions = .compact) throws -> String { let encoder = JSONEncoder() - encoder.outputFormatting = options.outputFormatting + encoder.outputFormatting = options.outputFormatting.union(.withoutEscapingSlashes) encoder.dateEncodingStrategy = options.dateEncodingStrategy let data = try encoder.encode(value) return String(decoding: data, as: UTF8.self) diff --git a/Tests/ContainerCommandsTests/ListFormattingTests.swift b/Tests/ContainerCommandsTests/ListFormattingTests.swift index 3c0c5be8..cb71ca9c 100644 --- a/Tests/ContainerCommandsTests/ListFormattingTests.swift +++ b/Tests/ContainerCommandsTests/ListFormattingTests.swift @@ -163,6 +163,15 @@ struct RenderJSONTests { #expect(!json.contains("\n")) } + @Test + func doesNotEscapeSlashes() throws { + let item = TestItem(id: "/foo/bar", name: "https://example.com/path") + let json = try Output.renderJSON(item) + #expect(json.contains("/foo/bar")) + #expect(json.contains("https://example.com/path")) + #expect(!json.contains("\\/")) + } + @Test func prettyIsMultiLine() throws { let items = [TestItem(id: "a", name: "b")]