1const std = @import("../std.zig");
2const assert = std.debug.assert;
3const maxInt = std.math.maxInt;
4
5const State = enum {
6    Complete,
7    Value,
8    ArrayStart,
9    Array,
10    ObjectStart,
11    Object,
12};
13
14/// Writes JSON ([RFC8259](https://tools.ietf.org/html/rfc8259)) formatted data
15/// to a stream. `max_depth` is a comptime-known upper bound on the nesting depth.
16/// TODO A future iteration of this API will allow passing `null` for this value,
17/// and disable safety checks in release builds.
18pub fn WriteStream(comptime OutStream: type, comptime max_depth: usize) type {
19    return struct {
20        const Self = @This();
21
22        pub const Stream = OutStream;
23
24        whitespace: std.json.StringifyOptions.Whitespace = std.json.StringifyOptions.Whitespace{
25            .indent_level = 0,
26            .indent = .{ .Space = 1 },
27        },
28
29        stream: OutStream,
30        state_index: usize,
31        state: [max_depth]State,
32
33        pub fn init(stream: OutStream) Self {
34            var self = Self{
35                .stream = stream,
36                .state_index = 1,
37                .state = undefined,
38            };
39            self.state[0] = .Complete;
40            self.state[1] = .Value;
41            return self;
42        }
43
44        pub fn beginArray(self: *Self) !void {
45            assert(self.state[self.state_index] == State.Value); // need to call arrayElem or objectField
46            try self.stream.writeByte('[');
47            self.state[self.state_index] = State.ArrayStart;
48            self.whitespace.indent_level += 1;
49        }
50
51        pub fn beginObject(self: *Self) !void {
52            assert(self.state[self.state_index] == State.Value); // need to call arrayElem or objectField
53            try self.stream.writeByte('{');
54            self.state[self.state_index] = State.ObjectStart;
55            self.whitespace.indent_level += 1;
56        }
57
58        pub fn arrayElem(self: *Self) !void {
59            const state = self.state[self.state_index];
60            switch (state) {
61                .Complete => unreachable,
62                .Value => unreachable,
63                .ObjectStart => unreachable,
64                .Object => unreachable,
65                .Array, .ArrayStart => {
66                    if (state == .Array) {
67                        try self.stream.writeByte(',');
68                    }
69                    self.state[self.state_index] = .Array;
70                    self.pushState(.Value);
71                    try self.indent();
72                },
73            }
74        }
75
76        pub fn objectField(self: *Self, name: []const u8) !void {
77            const state = self.state[self.state_index];
78            switch (state) {
79                .Complete => unreachable,
80                .Value => unreachable,
81                .ArrayStart => unreachable,
82                .Array => unreachable,
83                .Object, .ObjectStart => {
84                    if (state == .Object) {
85                        try self.stream.writeByte(',');
86                    }
87                    self.state[self.state_index] = .Object;
88                    self.pushState(.Value);
89                    try self.indent();
90                    try self.writeEscapedString(name);
91                    try self.stream.writeByte(':');
92                    if (self.whitespace.separator) {
93                        try self.stream.writeByte(' ');
94                    }
95                },
96            }
97        }
98
99        pub fn endArray(self: *Self) !void {
100            switch (self.state[self.state_index]) {
101                .Complete => unreachable,
102                .Value => unreachable,
103                .ObjectStart => unreachable,
104                .Object => unreachable,
105                .ArrayStart => {
106                    self.whitespace.indent_level -= 1;
107                    try self.stream.writeByte(']');
108                    self.popState();
109                },
110                .Array => {
111                    self.whitespace.indent_level -= 1;
112                    try self.indent();
113                    self.popState();
114                    try self.stream.writeByte(']');
115                },
116            }
117        }
118
119        pub fn endObject(self: *Self) !void {
120            switch (self.state[self.state_index]) {
121                .Complete => unreachable,
122                .Value => unreachable,
123                .ArrayStart => unreachable,
124                .Array => unreachable,
125                .ObjectStart => {
126                    self.whitespace.indent_level -= 1;
127                    try self.stream.writeByte('}');
128                    self.popState();
129                },
130                .Object => {
131                    self.whitespace.indent_level -= 1;
132                    try self.indent();
133                    self.popState();
134                    try self.stream.writeByte('}');
135                },
136            }
137        }
138
139        pub fn emitNull(self: *Self) !void {
140            assert(self.state[self.state_index] == State.Value);
141            try self.stringify(null);
142            self.popState();
143        }
144
145        pub fn emitBool(self: *Self, value: bool) !void {
146            assert(self.state[self.state_index] == State.Value);
147            try self.stringify(value);
148            self.popState();
149        }
150
151        pub fn emitNumber(
152            self: *Self,
153            /// An integer, float, or `std.math.BigInt`. Emitted as a bare number if it fits losslessly
154            /// in a IEEE 754 double float, otherwise emitted as a string to the full precision.
155            value: anytype,
156        ) !void {
157            assert(self.state[self.state_index] == State.Value);
158            switch (@typeInfo(@TypeOf(value))) {
159                .Int => |info| {
160                    if (info.bits < 53) {
161                        try self.stream.print("{}", .{value});
162                        self.popState();
163                        return;
164                    }
165                    if (value < 4503599627370496 and (info.signedness == .unsigned or value > -4503599627370496)) {
166                        try self.stream.print("{}", .{value});
167                        self.popState();
168                        return;
169                    }
170                },
171                .ComptimeInt => {
172                    return self.emitNumber(@as(std.math.IntFittingRange(value, value), value));
173                },
174                .Float, .ComptimeFloat => if (@floatCast(f64, value) == value) {
175                    try self.stream.print("{}", .{@floatCast(f64, value)});
176                    self.popState();
177                    return;
178                },
179                else => {},
180            }
181            try self.stream.print("\"{}\"", .{value});
182            self.popState();
183        }
184
185        pub fn emitString(self: *Self, string: []const u8) !void {
186            assert(self.state[self.state_index] == State.Value);
187            try self.writeEscapedString(string);
188            self.popState();
189        }
190
191        fn writeEscapedString(self: *Self, string: []const u8) !void {
192            assert(std.unicode.utf8ValidateSlice(string));
193            try self.stringify(string);
194        }
195
196        /// Writes the complete json into the output stream
197        pub fn emitJson(self: *Self, json: std.json.Value) Stream.Error!void {
198            assert(self.state[self.state_index] == State.Value);
199            try self.stringify(json);
200            self.popState();
201        }
202
203        fn indent(self: *Self) !void {
204            assert(self.state_index >= 1);
205            try self.stream.writeByte('\n');
206            try self.whitespace.outputIndent(self.stream);
207        }
208
209        fn pushState(self: *Self, state: State) void {
210            self.state_index += 1;
211            self.state[self.state_index] = state;
212        }
213
214        fn popState(self: *Self) void {
215            self.state_index -= 1;
216        }
217
218        fn stringify(self: *Self, value: anytype) !void {
219            try std.json.stringify(value, std.json.StringifyOptions{
220                .whitespace = self.whitespace,
221            }, self.stream);
222        }
223    };
224}
225
226pub fn writeStream(
227    out_stream: anytype,
228    comptime max_depth: usize,
229) WriteStream(@TypeOf(out_stream), max_depth) {
230    return WriteStream(@TypeOf(out_stream), max_depth).init(out_stream);
231}
232
233test "json write stream" {
234    var out_buf: [1024]u8 = undefined;
235    var slice_stream = std.io.fixedBufferStream(&out_buf);
236    const out = slice_stream.writer();
237
238    var arena_allocator = std.heap.ArenaAllocator.init(std.testing.allocator);
239    defer arena_allocator.deinit();
240
241    var w = std.json.writeStream(out, 10);
242
243    try w.beginObject();
244
245    try w.objectField("object");
246    try w.emitJson(try getJsonObject(arena_allocator.allocator()));
247
248    try w.objectField("string");
249    try w.emitString("This is a string");
250
251    try w.objectField("array");
252    try w.beginArray();
253    try w.arrayElem();
254    try w.emitString("Another string");
255    try w.arrayElem();
256    try w.emitNumber(@as(i32, 1));
257    try w.arrayElem();
258    try w.emitNumber(@as(f32, 3.5));
259    try w.endArray();
260
261    try w.objectField("int");
262    try w.emitNumber(@as(i32, 10));
263
264    try w.objectField("float");
265    try w.emitNumber(@as(f32, 3.5));
266
267    try w.endObject();
268
269    const result = slice_stream.getWritten();
270    const expected =
271        \\{
272        \\ "object": {
273        \\  "one": 1,
274        \\  "two": 2.0e+00
275        \\ },
276        \\ "string": "This is a string",
277        \\ "array": [
278        \\  "Another string",
279        \\  1,
280        \\  3.5e+00
281        \\ ],
282        \\ "int": 10,
283        \\ "float": 3.5e+00
284        \\}
285    ;
286    try std.testing.expect(std.mem.eql(u8, expected, result));
287}
288
289fn getJsonObject(allocator: std.mem.Allocator) !std.json.Value {
290    var value = std.json.Value{ .Object = std.json.ObjectMap.init(allocator) };
291    try value.Object.put("one", std.json.Value{ .Integer = @intCast(i64, 1) });
292    try value.Object.put("two", std.json.Value{ .Float = 2.0 });
293    return value;
294}
295