| ... | @@ -34,6 +34,14 @@ pub const ParseOptions = struct { | ... | @@ -34,6 +34,14 @@ pub const ParseOptions = struct { |
| 34 | /// The default for `parseFromTokenSource` with a `*std.json.Reader` is `std.json.default_max_value_len`. | 34 | /// The default for `parseFromTokenSource` with a `*std.json.Reader` is `std.json.default_max_value_len`. |
| 35 | /// Ignored for `parseFromValue` and `parseFromValueLeaky`. | 35 | /// Ignored for `parseFromValue` and `parseFromValueLeaky`. |
| 36 | max_value_len: ?usize = null, | 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 | pub fn Parsed(comptime T: type) type { | 47 | pub fn Parsed(comptime T: type) type { |
| ... | @@ -113,7 +121,6 @@ pub fn parseFromTokenSourceLeaky( | ... | @@ -113,7 +121,6 @@ pub fn parseFromTokenSourceLeaky( |
| 113 | if (@TypeOf(scanner_or_reader.*) == Scanner) { | 121 | if (@TypeOf(scanner_or_reader.*) == Scanner) { |
| 114 | assert(scanner_or_reader.is_end_of_input); | 122 | assert(scanner_or_reader.is_end_of_input); |
| 115 | } | 123 | } |
| 116 | | | |
| 117 | var resolved_options = options; | 124 | var resolved_options = options; |
| 118 | if (resolved_options.max_value_len == null) { | 125 | if (resolved_options.max_value_len == null) { |
| 119 | if (@TypeOf(scanner_or_reader.*) == Scanner) { | 126 | if (@TypeOf(scanner_or_reader.*) == Scanner) { |
| ... | @@ -122,8 +129,15 @@ pub fn parseFromTokenSourceLeaky( | ... | @@ -122,8 +129,15 @@ pub fn parseFromTokenSourceLeaky( |
| 122 | resolved_options.max_value_len = default_max_value_len; | 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 | assert(.end_of_document == try scanner_or_reader.next()); | 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,7 +195,14 @@ pub const ParseFromValueError = std.fmt.ParseIntError || std.fmt.ParseFloatError |
| 181 | LengthMismatch, | 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 | comptime T: type, | 206 | comptime T: type, |
| 186 | allocator: Allocator, | 207 | allocator: Allocator, |
| 187 | source: anytype, | 208 | source: anytype, |
| ... | @@ -220,7 +241,7 @@ fn internalParse( | ... | @@ -220,7 +241,7 @@ fn internalParse( |
| 220 | return null; | 241 | return null; |
| 221 | }, | 242 | }, |
| 222 | else => { | 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,16 +271,17 @@ fn internalParse( |
| 250 | var name_token: ?Token = try source.nextAllocMax(allocator, .alloc_if_needed, options.max_value_len.?); | 271 | var name_token: ?Token = try source.nextAllocMax(allocator, .alloc_if_needed, options.max_value_len.?); |
| 251 | const field_name = switch (name_token.?) { | 272 | const field_name = switch (name_token.?) { |
| 252 | inline .string, .allocated_string => |slice| slice, | 273 | inline .string, .allocated_string => |slice| slice, |
| 253 | else => return error.UnexpectedToken, | 274 | else => { |
| | 275 | return error.UnexpectedToken; |
| | 276 | }, |
| 254 | }; | 277 | }; |
| 255 | | 278 | |
| 256 | inline for (unionInfo.fields) |u_field| { | 279 | inline for (unionInfo.fields) |u_field| { |
| 257 | if (std.mem.eql(u8, u_field.name, field_name)) { | 280 | if (std.mem.eql(u8, u_field.name, field_name)) { |
| 258 | // Free the name token now in case we're using an allocator that optimizes freeing the last allocated object. | 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 | freeAllocated(allocator, name_token.?); | 283 | freeAllocated(allocator, name_token.?); |
| 261 | name_token = null; | 284 | name_token = null; |
| 262 | | | |
| 263 | if (u_field.type == void) { | 285 | if (u_field.type == void) { |
| 264 | // void isn't really a json type, but we can support void payload union tags with {} as a value. | 286 | // void isn't really a json type, but we can support void payload union tags with {} as a value. |
| 265 | if (.object_begin != try source.next()) return error.UnexpectedToken; | 287 | if (.object_begin != try source.next()) return error.UnexpectedToken; |
| ... | @@ -267,7 +289,7 @@ fn internalParse( | ... | @@ -267,7 +289,7 @@ fn internalParse( |
| 267 | result = @unionInit(T, u_field.name, {}); | 289 | result = @unionInit(T, u_field.name, {}); |
| 268 | } else { | 290 | } else { |
| 269 | // Recurse. | 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 | break; | 294 | break; |
| 273 | } | 295 | } |
| ... | @@ -287,7 +309,7 @@ fn internalParse( | ... | @@ -287,7 +309,7 @@ fn internalParse( |
| 287 | | 309 | |
| 288 | var r: T = undefined; | 310 | var r: T = undefined; |
| 289 | inline for (0..structInfo.fields.len) |i| { | 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 | if (.array_end != try source.next()) return error.UnexpectedToken; | 315 | if (.array_end != try source.next()) return error.UnexpectedToken; |
| ... | @@ -307,32 +329,35 @@ fn internalParse( | ... | @@ -307,32 +329,35 @@ fn internalParse( |
| 307 | while (true) { | 329 | while (true) { |
| 308 | var name_token: ?Token = try source.nextAllocMax(allocator, .alloc_if_needed, options.max_value_len.?); | 330 | var name_token: ?Token = try source.nextAllocMax(allocator, .alloc_if_needed, options.max_value_len.?); |
| 309 | const field_name = switch (name_token.?) { | 331 | const field_name = switch (name_token.?) { |
| 310 | .object_end => break, // No more fields. | | |
| 311 | inline .string, .allocated_string => |slice| slice, | 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 | inline for (structInfo.fields, 0..) |field, i| { | 341 | inline for (structInfo.fields, 0..) |field, i| { |
| 316 | if (field.is_comptime) @compileError("comptime fields are not supported: " ++ @typeName(T) ++ "." ++ field.name); | 342 | if (field.is_comptime) @compileError("comptime fields are not supported: " ++ @typeName(T) ++ "." ++ field.name); |
| 317 | if (std.mem.eql(u8, field.name, field_name)) { | 343 | if (std.mem.eql(u8, field.name, field_name)) { |
| 318 | // Free the name token now in case we're using an allocator that optimizes freeing the last allocated object. | 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 | freeAllocated(allocator, name_token.?); | 346 | freeAllocated(allocator, name_token.?); |
| 321 | name_token = null; | 347 | name_token = null; |
| 322 | | | |
| 323 | if (fields_seen[i]) { | 348 | if (fields_seen[i]) { |
| 324 | switch (options.duplicate_field_behavior) { | 349 | switch (options.duplicate_field_behavior) { |
| 325 | .use_first => { | 350 | .use_first => { |
| 326 | // Parse and ignore the redundant value. | 351 | // Parse and ignore the redundant value. |
| 327 | // We don't want to skip the value, because we want type checking. | 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 | break; | 354 | break; |
| 330 | }, | 355 | }, |
| 331 | .@"error" => return error.DuplicateField, | 356 | .@"error" => return error.DuplicateField, |
| 332 | .use_last => {}, | 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 | fields_seen[i] = true; | 361 | fields_seen[i] = true; |
| 337 | break; | 362 | break; |
| 338 | } | 363 | } |
| ... | @@ -418,7 +443,7 @@ fn internalParse( | ... | @@ -418,7 +443,7 @@ fn internalParse( |
| 418 | switch (ptrInfo.size) { | 443 | switch (ptrInfo.size) { |
| 419 | .One => { | 444 | .One => { |
| 420 | const r: *ptrInfo.child = try allocator.create(ptrInfo.child); | 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 | return r; | 447 | return r; |
| 423 | }, | 448 | }, |
| 424 | .Slice => { | 449 | .Slice => { |
| ... | @@ -438,7 +463,7 @@ fn internalParse( | ... | @@ -438,7 +463,7 @@ fn internalParse( |
| 438 | } | 463 | } |
| 439 | | 464 | |
| 440 | try arraylist.ensureUnusedCapacity(1); | 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 | if (ptrInfo.sentinel) |some| { | 469 | if (ptrInfo.sentinel) |some| { |
| ... | @@ -459,7 +484,7 @@ fn internalParse( | ... | @@ -459,7 +484,7 @@ fn internalParse( |
| 459 | return try value_list.toOwnedSliceSentinel(@as(*const u8, @ptrCast(sentinel_ptr)).*); | 484 | return try value_list.toOwnedSliceSentinel(@as(*const u8, @ptrCast(sentinel_ptr)).*); |
| 460 | } | 485 | } |
| 461 | if (ptrInfo.is_const) { | 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 | inline .string, .allocated_string => |slice| return slice, | 488 | inline .string, .allocated_string => |slice| return slice, |
| 464 | else => unreachable, | 489 | else => unreachable, |
| 465 | } | 490 | } |
| ... | @@ -495,7 +520,7 @@ fn internalParseArray( | ... | @@ -495,7 +520,7 @@ fn internalParseArray( |
| 495 | var r: T = undefined; | 520 | var r: T = undefined; |
| 496 | var i: usize = 0; | 521 | var i: usize = 0; |
| 497 | while (i < len) : (i += 1) { | 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 | if (.array_end != try source.next()) return error.UnexpectedToken; | 526 | if (.array_end != try source.next()) return error.UnexpectedToken; |