diff --git a/lib/std/json.zig b/lib/std/json.zig index 23f857680500..00a5298fb9cc 100644 --- a/lib/std/json.zig +++ b/lib/std/json.zig @@ -60,6 +60,8 @@ test Stringify { try testing.expectEqualSlices(u8, expected, out.written()); } +pub const ObjectMapManaged = @import("json/dynamic.zig").ObjectMapManaged; +pub const ArrayManaged = @import("json/dynamic.zig").ArrayManaged; pub const ObjectMap = @import("json/dynamic.zig").ObjectMap; pub const Array = @import("json/dynamic.zig").Array; pub const Value = @import("json/dynamic.zig").Value; diff --git a/lib/std/json/Stringify.zig b/lib/std/json/Stringify.zig index 31413bf7248b..0cc4bd08cfce 100644 --- a/lib/std/json/Stringify.zig +++ b/lib/std/json/Stringify.zig @@ -770,9 +770,10 @@ fn testBasicWriteStream(w: *Stringify) !void { } fn getJsonObject(allocator: std.mem.Allocator) !std.json.Value { - var v: std.json.Value = .{ .object = std.json.ObjectMap.init(allocator) }; - try v.object.put("one", std.json.Value{ .integer = @as(i64, @intCast(1)) }); - try v.object.put("two", std.json.Value{ .float = 2.0 }); + const obj = std.json.ObjectMap.empty; + var v: std.json.Value = .{ .object = obj }; + try v.object.put(allocator, "one", std.json.Value{ .integer = @as(i64, @intCast(1)) }); + try v.object.put(allocator, "two", std.json.Value{ .float = 2.0 }); return v; } diff --git a/lib/std/json/dynamic.zig b/lib/std/json/dynamic.zig index 8aacf42865ca..32499b6cdd16 100644 --- a/lib/std/json/dynamic.zig +++ b/lib/std/json/dynamic.zig @@ -10,8 +10,11 @@ const ParseError = @import("./static.zig").ParseError; const isNumberFormattedLikeAnInteger = @import("Scanner.zig").isNumberFormattedLikeAnInteger; -pub const ObjectMap = StringArrayHashMap(Value); -pub const Array = std.array_list.Managed(Value); +pub const ObjectMap = std.StringArrayHashMapUnmanaged(Value); +pub const Array = std.ArrayList(Value); + +pub const ObjectMapManaged = StringArrayHashMap(Value); +pub const ArrayManaged = std.array_list.Managed(Value); /// Represents any JSON value, potentially containing other JSON values. /// A .float value may be an approximation of the original value. @@ -27,6 +30,9 @@ pub const Value = union(enum) { array: Array, object: ObjectMap, + array_managed: *ArrayManaged, + object_managed: *ObjectMapManaged, + pub fn parseFromNumberSlice(s: []const u8) Value { if (!isNumberFormattedLikeAnInteger(s)) { const f = std.fmt.parseFloat(f64, s) catch unreachable; @@ -62,6 +68,7 @@ pub const Value = union(enum) { .number_string => |inner| try jws.print("{s}", .{inner}), .string => |inner| try jws.write(inner), .array => |inner| try jws.write(inner.items), + .array_managed => |inner| try jws.write(inner.items), .object => |inner| { try jws.beginObject(); var it = inner.iterator(); @@ -71,17 +78,27 @@ pub const Value = union(enum) { } try jws.endObject(); }, + .object_managed => |inner| { + try jws.beginObject(); + var it = inner.iterator(); + while (it.next()) |entry| { + try jws.objectField(entry.key_ptr.*); + try jws.write(entry.value_ptr.*); + } + try jws.endObject(); + }, } } pub fn jsonParse(allocator: Allocator, source: anytype, options: ParseOptions) ParseError(@TypeOf(source.*))!@This() { // The grammar of the stack is: // (.array | .object .string)* - var stack = Array.init(allocator); + var stack = ArrayManaged.init(allocator); defer stack.deinit(); while (true) { // Assert the stack grammar at the top of the stack. + // This doesn't need to check for managed, since we only use unmanaged here debug.assert(stack.items.len == 0 or stack.items[stack.items.len - 1] == .array or (stack.items[stack.items.len - 2] == .object and stack.items[stack.items.len - 1] == .string)); @@ -104,10 +121,10 @@ pub const Value = union(enum) { .object_begin => { switch (try source.nextAllocMax(allocator, .alloc_always, options.max_value_len.?)) { - .object_end => return try handleCompleteValue(&stack, allocator, source, Value{ .object = ObjectMap.init(allocator) }, options) orelse continue, + .object_end => return try handleCompleteValue(&stack, allocator, source, Value{ .object = ObjectMap.empty }, options) orelse continue, .allocated_string => |key| { try stack.appendSlice(&[_]Value{ - Value{ .object = ObjectMap.init(allocator) }, + Value{ .object = .empty }, Value{ .string = key }, }); }, @@ -115,7 +132,7 @@ pub const Value = union(enum) { } }, .array_begin => { - try stack.append(Value{ .array = Array.init(allocator) }); + try stack.append(Value{ .array = .empty }); }, .array_end => return try handleCompleteValue(&stack, allocator, source, stack.pop().?, options) orelse continue, @@ -131,11 +148,12 @@ pub const Value = union(enum) { } }; -fn handleCompleteValue(stack: *Array, allocator: Allocator, source: anytype, value_: Value, options: ParseOptions) !?Value { +fn handleCompleteValue(stack: *ArrayManaged, allocator: Allocator, source: anytype, value_: Value, options: ParseOptions) !?Value { if (stack.items.len == 0) return value_; var value = value_; while (true) { // Assert the stack grammar at the top of the stack. + // This doesn't need to check for managed, since we only use unmanaged here debug.assert(stack.items[stack.items.len - 1] == .array or (stack.items[stack.items.len - 2] == .object and stack.items[stack.items.len - 1] == .string)); switch (stack.items[stack.items.len - 1]) { @@ -146,7 +164,7 @@ fn handleCompleteValue(stack: *Array, allocator: Allocator, source: anytype, val // stack: [..., .object] var object = &stack.items[stack.items.len - 1].object; - const gop = try object.getOrPut(key); + const gop = try object.getOrPut(allocator, key); if (gop.found_existing) { switch (options.duplicate_field_behavior) { .use_first => {}, @@ -180,7 +198,7 @@ fn handleCompleteValue(stack: *Array, allocator: Allocator, source: anytype, val }, .array => |*array| { // stack: [..., .array] - try array.append(value); + try array.append(allocator, value); return null; }, else => unreachable, diff --git a/lib/std/json/dynamic_test.zig b/lib/std/json/dynamic_test.zig index d2d318d53715..1c6ab3494302 100644 --- a/lib/std/json/dynamic_test.zig +++ b/lib/std/json/dynamic_test.zig @@ -129,7 +129,7 @@ test "Value.array allocator should still be usable after parsing" { // Allocation should succeed var i: usize = 0; while (i < 100) : (i += 1) { - try parsed.value.array.append(Value{ .integer = 100 }); + try parsed.value.array.append(parsed.arena.allocator(), Value{ .integer = 100 }); } try testing.expectEqual(parsed.value.array.items.len, 100); } @@ -225,9 +225,10 @@ test "Value.jsonStringify" { .{ .integer = 2 }, .{ .number_string = "3" }, }; - var obj = ObjectMap.init(testing.allocator); - defer obj.deinit(); - try obj.putNoClobber("a", .{ .string = "b" }); + const allocator = testing.allocator; + var obj = ObjectMap.empty; + defer obj.deinit(allocator); + try obj.putNoClobber(allocator, "a", .{ .string = "b" }); const array = [_]Value{ .null, .{ .bool = true }, @@ -235,7 +236,7 @@ test "Value.jsonStringify" { .{ .number_string = "43" }, .{ .float = 42 }, .{ .string = "weeee" }, - .{ .array = Array.fromOwnedSlice(undefined, &vals) }, + .{ .array = Array.fromOwnedSlice(&vals) }, .{ .object = obj }, }; var buffer: [0x1000]u8 = undefined; diff --git a/lib/std/json/hashmap.zig b/lib/std/json/hashmap.zig index be4e9bd2dd5a..cd68cac523f5 100644 --- a/lib/std/json/hashmap.zig +++ b/lib/std/json/hashmap.zig @@ -50,12 +50,15 @@ pub fn ArrayHashMap(comptime T: type) type { } pub fn jsonParseFromValue(allocator: Allocator, source: Value, options: ParseOptions) !@This() { - if (source != .object) return error.UnexpectedToken; + var it = switch (source) { + .object => |inner| inner.iterator(), + .object_managed => |inner| inner.iterator(), + else => return error.UnexpectedToken, + }; var map: std.StringArrayHashMapUnmanaged(T) = .empty; errdefer map.deinit(allocator); - var it = source.object.iterator(); while (it.next()) |kv| { try map.put(allocator, kv.key_ptr.*, try innerParseFromValue(T, allocator, kv.value_ptr.*, options)); } diff --git a/lib/std/json/static.zig b/lib/std/json/static.zig index ac2b1954b274..92cb1874243d 100644 --- a/lib/std/json/static.zig +++ b/lib/std/json/static.zig @@ -606,19 +606,27 @@ pub fn innerParseFromValue( if (unionInfo.tag_type == null) @compileError("Unable to parse into untagged union '" ++ @typeName(T) ++ "'"); - if (source != .object) return error.UnexpectedToken; - if (source.object.count() != 1) return error.UnexpectedToken; + const obj = switch (source) { + .object => |inner| inner, + .object_managed => |inner| inner.unmanaged, + else => return error.UnexpectedToken, + }; + if (obj.count() != 1) return error.UnexpectedToken; - var it = source.object.iterator(); + var it = obj.iterator(); const kv = it.next().?; const field_name = kv.key_ptr.*; inline for (unionInfo.fields) |u_field| { if (std.mem.eql(u8, u_field.name, field_name)) { if (u_field.type == void) { + const count = switch (kv.value_ptr.*) { + .object => |inner| inner.count(), + .object_managed => |inner| inner.count(), + else => return error.UnexpectedToken, + }; // void isn't really a json type, but we can support void payload union tags with {} as a value. - if (kv.value_ptr.* != .object) return error.UnexpectedToken; - if (kv.value_ptr.*.object.count() != 0) return error.UnexpectedToken; + if (count != 0) return error.UnexpectedToken; return @unionInit(T, u_field.name, {}); } // Recurse. @@ -631,11 +639,15 @@ pub fn innerParseFromValue( .@"struct" => |structInfo| { if (structInfo.is_tuple) { - if (source != .array) return error.UnexpectedToken; - if (source.array.items.len != structInfo.fields.len) return error.UnexpectedToken; + const items = switch (source) { + .array => |inner| inner.items, + .array_managed => |inner| inner.items, + else => return error.UnexpectedToken, + }; + if (items.len != structInfo.fields.len) return error.UnexpectedToken; var r: T = undefined; - inline for (0..structInfo.fields.len, source.array.items) |i, item| { + inline for (0..structInfo.fields.len, items) |i, item| { r[i] = try innerParseFromValue(structInfo.fields[i].type, allocator, item, options); } @@ -646,12 +658,15 @@ pub fn innerParseFromValue( return T.jsonParseFromValue(allocator, source, options); } - if (source != .object) return error.UnexpectedToken; + var it = switch (source) { + .object => |inner| inner.iterator(), + .object_managed => |inner| inner.iterator(), + else => return error.UnexpectedToken, + }; var r: T = undefined; var fields_seen = [_]bool{false} ** structInfo.fields.len; - var it = source.object.iterator(); while (it.next()) |kv| { const field_name = kv.key_ptr.*;