| ... | ... | @@ -34,6 +34,14 @@ pub const ParseOptions = struct { |
| 34 | 34 | /// The default for `parseFromTokenSource` with a `*std.json.Reader` is `std.json.default_max_value_len`. |
| 35 | 35 | /// Ignored for `parseFromValue` and `parseFromValueLeaky`. |
| 36 | 36 | max_value_len: ?usize = null, |
| 37 | |
| 38 | /// This determines whether strings should always be copied, |
| 39 | /// or if a reference to the given buffer should be preferred if possible. |
| 40 | /// The default for `parseFromSlice` or `parseFromTokenSource` with a `*std.json.Scanner` input |
| 41 | /// is `.alloc_if_needed`. |
| 42 | /// The default with a `*std.json.Reader` input is `.alloc_always`. |
| 43 | /// Ignored for `parseFromValue` and `parseFromValueLeaky`. |
| 44 | allocate: ?AllocWhen = null, |
| 37 | 45 | }; |
| 38 | 46 | |
| 39 | 47 | pub fn Parsed(comptime T: type) type { |
| ... | ... | @@ -113,7 +121,6 @@ pub fn parseFromTokenSourceLeaky( |
| 113 | 121 | if (@TypeOf(scanner_or_reader.*) == Scanner) { |
| 114 | 122 | assert(scanner_or_reader.is_end_of_input); |
| 115 | 123 | } |
| 116 | | |
| 117 | 124 | var resolved_options = options; |
| 118 | 125 | if (resolved_options.max_value_len == null) { |
| 119 | 126 | if (@TypeOf(scanner_or_reader.*) == Scanner) { |
| ... | ... | @@ -122,8 +129,15 @@ pub fn parseFromTokenSourceLeaky( |
| 122 | 129 | resolved_options.max_value_len = default_max_value_len; |
| 123 | 130 | } |
| 124 | 131 | } |
| 132 | if (resolved_options.allocate == null) { |
| 133 | if (@TypeOf(scanner_or_reader.*) == Scanner) { |
| 134 | resolved_options.allocate = .alloc_if_needed; |
| 135 | } else { |
| 136 | resolved_options.allocate = .alloc_always; |
| 137 | } |
| 138 | } |
| 125 | 139 | |
| 126 | | const value = try internalParse(T, allocator, scanner_or_reader, resolved_options); |
| 140 | const value = try innerParse(T, allocator, scanner_or_reader, resolved_options); |
| 127 | 141 | |
| 128 | 142 | assert(.end_of_document == try scanner_or_reader.next()); |
| 129 | 143 | |
| ... | ... | @@ -181,7 +195,14 @@ pub const ParseFromValueError = std.fmt.ParseIntError || std.fmt.ParseFloatError |
| 181 | 195 | LengthMismatch, |
| 182 | 196 | }; |
| 183 | 197 | |
| 184 | | fn internalParse( |
| 198 | /// This is an internal function called recursively |
| 199 | /// during the implementation of `parseFromTokenSourceLeaky` and similar. |
| 200 | /// It is exposed primarily to enable custom `jsonParse()` methods to call back into the `parseFrom*` system, |
| 201 | /// such as if you're implementing a custom container of type `T`; |
| 202 | /// you can call `internalParse(T, ...)` for each of the container's items. |
| 203 | /// Note that `null` fields are not allowed on the `options` when calling this function. |
| 204 | /// (The `options` you get in your `jsonParse` method has no `null` fields.) |
| 205 | pub fn innerParse( |
| 185 | 206 | comptime T: type, |
| 186 | 207 | allocator: Allocator, |
| 187 | 208 | source: anytype, |
| ... | ... | @@ -220,7 +241,7 @@ fn internalParse( |
| 220 | 241 | return null; |
| 221 | 242 | }, |
| 222 | 243 | else => { |
| 223 | | return try internalParse(optionalInfo.child, allocator, source, options); |
| 244 | return try innerParse(optionalInfo.child, allocator, source, options); |
| 224 | 245 | }, |
| 225 | 246 | } |
| 226 | 247 | }, |
| ... | ... | @@ -250,16 +271,17 @@ fn internalParse( |
| 250 | 271 | var name_token: ?Token = try source.nextAllocMax(allocator, .alloc_if_needed, options.max_value_len.?); |
| 251 | 272 | const field_name = switch (name_token.?) { |
| 252 | 273 | inline .string, .allocated_string => |slice| slice, |
| 253 | | else => return error.UnexpectedToken, |
| 274 | else => { |
| 275 | return error.UnexpectedToken; |
| 276 | }, |
| 254 | 277 | }; |
| 255 | 278 | |
| 256 | 279 | inline for (unionInfo.fields) |u_field| { |
| 257 | 280 | if (std.mem.eql(u8, u_field.name, field_name)) { |
| 258 | 281 | // Free the name token now in case we're using an allocator that optimizes freeing the last allocated object. |
| 259 | | // (Recursing into internalParse() might trigger more allocations.) |
| 282 | // (Recursing into innerParse() might trigger more allocations.) |
| 260 | 283 | freeAllocated(allocator, name_token.?); |
| 261 | 284 | name_token = null; |
| 262 | | |
| 263 | 285 | if (u_field.type == void) { |
| 264 | 286 | // void isn't really a json type, but we can support void payload union tags with {} as a value. |
| 265 | 287 | if (.object_begin != try source.next()) return error.UnexpectedToken; |
| ... | ... | @@ -267,7 +289,7 @@ fn internalParse( |
| 267 | 289 | result = @unionInit(T, u_field.name, {}); |
| 268 | 290 | } else { |
| 269 | 291 | // Recurse. |
| 270 | | result = @unionInit(T, u_field.name, try internalParse(u_field.type, allocator, source, options)); |
| 292 | result = @unionInit(T, u_field.name, try innerParse(u_field.type, allocator, source, options)); |
| 271 | 293 | } |
| 272 | 294 | break; |
| 273 | 295 | } |
| ... | ... | @@ -287,7 +309,7 @@ fn internalParse( |
| 287 | 309 | |
| 288 | 310 | var r: T = undefined; |
| 289 | 311 | inline for (0..structInfo.fields.len) |i| { |
| 290 | | r[i] = try internalParse(structInfo.fields[i].type, allocator, source, options); |
| 312 | r[i] = try innerParse(structInfo.fields[i].type, allocator, source, options); |
| 291 | 313 | } |
| 292 | 314 | |
| 293 | 315 | if (.array_end != try source.next()) return error.UnexpectedToken; |
| ... | ... | @@ -307,32 +329,35 @@ fn internalParse( |
| 307 | 329 | while (true) { |
| 308 | 330 | var name_token: ?Token = try source.nextAllocMax(allocator, .alloc_if_needed, options.max_value_len.?); |
| 309 | 331 | const field_name = switch (name_token.?) { |
| 310 | | .object_end => break, // No more fields. |
| 311 | 332 | inline .string, .allocated_string => |slice| slice, |
| 312 | | else => return error.UnexpectedToken, |
| 333 | .object_end => { // No more fields. |
| 334 | break; |
| 335 | }, |
| 336 | else => { |
| 337 | return error.UnexpectedToken; |
| 338 | }, |
| 313 | 339 | }; |
| 314 | 340 | |
| 315 | 341 | inline for (structInfo.fields, 0..) |field, i| { |
| 316 | 342 | if (field.is_comptime) @compileError("comptime fields are not supported: " ++ @typeName(T) ++ "." ++ field.name); |
| 317 | 343 | if (std.mem.eql(u8, field.name, field_name)) { |
| 318 | 344 | // Free the name token now in case we're using an allocator that optimizes freeing the last allocated object. |
| 319 | | // (Recursing into internalParse() might trigger more allocations.) |
| 345 | // (Recursing into innerParse() might trigger more allocations.) |
| 320 | 346 | freeAllocated(allocator, name_token.?); |
| 321 | 347 | name_token = null; |
| 322 | | |
| 323 | 348 | if (fields_seen[i]) { |
| 324 | 349 | switch (options.duplicate_field_behavior) { |
| 325 | 350 | .use_first => { |
| 326 | 351 | // Parse and ignore the redundant value. |
| 327 | 352 | // We don't want to skip the value, because we want type checking. |
| 328 | | _ = try internalParse(field.type, allocator, source, options); |
| 353 | _ = try innerParse(field.type, allocator, source, options); |
| 329 | 354 | break; |
| 330 | 355 | }, |
| 331 | 356 | .@"error" => return error.DuplicateField, |
| 332 | 357 | .use_last => {}, |
| 333 | 358 | } |
| 334 | 359 | } |
| 335 | | @field(r, field.name) = try internalParse(field.type, allocator, source, options); |
| 360 | @field(r, field.name) = try innerParse(field.type, allocator, source, options); |
| 336 | 361 | fields_seen[i] = true; |
| 337 | 362 | break; |
| 338 | 363 | } |
| ... | ... | @@ -418,7 +443,7 @@ fn internalParse( |
| 418 | 443 | switch (ptrInfo.size) { |
| 419 | 444 | .One => { |
| 420 | 445 | const r: *ptrInfo.child = try allocator.create(ptrInfo.child); |
| 421 | | r.* = try internalParse(ptrInfo.child, allocator, source, options); |
| 446 | r.* = try innerParse(ptrInfo.child, allocator, source, options); |
| 422 | 447 | return r; |
| 423 | 448 | }, |
| 424 | 449 | .Slice => { |
| ... | ... | @@ -438,7 +463,7 @@ fn internalParse( |
| 438 | 463 | } |
| 439 | 464 | |
| 440 | 465 | try arraylist.ensureUnusedCapacity(1); |
| 441 | | arraylist.appendAssumeCapacity(try internalParse(ptrInfo.child, allocator, source, options)); |
| 466 | arraylist.appendAssumeCapacity(try innerParse(ptrInfo.child, allocator, source, options)); |
| 442 | 467 | } |
| 443 | 468 | |
| 444 | 469 | if (ptrInfo.sentinel) |some| { |
| ... | ... | @@ -459,7 +484,7 @@ fn internalParse( |
| 459 | 484 | return try value_list.toOwnedSliceSentinel(@as(*const u8, @ptrCast(sentinel_ptr)).*); |
| 460 | 485 | } |
| 461 | 486 | if (ptrInfo.is_const) { |
| 462 | | switch (try source.nextAllocMax(allocator, .alloc_if_needed, options.max_value_len.?)) { |
| 487 | switch (try source.nextAllocMax(allocator, options.allocate.?, options.max_value_len.?)) { |
| 463 | 488 | inline .string, .allocated_string => |slice| return slice, |
| 464 | 489 | else => unreachable, |
| 465 | 490 | } |
| ... | ... | @@ -495,7 +520,7 @@ fn internalParseArray( |
| 495 | 520 | var r: T = undefined; |
| 496 | 521 | var i: usize = 0; |
| 497 | 522 | while (i < len) : (i += 1) { |
| 498 | | r[i] = try internalParse(Child, allocator, source, options); |
| 523 | r[i] = try innerParse(Child, allocator, source, options); |
| 499 | 524 | } |
| 500 | 525 | |
| 501 | 526 | if (.array_end != try source.next()) return error.UnexpectedToken; |