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 }