atproto utils for zig zat.dev
atproto sdk zig

feat: extractAt ignores unknown JSON fields by default

enables partial extraction from JSON with extra fields, which is common
when parsing external API responses (e.g., TAP firehose messages have
live, rev, cid fields we don't need).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

Changed files
+36 -1
src
internal
+36 -1
src/internal/json.zig
··· 125 }, 126 }; 127 } 128 - return std.json.parseFromValueLeaky(T, allocator, current, .{}) catch |err| { 129 log.debug("extractAt: parse failed for {s} at path {any}: {s} (json type: {s})", .{ 130 @typeName(T), 131 path, ··· 347 const result = extractAtOptional(Thing, arena.allocator(), parsed.value, .{ "data", "missing" }); 348 try std.testing.expect(result == null); 349 }
··· 125 }, 126 }; 127 } 128 + return std.json.parseFromValueLeaky(T, allocator, current, .{ .ignore_unknown_fields = true }) catch |err| { 129 log.debug("extractAt: parse failed for {s} at path {any}: {s} (json type: {s})", .{ 130 @typeName(T), 131 path, ··· 347 const result = extractAtOptional(Thing, arena.allocator(), parsed.value, .{ "data", "missing" }); 348 try std.testing.expect(result == null); 349 } 350 + 351 + test "extractAt ignores unknown fields" { 352 + // real-world case: TAP messages have extra fields (live, rev, cid) that we don't need 353 + const json_str = 354 + \\{ 355 + \\ "record": { 356 + \\ "live": true, 357 + \\ "did": "did:plc:abc123", 358 + \\ "rev": "3mbspmpaidl2a", 359 + \\ "collection": "pub.leaflet.document", 360 + \\ "rkey": "xyz789", 361 + \\ "action": "create", 362 + \\ "cid": "bafyreitest" 363 + \\ } 364 + \\} 365 + ; 366 + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); 367 + defer arena.deinit(); 368 + 369 + const parsed = try std.json.parseFromSlice(std.json.Value, arena.allocator(), json_str, .{}); 370 + 371 + // only extract the fields we care about 372 + const Record = struct { 373 + collection: []const u8, 374 + action: []const u8, 375 + did: []const u8, 376 + rkey: []const u8, 377 + }; 378 + 379 + const rec = try extractAt(Record, arena.allocator(), parsed.value, .{"record"}); 380 + try std.testing.expectEqualStrings("pub.leaflet.document", rec.collection); 381 + try std.testing.expectEqualStrings("create", rec.action); 382 + try std.testing.expectEqualStrings("did:plc:abc123", rec.did); 383 + try std.testing.expectEqualStrings("xyz789", rec.rkey); 384 + }