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