atproto utils for zig zat.dev
atproto sdk zig
26
fork

Configure Feed

Select the types of activity you want to include in your feed.

add comptime json extraction (extractAt, extractAtOptional)

zzstoatzz cba652f3 3fcb073a

+119
+119
src/internal/json.zig
··· 2 2 //! 3 3 //! simplifies navigating nested json structures. 4 4 //! eliminates the verbose nested if-checks. 5 + //! 6 + //! two approaches: 7 + //! - runtime paths: getString(value, "embed.external.uri") - for dynamic paths 8 + //! - comptime paths: extractAt(T, alloc, value, .{"embed", "external"}) - for static paths with type safety 5 9 6 10 const std = @import("std"); 7 11 ··· 83 87 }; 84 88 } 85 89 90 + // === comptime path extraction === 91 + 92 + /// extract a typed struct from a nested path 93 + /// uses comptime tuple for path segments - no runtime string parsing 94 + /// leverages std.json.parseFromValueLeaky for type-safe extraction 95 + pub fn extractAt( 96 + comptime T: type, 97 + allocator: std.mem.Allocator, 98 + value: std.json.Value, 99 + comptime path: anytype, 100 + ) std.json.ParseFromValueError!T { 101 + var current = value; 102 + inline for (path) |segment| { 103 + current = switch (current) { 104 + .object => |obj| obj.get(segment) orelse return error.MissingField, 105 + else => return error.UnexpectedToken, 106 + }; 107 + } 108 + return std.json.parseFromValueLeaky(T, allocator, current, .{}); 109 + } 110 + 111 + /// extract a typed value, returning null if path doesn't exist 112 + pub fn extractAtOptional( 113 + comptime T: type, 114 + allocator: std.mem.Allocator, 115 + value: std.json.Value, 116 + comptime path: anytype, 117 + ) ?T { 118 + return extractAt(T, allocator, value, path) catch null; 119 + } 120 + 86 121 // === tests === 87 122 88 123 test "getPath simple" { ··· 151 186 const title = getString(parsed.value, "embed.external.title"); 152 187 try std.testing.expectEqualStrings("Tangled", title.?); 153 188 } 189 + 190 + // === comptime extraction tests === 191 + 192 + test "extractAt struct" { 193 + const json_str = 194 + \\{ 195 + \\ "embed": { 196 + \\ "external": { 197 + \\ "uri": "https://tangled.sh", 198 + \\ "title": "Tangled" 199 + \\ } 200 + \\ } 201 + \\} 202 + ; 203 + const parsed = try std.json.parseFromSlice(std.json.Value, std.testing.allocator, json_str, .{}); 204 + defer parsed.deinit(); 205 + 206 + const External = struct { 207 + uri: []const u8, 208 + title: []const u8, 209 + }; 210 + 211 + const ext = try extractAt(External, std.testing.allocator, parsed.value, .{ "embed", "external" }); 212 + try std.testing.expectEqualStrings("https://tangled.sh", ext.uri); 213 + try std.testing.expectEqualStrings("Tangled", ext.title); 214 + } 215 + 216 + test "extractAt with optional fields" { 217 + const json_str = 218 + \\{ 219 + \\ "user": { 220 + \\ "name": "alice", 221 + \\ "age": 30 222 + \\ } 223 + \\} 224 + ; 225 + const parsed = try std.json.parseFromSlice(std.json.Value, std.testing.allocator, json_str, .{}); 226 + defer parsed.deinit(); 227 + 228 + const User = struct { 229 + name: []const u8, 230 + age: i64, 231 + bio: ?[]const u8 = null, 232 + }; 233 + 234 + const user = try extractAt(User, std.testing.allocator, parsed.value, .{"user"}); 235 + try std.testing.expectEqualStrings("alice", user.name); 236 + try std.testing.expectEqual(@as(i64, 30), user.age); 237 + try std.testing.expect(user.bio == null); 238 + } 239 + 240 + test "extractAt empty path extracts root" { 241 + const json_str = 242 + \\{"name": "root", "value": 42} 243 + ; 244 + const parsed = try std.json.parseFromSlice(std.json.Value, std.testing.allocator, json_str, .{}); 245 + defer parsed.deinit(); 246 + 247 + const Root = struct { 248 + name: []const u8, 249 + value: i64, 250 + }; 251 + 252 + const root = try extractAt(Root, std.testing.allocator, parsed.value, .{}); 253 + try std.testing.expectEqualStrings("root", root.name); 254 + try std.testing.expectEqual(@as(i64, 42), root.value); 255 + } 256 + 257 + test "extractAtOptional returns null on missing path" { 258 + const json_str = 259 + \\{"exists": {"value": 1}} 260 + ; 261 + const parsed = try std.json.parseFromSlice(std.json.Value, std.testing.allocator, json_str, .{}); 262 + defer parsed.deinit(); 263 + 264 + const Thing = struct { value: i64 }; 265 + 266 + const exists = extractAtOptional(Thing, std.testing.allocator, parsed.value, .{"exists"}); 267 + try std.testing.expect(exists != null); 268 + try std.testing.expectEqual(@as(i64, 1), exists.?.value); 269 + 270 + const missing = extractAtOptional(Thing, std.testing.allocator, parsed.value, .{"missing"}); 271 + try std.testing.expect(missing == null); 272 + }