Commit 8f7b50e2c4

Techatrix <techatrix@mailbox.org>
2024-06-26 23:37:11
json: respect duplicate_field_behavior in std.json.Value.jsonParse
1 parent fe66a12
Changed files (3)
lib/std/json/dynamic.zig
@@ -147,7 +147,19 @@ fn handleCompleteValue(stack: *Array, allocator: Allocator, source: anytype, val
 
                 // stack: [..., .object]
                 var object = &stack.items[stack.items.len - 1].object;
-                try object.put(key, value);
+
+                const gop = try object.getOrPut(key);
+                if (gop.found_existing) {
+                    switch (options.duplicate_field_behavior) {
+                        .use_first => {},
+                        .@"error" => return error.DuplicateField,
+                        .use_last => {
+                            gop.value_ptr.* = value;
+                        },
+                    }
+                } else {
+                    gop.value_ptr.* = value;
+                }
 
                 // This is an invalid state to leave the stack in,
                 // so we have to process the next token before we return.
lib/std/json/dynamic_test.zig
@@ -181,6 +181,34 @@ test "escaped characters" {
     try testing.expectEqualSlices(u8, obj.get("surrogatepair").?.string, "๐Ÿ˜‚");
 }
 
+test "Value with duplicate fields" {
+    var arena_allocator = std.heap.ArenaAllocator.init(std.testing.allocator);
+    defer arena_allocator.deinit();
+
+    const doc =
+        \\{
+        \\  "abc": 0,
+        \\  "abc": 1
+        \\}
+    ;
+
+    try testing.expectError(error.DuplicateField, parseFromSliceLeaky(std.json.Value, arena_allocator.allocator(), doc, .{
+        .duplicate_field_behavior = .@"error",
+    }));
+
+    const first = try parseFromSliceLeaky(std.json.Value, arena_allocator.allocator(), doc, .{
+        .duplicate_field_behavior = .use_first,
+    });
+    try testing.expectEqual(@as(usize, 1), first.object.count());
+    try testing.expectEqual(@as(i64, 0), first.object.get("abc").?.integer);
+
+    const last = try parseFromSliceLeaky(std.json.Value, arena_allocator.allocator(), doc, .{
+        .duplicate_field_behavior = .use_last,
+    });
+    try testing.expectEqual(@as(usize, 1), last.object.count());
+    try testing.expectEqual(@as(i64, 1), last.object.get("abc").?.integer);
+}
+
 test "Value.jsonStringify" {
     var vals = [_]Value{
         .{ .integer = 1 },
lib/std/json/test.zig
@@ -28,7 +28,9 @@ fn testLowLevelScanner(s: []const u8) !void {
     }
 }
 fn testHighLevelDynamicParser(s: []const u8) !void {
-    var parsed = try parseFromSlice(Value, testing.allocator, s, .{});
+    var parsed = try parseFromSlice(Value, testing.allocator, s, .{
+        .duplicate_field_behavior = .use_first,
+    });
     defer parsed.deinit();
 }