Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions lib/std/json.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
7 changes: 4 additions & 3 deletions lib/std/json/Stringify.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
36 changes: 27 additions & 9 deletions lib/std/json/dynamic.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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;
Expand Down Expand Up @@ -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();
Expand All @@ -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));
Expand All @@ -104,18 +121,18 @@ 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 },
});
},
else => unreachable,
}
},
.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,

Expand All @@ -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]) {
Expand All @@ -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 => {},
Expand Down Expand Up @@ -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,
Expand Down
11 changes: 6 additions & 5 deletions lib/std/json/dynamic_test.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -225,17 +225,18 @@ 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 },
.{ .integer = 42 },
.{ .number_string = "43" },
.{ .float = 42 },
.{ .string = "weeee" },
.{ .array = Array.fromOwnedSlice(undefined, &vals) },
.{ .array = Array.fromOwnedSlice(&vals) },
.{ .object = obj },
};
var buffer: [0x1000]u8 = undefined;
Expand Down
7 changes: 5 additions & 2 deletions lib/std/json/hashmap.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
Expand Down
35 changes: 25 additions & 10 deletions lib/std/json/static.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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);
}

Expand All @@ -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.*;

Expand Down