| ... | @@ -156,36 +156,23 @@ pub fn writeStreamArbitraryDepth( | ... | @@ -156,36 +156,23 @@ pub fn writeStreamArbitraryDepth( |
| 156 | /// | <array> | 156 | /// | <array> |
| 157 | /// | write | 157 | /// | write |
| 158 | /// | print | 158 | /// | print |
| | 159 | /// | <writeRawStream> |
| 159 | /// <object> = beginObject ( <field> <value> )* endObject | 160 | /// <object> = beginObject ( <field> <value> )* endObject |
| 160 | /// <field> = objectField | objectFieldRaw | 161 | /// <field> = objectField | objectFieldRaw | <objectFieldRawStream> |
| 161 | /// <array> = beginArray ( <value> )* endArray | 162 | /// <array> = beginArray ( <value> )* endArray |
| | 163 | /// <writeRawStream> = beginWriteRaw ( stream.writeAll )* endWriteRaw |
| | 164 | /// <objectFieldRawStream> = beginObjectFieldRaw ( stream.writeAll )* endObjectFieldRaw |
| 162 | /// ``` | 165 | /// ``` |
| 163 | /// | 166 | /// |
| 164 | /// Supported types: | 167 | /// The `safety_checks_hint` parameter determines how much memory is used to enable assertions that the above grammar is being followed, |
| 165 | /// * Zig `bool` -> JSON `true` or `false`. | 168 | /// e.g. tripping an assertion rather than allowing `endObject` to emit the final `}` in `[[[]]}`. |
| 166 | /// * Zig `?T` -> `null` or the rendering of `T`. | 169 | /// "Depth" in this context means the depth of nested `[]` or `{}` expressions |
| 167 | /// * Zig `i32`, `u64`, etc. -> JSON number or string. | 170 | /// (or equivalently the amount of recursion on the `<value>` grammar expression above). |
| 168 | /// * When option `emit_nonportable_numbers_as_strings` is true, if the value is outside the range `+-1<<53` (the precise integer range of f64), it is rendered as a JSON string in base 10. Otherwise, it is rendered as JSON number. | 171 | /// For example, emitting the JSON `[[[]]]` requires a depth of 3. |
| 169 | /// * Zig floats -> JSON number or string. | 172 | /// If `.checked_to_fixed_depth` is used, there is additionally an assertion that the nesting depth never exceeds the given limit. |
| 170 | /// * If the value cannot be precisely represented by an f64, it is rendered as a JSON string. Otherwise, it is rendered as JSON number. | 173 | /// `.checked_to_arbitrary_depth` requires a runtime allocator for the memory. |
| 171 | /// * TODO: Float rendering will likely change in the future, e.g. to remove the unnecessary "e+00". | 174 | /// `.checked_to_fixed_depth` embeds the storage required in the `WriteStream` struct. |
| 172 | /// * Zig `[]const u8`, `[]u8`, `*[N]u8`, `@Vector(N, u8)`, and similar -> JSON string. | 175 | /// `.assumed_correct` requires no space and performs none of these assertions. |
| 173 | /// * See `StringifyOptions.emit_strings_as_arrays`. | | |
| 174 | /// * If the content is not valid UTF-8, rendered as an array of numbers instead. | | |
| 175 | /// * Zig `[]T`, `[N]T`, `*[N]T`, `@Vector(N, T)`, and similar -> JSON array of the rendering of each item. | | |
| 176 | /// * Zig tuple -> JSON array of the rendering of each item. | | |
| 177 | /// * Zig `struct` -> JSON object with each field in declaration order. | | |
| 178 | /// * If the struct declares a method `pub fn jsonStringify(self: *@This(), jw: anytype) !void`, it is called to do the serialization instead of the default behavior. The given `jw` is a pointer to this `WriteStream`. See `std.json.Value` for an example. | | |
| 179 | /// * See `StringifyOptions.emit_null_optional_fields`. | | |
| 180 | /// * Zig `union(enum)` -> JSON object with one field named for the active tag and a value representing the payload. | | |
| 181 | /// * If the payload is `void`, then the emitted value is `{}`. | | |
| 182 | /// * If the union declares a method `pub fn jsonStringify(self: *@This(), jw: anytype) !void`, it is called to do the serialization instead of the default behavior. The given `jw` is a pointer to this `WriteStream`. | | |
| 183 | /// * Zig `enum` -> JSON string naming the active tag. | | |
| 184 | /// * If the enum declares a method `pub fn jsonStringify(self: *@This(), jw: anytype) !void`, it is called to do the serialization instead of the default behavior. The given `jw` is a pointer to this `WriteStream`. | | |
| 185 | /// * Zig untyped enum literal -> JSON string naming the active tag. | | |
| 186 | /// * Zig error -> JSON string naming the error. | | |
| 187 | /// * Zig `*T` -> the rendering of `T`. Note there is no guard against circular-reference infinite recursion. | | |
| 188 | /// | | |
| 189 | /// In `ReleaseFast` and `ReleaseSmall` mode, the given `safety_checks_hint` is ignored and is always treated as `.assumed_correct`. | 176 | /// In `ReleaseFast` and `ReleaseSmall` mode, the given `safety_checks_hint` is ignored and is always treated as `.assumed_correct`. |
| 190 | pub fn WriteStream( | 177 | pub fn WriteStream( |
| 191 | comptime OutStream: type, | 178 | comptime OutStream: type, |
| ... | @@ -197,10 +184,14 @@ pub fn WriteStream( | ... | @@ -197,10 +184,14 @@ pub fn WriteStream( |
| 197 | ) type { | 184 | ) type { |
| 198 | return struct { | 185 | return struct { |
| 199 | const Self = @This(); | 186 | const Self = @This(); |
| 200 | const safety_checks: @TypeOf(safety_checks_hint) = switch (@import("builtin").mode) { | 187 | const build_mode_has_safety = switch (@import("builtin").mode) { |
| 201 | .Debug, .ReleaseSafe => safety_checks_hint, | 188 | .Debug, .ReleaseSafe => true, |
| 202 | .ReleaseFast, .ReleaseSmall => .assumed_correct, | 189 | .ReleaseFast, .ReleaseSmall => false, |
| 203 | }; | 190 | }; |
| | 191 | const safety_checks: @TypeOf(safety_checks_hint) = if (build_mode_has_safety) |
| | 192 | safety_checks_hint |
| | 193 | else |
| | 194 | .assumed_correct; |
| 204 | | 195 | |
| 205 | pub const Stream = OutStream; | 196 | pub const Stream = OutStream; |
| 206 | pub const Error = switch (safety_checks) { | 197 | pub const Error = switch (safety_checks) { |
| ... | @@ -225,6 +216,11 @@ pub fn WriteStream( | ... | @@ -225,6 +216,11 @@ pub fn WriteStream( |
| 225 | .assumed_correct => void, | 216 | .assumed_correct => void, |
| 226 | }, | 217 | }, |
| 227 | | 218 | |
| | 219 | raw_streaming_mode: if (build_mode_has_safety) |
| | 220 | enum { none, value, objectField } |
| | 221 | else |
| | 222 | void = if (build_mode_has_safety) .none else {}, |
| | 223 | |
| 228 | pub fn init(safety_allocator: Allocator, stream: OutStream, options: StringifyOptions) Self { | 224 | pub fn init(safety_allocator: Allocator, stream: OutStream, options: StringifyOptions) Self { |
| 229 | return .{ | 225 | return .{ |
| 230 | .options = options, | 226 | .options = options, |
| ... | @@ -237,6 +233,7 @@ pub fn WriteStream( | ... | @@ -237,6 +233,7 @@ pub fn WriteStream( |
| 237 | }; | 233 | }; |
| 238 | } | 234 | } |
| 239 | | 235 | |
| | 236 | /// Only necessary with .checked_to_arbitrary_depth. |
| 240 | pub fn deinit(self: *Self) void { | 237 | pub fn deinit(self: *Self) void { |
| 241 | switch (safety_checks) { | 238 | switch (safety_checks) { |
| 242 | .checked_to_arbitrary_depth => self.nesting_stack.deinit(), | 239 | .checked_to_arbitrary_depth => self.nesting_stack.deinit(), |
| ... | @@ -246,6 +243,7 @@ pub fn WriteStream( | ... | @@ -246,6 +243,7 @@ pub fn WriteStream( |
| 246 | } | 243 | } |
| 247 | | 244 | |
| 248 | pub fn beginArray(self: *Self) Error!void { | 245 | pub fn beginArray(self: *Self) Error!void { |
| | 246 | if (build_mode_has_safety) assert(self.raw_streaming_mode == .none); |
| 249 | try self.valueStart(); | 247 | try self.valueStart(); |
| 250 | try self.stream.writeByte('['); | 248 | try self.stream.writeByte('['); |
| 251 | try self.pushIndentation(ARRAY_MODE); | 249 | try self.pushIndentation(ARRAY_MODE); |
| ... | @@ -253,6 +251,7 @@ pub fn WriteStream( | ... | @@ -253,6 +251,7 @@ pub fn WriteStream( |
| 253 | } | 251 | } |
| 254 | | 252 | |
| 255 | pub fn beginObject(self: *Self) Error!void { | 253 | pub fn beginObject(self: *Self) Error!void { |
| | 254 | if (build_mode_has_safety) assert(self.raw_streaming_mode == .none); |
| 256 | try self.valueStart(); | 255 | try self.valueStart(); |
| 257 | try self.stream.writeByte('{'); | 256 | try self.stream.writeByte('{'); |
| 258 | try self.pushIndentation(OBJECT_MODE); | 257 | try self.pushIndentation(OBJECT_MODE); |
| ... | @@ -260,6 +259,7 @@ pub fn WriteStream( | ... | @@ -260,6 +259,7 @@ pub fn WriteStream( |
| 260 | } | 259 | } |
| 261 | | 260 | |
| 262 | pub fn endArray(self: *Self) Error!void { | 261 | pub fn endArray(self: *Self) Error!void { |
| | 262 | if (build_mode_has_safety) assert(self.raw_streaming_mode == .none); |
| 263 | self.popIndentation(ARRAY_MODE); | 263 | self.popIndentation(ARRAY_MODE); |
| 264 | switch (self.next_punctuation) { | 264 | switch (self.next_punctuation) { |
| 265 | .none => {}, | 265 | .none => {}, |
| ... | @@ -273,6 +273,7 @@ pub fn WriteStream( | ... | @@ -273,6 +273,7 @@ pub fn WriteStream( |
| 273 | } | 273 | } |
| 274 | | 274 | |
| 275 | pub fn endObject(self: *Self) Error!void { | 275 | pub fn endObject(self: *Self) Error!void { |
| | 276 | if (build_mode_has_safety) assert(self.raw_streaming_mode == .none); |
| 276 | self.popIndentation(OBJECT_MODE); | 277 | self.popIndentation(OBJECT_MODE); |
| 277 | switch (self.next_punctuation) { | 278 | switch (self.next_punctuation) { |
| 278 | .none => {}, | 279 | .none => {}, |
| ... | @@ -389,16 +390,39 @@ pub fn WriteStream( | ... | @@ -389,16 +390,39 @@ pub fn WriteStream( |
| 389 | /// e.g. `"1"`, `"[]"`, `"[1,2]"`, not `"1,2"`. | 390 | /// e.g. `"1"`, `"[]"`, `"[1,2]"`, not `"1,2"`. |
| 390 | /// This function may be useful for doing your own number formatting. | 391 | /// This function may be useful for doing your own number formatting. |
| 391 | pub fn print(self: *Self, comptime fmt: []const u8, args: anytype) Error!void { | 392 | pub fn print(self: *Self, comptime fmt: []const u8, args: anytype) Error!void { |
| | 393 | if (build_mode_has_safety) assert(self.raw_streaming_mode == .none); |
| 392 | try self.valueStart(); | 394 | try self.valueStart(); |
| 393 | try self.stream.print(fmt, args); | 395 | try self.stream.print(fmt, args); |
| 394 | self.valueDone(); | 396 | self.valueDone(); |
| 395 | } | 397 | } |
| 396 | | 398 | |
| | 399 | /// An alternative to calling `write` that allows you to write directly to the `.stream` field, e.g. with `.stream.writeAll()`. |
| | 400 | /// Call `beginWriteRaw()`, then write a complete value (including any quotes if necessary) directly to the `.stream` field, |
| | 401 | /// then call `endWriteRaw()`. |
| | 402 | /// This can be useful for streaming very long strings into the output without needing it all buffered in memory. |
| | 403 | pub fn beginWriteRaw(self: *Self) !void { |
| | 404 | if (build_mode_has_safety) { |
| | 405 | assert(self.raw_streaming_mode == .none); |
| | 406 | self.raw_streaming_mode = .value; |
| | 407 | } |
| | 408 | try self.valueStart(); |
| | 409 | } |
| | 410 | |
| | 411 | /// See `beginWriteRaw`. |
| | 412 | pub fn endWriteRaw(self: *Self) void { |
| | 413 | if (build_mode_has_safety) { |
| | 414 | assert(self.raw_streaming_mode == .value); |
| | 415 | self.raw_streaming_mode = .none; |
| | 416 | } |
| | 417 | self.valueDone(); |
| | 418 | } |
| | 419 | |
| 397 | /// See `WriteStream` for when to call this method. | 420 | /// See `WriteStream` for when to call this method. |
| 398 | /// `key` is the string content of the property name. | 421 | /// `key` is the string content of the property name. |
| 399 | /// Surrounding quotes will be added and any special characters will be escaped. | 422 | /// Surrounding quotes will be added and any special characters will be escaped. |
| 400 | /// See also `objectFieldRaw`. | 423 | /// See also `objectFieldRaw`. |
| 401 | pub fn objectField(self: *Self, key: []const u8) Error!void { | 424 | pub fn objectField(self: *Self, key: []const u8) Error!void { |
| | 425 | if (build_mode_has_safety) assert(self.raw_streaming_mode == .none); |
| 402 | try self.objectFieldStart(); | 426 | try self.objectFieldStart(); |
| 403 | try encodeJsonString(key, self.options, self.stream); | 427 | try encodeJsonString(key, self.options, self.stream); |
| 404 | self.next_punctuation = .colon; | 428 | self.next_punctuation = .colon; |
| ... | @@ -408,14 +432,65 @@ pub fn WriteStream( | ... | @@ -408,14 +432,65 @@ pub fn WriteStream( |
| 408 | /// A few assertions are performed on the given value to ensure that the caller of this function understands the API contract. | 432 | /// A few assertions are performed on the given value to ensure that the caller of this function understands the API contract. |
| 409 | /// See also `objectField`. | 433 | /// See also `objectField`. |
| 410 | pub fn objectFieldRaw(self: *Self, quoted_key: []const u8) Error!void { | 434 | pub fn objectFieldRaw(self: *Self, quoted_key: []const u8) Error!void { |
| | 435 | if (build_mode_has_safety) assert(self.raw_streaming_mode == .none); |
| 411 | assert(quoted_key.len >= 2 and quoted_key[0] == '"' and quoted_key[quoted_key.len - 1] == '"'); // quoted_key should be "quoted". | 436 | assert(quoted_key.len >= 2 and quoted_key[0] == '"' and quoted_key[quoted_key.len - 1] == '"'); // quoted_key should be "quoted". |
| 412 | try self.objectFieldStart(); | 437 | try self.objectFieldStart(); |
| 413 | try self.stream.writeAll(quoted_key); | 438 | try self.stream.writeAll(quoted_key); |
| 414 | self.next_punctuation = .colon; | 439 | self.next_punctuation = .colon; |
| 415 | } | 440 | } |
| 416 | | 441 | |
| 417 | /// See `WriteStream`. | 442 | /// In the rare case that you need to write very long object field names, |
| | 443 | /// this is an alternative to `objectField` and `objectFieldRaw` that allows you to write directly to the `.stream` field |
| | 444 | /// similar to `beginWriteRaw`. |
| | 445 | /// Call `endObjectFieldRaw()` when you're done. |
| | 446 | pub fn beginObjectFieldRaw(self: *Self) !void { |
| | 447 | if (build_mode_has_safety) { |
| | 448 | assert(self.raw_streaming_mode == .none); |
| | 449 | self.raw_streaming_mode = .objectField; |
| | 450 | } |
| | 451 | try self.objectFieldStart(); |
| | 452 | } |
| | 453 | |
| | 454 | /// See `beginObjectFieldRaw`. |
| | 455 | pub fn endObjectFieldRaw(self: *Self) void { |
| | 456 | if (build_mode_has_safety) { |
| | 457 | assert(self.raw_streaming_mode == .objectField); |
| | 458 | self.raw_streaming_mode = .none; |
| | 459 | } |
| | 460 | self.next_punctuation = .colon; |
| | 461 | } |
| | 462 | |
| | 463 | /// Renders the given Zig value as JSON. |
| | 464 | /// |
| | 465 | /// Supported types: |
| | 466 | /// * Zig `bool` -> JSON `true` or `false`. |
| | 467 | /// * Zig `?T` -> `null` or the rendering of `T`. |
| | 468 | /// * Zig `i32`, `u64`, etc. -> JSON number or string. |
| | 469 | /// * When option `emit_nonportable_numbers_as_strings` is true, if the value is outside the range `+-1<<53` (the precise integer range of f64), it is rendered as a JSON string in base 10. Otherwise, it is rendered as JSON number. |
| | 470 | /// * Zig floats -> JSON number or string. |
| | 471 | /// * If the value cannot be precisely represented by an f64, it is rendered as a JSON string. Otherwise, it is rendered as JSON number. |
| | 472 | /// * TODO: Float rendering will likely change in the future, e.g. to remove the unnecessary "e+00". |
| | 473 | /// * Zig `[]const u8`, `[]u8`, `*[N]u8`, `@Vector(N, u8)`, and similar -> JSON string. |
| | 474 | /// * See `StringifyOptions.emit_strings_as_arrays`. |
| | 475 | /// * If the content is not valid UTF-8, rendered as an array of numbers instead. |
| | 476 | /// * Zig `[]T`, `[N]T`, `*[N]T`, `@Vector(N, T)`, and similar -> JSON array of the rendering of each item. |
| | 477 | /// * Zig tuple -> JSON array of the rendering of each item. |
| | 478 | /// * Zig `struct` -> JSON object with each field in declaration order. |
| | 479 | /// * If the struct declares a method `pub fn jsonStringify(self: *@This(), jw: anytype) !void`, it is called to do the serialization instead of the default behavior. The given `jw` is a pointer to this `WriteStream`. See `std.json.Value` for an example. |
| | 480 | /// * See `StringifyOptions.emit_null_optional_fields`. |
| | 481 | /// * Zig `union(enum)` -> JSON object with one field named for the active tag and a value representing the payload. |
| | 482 | /// * If the payload is `void`, then the emitted value is `{}`. |
| | 483 | /// * If the union declares a method `pub fn jsonStringify(self: *@This(), jw: anytype) !void`, it is called to do the serialization instead of the default behavior. The given `jw` is a pointer to this `WriteStream`. |
| | 484 | /// * Zig `enum` -> JSON string naming the active tag. |
| | 485 | /// * If the enum declares a method `pub fn jsonStringify(self: *@This(), jw: anytype) !void`, it is called to do the serialization instead of the default behavior. The given `jw` is a pointer to this `WriteStream`. |
| | 486 | /// * Zig untyped enum literal -> JSON string naming the active tag. |
| | 487 | /// * Zig error -> JSON string naming the error. |
| | 488 | /// * Zig `*T` -> the rendering of `T`. Note there is no guard against circular-reference infinite recursion. |
| | 489 | /// |
| | 490 | /// See also alternative functions `print` and `beginWriteRaw`. |
| | 491 | /// For writing object field names, use `objectField` instead. |
| 418 | pub fn write(self: *Self, value: anytype) Error!void { | 492 | pub fn write(self: *Self, value: anytype) Error!void { |
| | 493 | if (build_mode_has_safety) assert(self.raw_streaming_mode == .none); |
| 419 | const T = @TypeOf(value); | 494 | const T = @TypeOf(value); |
| 420 | switch (@typeInfo(T)) { | 495 | switch (@typeInfo(T)) { |
| 421 | .Int => { | 496 | .Int => { |