lib/pretty/core/src/json/document.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const escape = @import("escape.zig");
3 const pretty = @import("../root.zig");
4
5 const Builder = pretty.Builder;
6 const Doc = pretty.Doc;
7 const LayoutOptions = pretty.LayoutOptions;
8 const WriteState = pretty.WriteState;
9 const softbreak = pretty.softbreak;
10 const softline = pretty.softline;
11
12 pub fn valueDoc(builder: Builder, value: std.json.Value) anyerror!Doc {
13 return switch (value) {
14 .null => try builder.styledText(.keyword, "null"),
15 .bool => |item| try builder.styledText(.keyword, if (item) "true" else "false"),
16 .integer => |item| try builder.styledFmt(.number, "{d}", .{item}),
17 .float => |item| try builder.styledFmt(.number, "{d}", .{item}),
18 .number_string => |item| try builder.styledText(.number, item),
19 .string => |item| try builder.styledText(.string, try escape.stringAlloc(builder.allocator, item)),
20 .array => |items| try arrayDoc(builder, items.items),
21 .object => |object| try objectDoc(builder, object),
22 };
23 }
24
25 pub fn renderValueAlloc(
26 allocator: std.mem.Allocator,
27 value: std.json.Value,
28 options: LayoutOptions,
29 ) ![]u8 {
30 var arena_state = std.heap.ArenaAllocator.init(allocator);
31 defer arena_state.deinit();
32 const builder = Builder.init(arena_state.allocator());
33 return try pretty.renderAlloc(allocator, try valueDoc(builder, value), options);
34 }
35
36 pub fn writeValue(
37 writer: anytype,
38 allocator: std.mem.Allocator,
39 value: std.json.Value,
40 options: LayoutOptions,
41 ) !void {
42 try writeValueWithState(writer, allocator, value, options, .{});
43 }
44
45 pub fn writeValueWithState(
46 writer: anytype,
47 allocator: std.mem.Allocator,
48 value: std.json.Value,
49 options: LayoutOptions,
50 state: WriteState,
51 ) !void {
52 var arena_state = std.heap.ArenaAllocator.init(allocator);
53 defer arena_state.deinit();
54 const builder = Builder.init(arena_state.allocator());
55 try pretty.writeWithState(writer, try valueDoc(builder, value), options, state);
56 }
57
58 fn arrayDoc(builder: Builder, items: []const std.json.Value) anyerror!Doc {
59 if (items.len == 0) return try builder.concat(&.{
60 try builder.punct("["),
61 try builder.punct("]"),
62 });
63
64 const docs = try builder.allocator.alloc(Doc, items.len);
65 for (items, 0..) |item, index| docs[index] = try valueDoc(builder, item);
66
67 return try builder.group(try builder.concat(&.{
68 try builder.punct("["),
69 try builder.nest(2, try builder.concat(&.{
70 softbreak,
71 try builder.join(docs, try builder.concat(&.{
72 try builder.punct(","),
73 softline,
74 })),
75 })),
76 softbreak,
77 try builder.punct("]"),
78 }));
79 }
80
81 fn objectDoc(builder: Builder, object: std.json.ObjectMap) anyerror!Doc {
82 if (object.count() == 0) return try builder.concat(&.{
83 try builder.punct("{"),
84 try builder.punct("}"),
85 });
86
87 var fields: std.ArrayList(Doc) = .empty;
88 var iterator = object.iterator();
89 while (iterator.next()) |entry| {
90 try fields.append(builder.allocator, try builder.concat(&.{
91 try builder.styledText(.string, try escape.stringAlloc(builder.allocator, entry.key_ptr.*)),
92 try builder.punct(":"),
93 builder.text(" "),
94 try valueDoc(builder, entry.value_ptr.*),
95 }));
96 }
97
98 return try builder.group(try builder.concat(&.{
99 try builder.punct("{"),
100 try builder.nest(2, try builder.concat(&.{
101 softbreak,
102 try builder.join(fields.items, try builder.concat(&.{
103 try builder.punct(","),
104 softline,
105 })),
106 })),
107 softbreak,
108 try builder.punct("}"),
109 }));
110 }
111
112 test "valueDoc renders grouped JSON values" {
113 const allocator = std.testing.allocator;
114 var parsed = try std.json.parseFromSlice(std.json.Value, allocator, "{\"name\":\"alpha\",\"items\":[1,true,null]}", .{});
115 defer parsed.deinit();
116
117 const wide = try renderValueAlloc(allocator, parsed.value, .{ .width = 80 });
118 defer allocator.free(wide);
119 try std.testing.expectEqualStrings("{\"name\": \"alpha\", \"items\": [1, true, null]}", wide);
120
121 const narrow = try renderValueAlloc(allocator, parsed.value, .{ .width = 12 });
122 defer allocator.free(narrow);
123 try std.testing.expect(std.mem.indexOf(u8, narrow, "\n \"items\": [") != null);
124 }
125
126 test "writeValueWithState preserves caller indentation" {
127 const allocator = std.testing.allocator;
128 var parsed = try std.json.parseFromSlice(std.json.Value, allocator, "{\"inner\":[1,2]}", .{});
129 defer parsed.deinit();
130
131 var out = std.Io.Writer.Allocating.init(allocator);
132 defer out.deinit();
133 try out.writer.writeAll(" \"metadata\": ");
134 try writeValueWithState(&out.writer, allocator, parsed.value, .{ .width = 1 }, .{ .indent = 4, .column = 16 });
135
136 const rendered = try out.toOwnedSlice();
137 defer allocator.free(rendered);
138 try std.testing.expectEqualStrings(
139 \\ "metadata": {
140 \\ "inner": [
141 \\ 1,
142 \\ 2
143 \\ ]
144 \\ }
145 , rendered);
146 }