json-as 1.5.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/CHANGELOG.md +5 -1
  2. package/README.md +42 -0
  3. package/assembly/deserialize/error.ts +21 -0
  4. package/assembly/deserialize/index/arbitrary.ts +29 -8
  5. package/assembly/deserialize/index/array.ts +2 -1
  6. package/assembly/deserialize/index/bool.ts +1 -1
  7. package/assembly/deserialize/index/object.ts +3 -0
  8. package/assembly/deserialize/index/string.ts +37 -0
  9. package/assembly/deserialize/index.ts +0 -1
  10. package/assembly/deserialize/naive/array/arbitrary.ts +3 -2
  11. package/assembly/deserialize/naive/array/array.ts +1 -0
  12. package/assembly/deserialize/naive/array/bool.ts +21 -11
  13. package/assembly/deserialize/naive/array/box.ts +13 -33
  14. package/assembly/deserialize/naive/array/float.ts +23 -13
  15. package/assembly/deserialize/naive/array/integer.ts +33 -21
  16. package/assembly/deserialize/naive/array/object.ts +1 -0
  17. package/assembly/deserialize/naive/array/string.ts +29 -16
  18. package/assembly/deserialize/naive/bool.ts +17 -5
  19. package/assembly/deserialize/naive/float.ts +1 -2
  20. package/assembly/deserialize/naive/map.ts +339 -39
  21. package/assembly/deserialize/naive/object.ts +122 -65
  22. package/assembly/deserialize/naive/raw.ts +13 -1
  23. package/assembly/deserialize/naive/set.ts +0 -1
  24. package/assembly/deserialize/naive/staticarray.ts +3 -2
  25. package/assembly/deserialize/naive/string.ts +41 -52
  26. package/assembly/deserialize/parseMode.ts +216 -0
  27. package/assembly/deserialize/simd/array/integer.ts +6 -3
  28. package/assembly/deserialize/simd/float.ts +35 -29
  29. package/assembly/deserialize/simd/string.ts +79 -17
  30. package/assembly/deserialize/string-validation.ts +34 -0
  31. package/assembly/deserialize/swar/array/array.ts +63 -13
  32. package/assembly/deserialize/swar/array/bool.ts +14 -3
  33. package/assembly/deserialize/swar/array/float.ts +394 -13
  34. package/assembly/deserialize/swar/array/generic.ts +12 -2
  35. package/assembly/deserialize/swar/array/integer.ts +22 -62
  36. package/assembly/deserialize/swar/array/object.ts +11 -2
  37. package/assembly/deserialize/swar/array/shared.ts +1 -1
  38. package/assembly/deserialize/swar/array/string.ts +34 -5
  39. package/assembly/deserialize/swar/array/struct.ts +109 -15
  40. package/assembly/deserialize/swar/float.ts +19 -12
  41. package/assembly/deserialize/swar/string.ts +70 -111
  42. package/assembly/index.d.ts +6 -0
  43. package/assembly/index.ts +411 -33
  44. package/assembly/serialize/index/bool.ts +1 -1
  45. package/assembly/serialize/index/float.ts +1 -5
  46. package/assembly/serialize/index/integer.ts +1 -1
  47. package/assembly/serialize/index/object.ts +17 -0
  48. package/assembly/serialize/naive/array.ts +64 -0
  49. package/assembly/serialize/naive/float.ts +0 -5
  50. package/assembly/serialize/naive/map.ts +37 -0
  51. package/assembly/serialize/naive/set.ts +0 -1
  52. package/assembly/serialize/naive/staticarray.ts +0 -1
  53. package/assembly/serialize/naive/string.ts +0 -6
  54. package/assembly/serialize/naive/typedarray.ts +0 -1
  55. package/assembly/util/eisel-lemire.ts +179 -0
  56. package/assembly/util/prettyWhitespaceSimd.ts +69 -0
  57. package/assembly/util/scanValueEndSimd.ts +59 -36
  58. package/assembly/util/validateJson.ts +338 -0
  59. package/lib/as-bs.ts +28 -6
  60. package/package.json +13 -3
  61. package/transform/lib/index.d.ts +6 -0
  62. package/transform/lib/index.js +770 -111
  63. package/transform/lib/types.d.ts +1 -0
  64. package/transform/lib/types.js +3 -0
  65. package/assembly/deserialize/index/struct.ts +0 -1
  66. package/assembly/deserialize/naive/struct.ts +0 -21
@@ -50,6 +50,7 @@ function envFlagDefaultTrue(value) {
50
50
  }
51
51
  const USE_FAST_PATH = envFlagDefaultTrue(process.env["JSON_USE_FAST_PATH"]);
52
52
  const THROW_FAST_PATH = process.env["JSON_FAST_PATH_THROW"]?.trim() === "1";
53
+ const USE_DEFAULT_OBJECT_SPECIALIZATION = true;
53
54
  function parseJSONCacheConfig(value) {
54
55
  if (!value)
55
56
  return { enabled: false, bytes: 0 };
@@ -115,6 +116,20 @@ function getSerializeCall(type, realName) {
115
116
  if (type == "ArrayBuffer") {
116
117
  return `JSON.__serialize<ArrayBuffer>(load<ArrayBuffer>(ptr, offsetof<this>(${JSON.stringify(realName)})));\n`;
117
118
  }
119
+ if (type == "bool" ||
120
+ type == "boolean" ||
121
+ type == "i8" ||
122
+ type == "i16" ||
123
+ type == "i32" ||
124
+ type == "i64" ||
125
+ type == "isize" ||
126
+ type == "u8" ||
127
+ type == "u16" ||
128
+ type == "u32" ||
129
+ type == "u64" ||
130
+ type == "usize") {
131
+ return `JSON.__serializePrimitiveUnsafe<${type}>(load<${type}>(ptr, offsetof<this>(${JSON.stringify(realName)})));\n`;
132
+ }
118
133
  return needsReferenceLoad(type)
119
134
  ? `JSON.__serialize<${type}>(changetype<${type}>(load<usize>(ptr, offsetof<this>(${JSON.stringify(realName)}))));\n`
120
135
  : `JSON.__serialize<${type}>(load<${type}>(ptr, offsetof<this>(${JSON.stringify(realName)})));\n`;
@@ -287,6 +302,174 @@ export class JSONTransform extends Visitor {
287
302
  }
288
303
  return type;
289
304
  }
305
+ getDefaultSchema(owner, type) {
306
+ const name = stripNull(type).trim();
307
+ const matches = (schema) => schema.name == name ||
308
+ schema.name.endsWith("." + name) ||
309
+ name.endsWith("." + schema.name);
310
+ if (matches(owner))
311
+ return owner;
312
+ for (const dep of owner.deps) {
313
+ if (dep && matches(dep))
314
+ return dep;
315
+ }
316
+ if (owner.parent && matches(owner.parent))
317
+ return owner.parent;
318
+ return null;
319
+ }
320
+ getDefaultElementType(type) {
321
+ const value = stripNull(type).trim();
322
+ if (value.startsWith("Array<") && value.endsWith(">"))
323
+ return value.slice(6, -1).trim();
324
+ if (value.startsWith("StaticArray<") && value.endsWith(">"))
325
+ return value.slice(12, -1).trim();
326
+ return null;
327
+ }
328
+ getDefaultExpressionJSON(expression, type, owner, visiting) {
329
+ if (expression.kind == NodeKind.Parenthesized) {
330
+ return this.getDefaultExpressionJSON(expression.expression, type, owner, visiting);
331
+ }
332
+ if (expression.kind == NodeKind.Assertion) {
333
+ return this.getDefaultExpressionJSON(expression.expression, type, owner, visiting);
334
+ }
335
+ if (expression.kind == NodeKind.Null)
336
+ return "null";
337
+ if (expression.kind == NodeKind.True)
338
+ return "true";
339
+ if (expression.kind == NodeKind.False)
340
+ return "false";
341
+ if (expression.kind == NodeKind.Literal) {
342
+ const literal = expression;
343
+ if (literal.literalKind == 2)
344
+ return JSON.stringify(literal.value);
345
+ if (literal.literalKind == 1) {
346
+ const value = toString(expression).trim();
347
+ return /^\d+$/.test(value) ? value : null;
348
+ }
349
+ if (literal.literalKind == 5) {
350
+ const elementType = this.getDefaultElementType(type);
351
+ if (!elementType)
352
+ return null;
353
+ const values = [];
354
+ for (const element of literal
355
+ .elementExpressions) {
356
+ const value = this.getDefaultExpressionJSON(element, elementType, owner, visiting);
357
+ if (value == null)
358
+ return null;
359
+ values.push(value);
360
+ }
361
+ return "[" + values.join(",") + "]";
362
+ }
363
+ return null;
364
+ }
365
+ if (expression.kind == NodeKind.UnaryPrefix) {
366
+ const value = toString(expression).trim();
367
+ return /^-\d+$/.test(value) ? value : null;
368
+ }
369
+ if (expression.kind == NodeKind.New) {
370
+ const value = expression;
371
+ if (value.args.length != 0)
372
+ return null;
373
+ const constructed = toString(value.typeName).trim();
374
+ if (constructed == "Array" ||
375
+ constructed.startsWith("Array<") ||
376
+ constructed == "StaticArray" ||
377
+ constructed.startsWith("StaticArray<"))
378
+ return "[]";
379
+ const nested = this.getDefaultSchema(owner, type) ||
380
+ this.getDefaultSchema(owner, constructed);
381
+ return nested ? this.getDefaultObjectJSON(nested, visiting) : null;
382
+ }
383
+ return null;
384
+ }
385
+ getImplicitDefaultJSON(member, owner, visiting) {
386
+ if (member.node.type.isNullable)
387
+ return "null";
388
+ const type = stripNull(member.type).trim();
389
+ if (isBoolean(type))
390
+ return "false";
391
+ if (isPrimitive(type) ||
392
+ isEnum(type, this.sources.get(owner.node.range.source), this.parser))
393
+ return "0";
394
+ if (isString(type))
395
+ return '""';
396
+ if (this.getDefaultElementType(type))
397
+ return "[]";
398
+ const nested = this.getDefaultSchema(owner, type);
399
+ return nested ? this.getDefaultObjectJSON(nested, visiting) : null;
400
+ }
401
+ getDefaultObjectJSON(schema, visiting = new Set()) {
402
+ if (visiting.has(schema) ||
403
+ schema.custom ||
404
+ !schema.static ||
405
+ schema.node.isGeneric ||
406
+ schema.members.some((member) => member.flags.size != 0 || member.custom || member.generic))
407
+ return null;
408
+ visiting.add(schema);
409
+ const fields = [];
410
+ for (const member of schema.members) {
411
+ const value = member.node.initializer
412
+ ? this.getDefaultExpressionJSON(member.node.initializer, member.type, schema, visiting)
413
+ : this.getImplicitDefaultJSON(member, schema, visiting);
414
+ if (value == null) {
415
+ visiting.delete(schema);
416
+ return null;
417
+ }
418
+ fields.push(JSON.stringify(member.alias || member.name) + ":" + value);
419
+ }
420
+ visiting.delete(schema);
421
+ return "{" + fields.join(",") + "}";
422
+ }
423
+ getDefaultMatcherSource(defaultJSON) {
424
+ const literal = JSON.stringify(defaultJSON);
425
+ if (defaultJSON.length > 256)
426
+ return (`@inline __DESERIALIZE_DEFAULT(srcStart: usize, srcEnd: usize, knownMatch: bool = false): this {\n` +
427
+ ` if (!knownMatch && !JSON.Util.matchesLiteral(srcStart, srcEnd, ${literal})) return changetype<this>(0);\n` +
428
+ ` return changetype<this>(__new(offsetof<this>(), idof<this>())).__INITIALIZE();\n` +
429
+ `}`);
430
+ const bytes = [];
431
+ for (let i = 0; i < defaultJSON.length; i++) {
432
+ const code = defaultJSON.charCodeAt(i);
433
+ bytes.push(code & 0xff, code >>> 8);
434
+ }
435
+ const mismatches = [];
436
+ let offset = 0;
437
+ for (; offset + 16 <= bytes.length; offset += 16) {
438
+ const lanes = bytes
439
+ .slice(offset, offset + 16)
440
+ .map((value) => (value < 128 ? value : value - 256))
441
+ .join(", ");
442
+ mismatches.push(`v128.xor(v128.load(srcStart, ${offset}), v128(${lanes}))`);
443
+ }
444
+ const checks = [];
445
+ if (mismatches.length != 0) {
446
+ checks.push(` let mismatch = ${mismatches[0]};`);
447
+ for (let i = 1; i < mismatches.length; i++)
448
+ checks.push(` mismatch = v128.or(mismatch, ${mismatches[i]});`);
449
+ checks.push(" if (v128.any_true(mismatch)) return false;");
450
+ }
451
+ const scalar = (start, size) => {
452
+ let value = 0n;
453
+ for (let i = 0; i < size; i++)
454
+ value |= BigInt(bytes[start + i]) << BigInt(i << 3);
455
+ return "0x" + value.toString(16);
456
+ };
457
+ for (; offset + 8 <= bytes.length; offset += 8)
458
+ checks.push(` if (load<u64>(srcStart, ${offset}) != ${scalar(offset, 8)}) return false;`);
459
+ for (; offset + 4 <= bytes.length; offset += 4)
460
+ checks.push(` if (load<u32>(srcStart, ${offset}) != ${scalar(offset, 4)}) return false;`);
461
+ for (; offset + 2 <= bytes.length; offset += 2)
462
+ checks.push(` if (load<u16>(srcStart, ${offset}) != ${scalar(offset, 2)}) return false;`);
463
+ return (`@inline __DESERIALIZE_DEFAULT(srcStart: usize, srcEnd: usize, knownMatch: bool = false): this {\n` +
464
+ ` if (!knownMatch) {\n` +
465
+ ` if (srcEnd - srcStart != ${bytes.length}) return changetype<this>(0);\n` +
466
+ checks
467
+ .join("\n")
468
+ .replaceAll(" ", " ")
469
+ .replaceAll("return false;", "return changetype<this>(0);") +
470
+ `\n }` +
471
+ `\n return changetype<this>(__new(offsetof<this>(), idof<this>())).__INITIALIZE();\n}`);
472
+ }
290
473
  visitClassDeclaration(node) {
291
474
  if (!node.decorators?.length)
292
475
  return;
@@ -676,9 +859,10 @@ export class JSONTransform extends Visitor {
676
859
  this.visitedClasses.add(fullClassPath);
677
860
  const requestedFastPath = USE_FAST_PATH;
678
861
  let SERIALIZE = "__SERIALIZE(ptr: usize): void {\n";
679
- let INITIALIZE = " __INITIALIZE(): this {\n";
862
+ let INITIALIZE = "@inline __INITIALIZE(): this {\n";
680
863
  let DESERIALIZE = "__DESERIALIZE_SLOW<__JSON_T>(srcStart: usize, srcEnd: usize, out: __JSON_T): usize {\n";
681
864
  let DESERIALIZE_FAST = "__DESERIALIZE_FAST<__JSON_T>(srcStart: usize, srcEnd: usize, out: __JSON_T): usize {\n";
865
+ let fastPrettyTailStart = 0;
682
866
  let DESERIALIZE_CUSTOM = "";
683
867
  let SERIALIZE_CUSTOM = "";
684
868
  if (DEBUG > 0)
@@ -793,6 +977,7 @@ export class JSONTransform extends Visitor {
793
977
  this.generateEmptyMethods(node);
794
978
  return;
795
979
  }
980
+ const defaultStringLiterals = new Map();
796
981
  for (const member of members) {
797
982
  if (!member.type)
798
983
  throwError("Fields must be strongly typed", node.range);
@@ -809,6 +994,18 @@ export class JSONTransform extends Visitor {
809
994
  mem.value = value;
810
995
  mem.node = member;
811
996
  mem.byteSize = estimatedSerializedByteSize(mem.type, source, this.parser);
997
+ if (member.initializer?.kind == NodeKind.Literal &&
998
+ member.initializer.literalKind ==
999
+ 2 &&
1000
+ isString(stripNull(mem.type))) {
1001
+ const literal = member.initializer;
1002
+ const encoded = JSON.stringify(literal.value);
1003
+ mem.byteSize = encoded.length << 1;
1004
+ defaultStringLiterals.set(mem.name, {
1005
+ expression: toString(member.initializer),
1006
+ encoded,
1007
+ });
1008
+ }
812
1009
  mem.custom = schema.deps.some((dep) => dep?.name == stripNull(type) && dep.custom);
813
1010
  const lzInner = lazyInner.get(name.text);
814
1011
  if (lzInner !== undefined) {
@@ -898,8 +1095,24 @@ export class JSONTransform extends Visitor {
898
1095
  let isRegular = isPure;
899
1096
  let isFirst = true;
900
1097
  const serValue = (member, realName) => {
901
- if (!member.flags.has(PropertyFlags.Lazy))
1098
+ if (!member.flags.has(PropertyFlags.Lazy)) {
1099
+ const defaultString = defaultStringLiterals.get(member.name);
1100
+ if (defaultString) {
1101
+ const stores = this.getStores(defaultString.encoded, this.program.options.hasFeature(16)).join("\n");
1102
+ return (`{\n` +
1103
+ ` const __str = load<${member.type}>(ptr, offsetof<this>(${JSON.stringify(realName)}));\n` +
1104
+ ` if (changetype<usize>(__str) == changetype<usize>(${defaultString.expression})) {\n` +
1105
+ stores
1106
+ .split("\n")
1107
+ .map((line) => ` ${line}\n`)
1108
+ .join("") +
1109
+ ` } else {\n` +
1110
+ ` JSON.__serialize<${member.type}>(__str);\n` +
1111
+ ` }\n` +
1112
+ `}\n`);
1113
+ }
902
1114
  return getSerializeCall(member.type, realName);
1115
+ }
903
1116
  const T = member.lazyInner;
904
1117
  const baseName = realName.slice(0, -3);
905
1118
  const baseT = stripNull(T);
@@ -1131,7 +1344,10 @@ export class JSONTransform extends Visitor {
1131
1344
  type.startsWith("JSON.Box<") ||
1132
1345
  isEnum(type, this.sources.get(this.schema.node.range.source), this.parser))
1133
1346
  sortedMembers.number.push(member);
1134
- else if (isArray(type) || type == "JSON.Arr" || type == "Arr")
1347
+ else if (isArray(type) ||
1348
+ type == "JSON.Arr" ||
1349
+ type == "Arr" ||
1350
+ needsReferenceLoad(type))
1135
1351
  sortedMembers.array.push(member);
1136
1352
  else
1137
1353
  sortedMembers.object.push(member);
@@ -1183,6 +1399,8 @@ export class JSONTransform extends Visitor {
1183
1399
  const FLOAT_TYPES = ["f32", "f64"];
1184
1400
  const INTEGER_TYPES = [...UNSIGNED_INTEGER_TYPES, ...SIGNED_INTEGER_TYPES];
1185
1401
  const STRING_FIELD_DESERIALIZER = "__deserializeStringField";
1402
+ const STRING_FIELD_TRUSTED_DESERIALIZER = "__deserializeStringFieldTrusted";
1403
+ const getCustomDeserializeExpression = (type, valueStart, valueEnd) => `changetype<nonnull<${stripNull(type)}>>(0).__DESERIALIZE_CUSTOM(JSON.Util.ptrToStr(${valueStart}, ${valueEnd}))`;
1186
1404
  const getArrayValueType = (type) => {
1187
1405
  if (!type.startsWith("Array<") && !type.startsWith("StaticArray<"))
1188
1406
  return null;
@@ -1219,7 +1437,32 @@ export class JSONTransform extends Visitor {
1219
1437
  out.push(` ${srcPtr} = ${valuePtr} + 8;`);
1220
1438
  out.push(" } else {");
1221
1439
  }
1222
- out.push(` ${srcPtr} = ${STRING_FIELD_DESERIALIZER}<${member.type}>(${valuePtr}, srcEnd, ${outPtr}, ${fieldOffset});`);
1440
+ const declaredDefaultString = defaultStringLiterals.get(member.name);
1441
+ const defaultString = declaredDefaultString?.encoded == '""'
1442
+ ? undefined
1443
+ : declaredDefaultString;
1444
+ const stringFieldDeserializer = fastPath && keyOffset && !member.node.type.isNullable
1445
+ ? STRING_FIELD_TRUSTED_DESERIALIZER
1446
+ : STRING_FIELD_DESERIALIZER;
1447
+ const stringValuePtr = stringFieldDeserializer == STRING_FIELD_TRUSTED_DESERIALIZER
1448
+ ? `${valuePtr} + 2`
1449
+ : valuePtr;
1450
+ const stringFieldCall = stringFieldDeserializer == STRING_FIELD_TRUSTED_DESERIALIZER
1451
+ ? stringFieldDeserializer
1452
+ : `${stringFieldDeserializer}<${member.type}>`;
1453
+ if (defaultString) {
1454
+ const encodedBytes = defaultString.encoded.length << 1;
1455
+ const comparisons = getComparisions(defaultString.encoded, valuePtr, "==").join(" && ");
1456
+ out.push(` if (${valuePtr} + ${encodedBytes} <= srcEnd && ${comparisons}) {`);
1457
+ out.push(` store<${member.type}>(${outPtr}, ${defaultString.expression}, ${fieldOffset});`);
1458
+ out.push(` ${srcPtr} = ${valuePtr} + ${encodedBytes};`);
1459
+ out.push(" } else {");
1460
+ out.push(` ${srcPtr} = ${stringFieldCall}(${stringValuePtr}, srcEnd, ${outPtr}, ${fieldOffset});`);
1461
+ out.push(" }");
1462
+ }
1463
+ else {
1464
+ out.push(` ${srcPtr} = ${stringFieldCall}(${stringValuePtr}, srcEnd, ${outPtr}, ${fieldOffset});`);
1465
+ }
1223
1466
  if (member.node.type.isNullable) {
1224
1467
  out.push(" }");
1225
1468
  }
@@ -1290,37 +1533,11 @@ export class JSONTransform extends Visitor {
1290
1533
  }
1291
1534
  else if (resolvedType == "JSON.Raw") {
1292
1535
  out.push("{");
1293
- out.push(` const valueStart = ${srcPtr};`);
1294
- out.push(" let depth: i32 = 0;");
1295
- out.push(" let inString = false;");
1296
- out.push(` while (${srcPtr} < srcEnd) {`);
1297
- out.push(` const code = load<u16>(${srcPtr});`);
1298
- out.push(" if (inString) {");
1299
- out.push(` if (code == 0x22 && load<u16>(${srcPtr} - 2) != 0x5c) inString = false;`);
1300
- out.push(` ${srcPtr} += 2;`);
1301
- out.push(" continue;");
1302
- out.push(" }");
1303
- out.push(" if (code == 0x22) {");
1304
- out.push(" inString = true;");
1305
- out.push(` ${srcPtr} += 2;`);
1306
- out.push(" continue;");
1307
- out.push(" }");
1308
- out.push(" if (code == 0x7b || code == 0x5b) {");
1309
- out.push(" depth++;");
1310
- out.push(` ${srcPtr} += 2;`);
1311
- out.push(" continue;");
1312
- out.push(" }");
1313
- out.push(" if (code == 0x7d || code == 0x5d) {");
1314
- out.push(" if (depth == 0) break;");
1315
- out.push(" depth--;");
1316
- out.push(` ${srcPtr} += 2;`);
1317
- out.push(" continue;");
1318
- out.push(" }");
1319
- out.push(" if (code == 0x2c && depth == 0) break;");
1320
- out.push(` ${srcPtr} += 2;`);
1321
- out.push(" }");
1322
- out.push(` if (inString || depth != 0 || ${srcPtr} <= valueStart) break;`);
1323
- out.push(` store<${member.type}>(${outPtr}, JSON.Raw.from(JSON.Util.ptrToStr(valueStart, ${srcPtr})), ${fieldOffset});`);
1536
+ out.push(` const valueStart = ${valuePtr};`);
1537
+ out.push(` const valueEnd = JSON.Util.scanValueEnd<JSON.Raw>(valueStart, srcEnd);`);
1538
+ out.push(" if (!valueEnd) break;");
1539
+ out.push(` store<${member.type}>(${outPtr}, JSON.Raw.from(JSON.Util.ptrToStr(valueStart, valueEnd)), ${fieldOffset});`);
1540
+ out.push(` ${srcPtr} = valueEnd;`);
1324
1541
  out.push("}");
1325
1542
  }
1326
1543
  else if (isBoolean(resolvedType)) {
@@ -1339,6 +1556,24 @@ export class JSONTransform extends Visitor {
1339
1556
  else if (FLOAT_TYPES.includes(resolvedType)) {
1340
1557
  out.push(`${srcPtr} = __deserializeFloatField<${resolvedType}>(${valuePtr}, srcEnd, ${outPtr}, ${fieldOffset});`);
1341
1558
  }
1559
+ else if (resolvedSchema?.custom) {
1560
+ out.push("{");
1561
+ if (member.node.type.isNullable) {
1562
+ out.push(` if (load<u64>(${valuePtr}) == 30399761348886638) {`);
1563
+ out.push(` store<${member.type}>(${outPtr}, changetype<${member.type}>(0), ${fieldOffset});`);
1564
+ out.push(` ${srcPtr} = ${valuePtr} + 8;`);
1565
+ out.push(" } else {");
1566
+ }
1567
+ out.push(` const valueStart = ${valuePtr};`);
1568
+ out.push(` const valueEnd = JSON.Util.scanValueEnd<${resolvedType}>(valueStart, srcEnd);`);
1569
+ out.push(" if (!valueEnd) break;");
1570
+ out.push(` store<${member.type}>(${outPtr}, ${getCustomDeserializeExpression(resolvedType, "valueStart", "valueEnd")}, ${fieldOffset});`);
1571
+ out.push(` ${srcPtr} = valueEnd;`);
1572
+ if (member.node.type.isNullable) {
1573
+ out.push(" }");
1574
+ }
1575
+ out.push("}");
1576
+ }
1342
1577
  else if (resolvedSchema && !resolvedSchema.custom) {
1343
1578
  if (fastPath) {
1344
1579
  out.push("{");
@@ -1417,25 +1652,29 @@ export class JSONTransform extends Visitor {
1417
1652
  out.push(` store<${resolvedType}>(${outPtr}, value, ${fieldOffset});`);
1418
1653
  out.push(" }");
1419
1654
  out.push(" let index = 0;");
1655
+ out.push(" const reusableLength = value.length;");
1656
+ out.push(" const reusableDataStart = value.dataStart;");
1420
1657
  out.push(` ${srcPtr} = ${valuePtr} + 2;`);
1421
1658
  out.push(` ${srcPtr} = JSON.Util.skipWhitespace(${srcPtr}, srcEnd);`);
1422
1659
  out.push(` if (load<u16>(${srcPtr}) == 0x5d) {`);
1423
- out.push(" value.length = 0;");
1660
+ out.push(" if (value.length != 0) value.length = 0;");
1424
1661
  out.push(` ${srcPtr} += 2;`);
1425
1662
  out.push(" } else while (true) {");
1426
1663
  out.push(` ${srcPtr} = JSON.Util.skipWhitespace(${srcPtr}, srcEnd);`);
1427
1664
  if (elementNullable) {
1428
- out.push(` if (index >= value.length) value.push(changetype<${valueType} | null>(0));`);
1665
+ out.push(` if (index >= reusableLength) value.push(changetype<${valueType} | null>(0));`);
1666
+ out.push(` const slot = index < reusableLength ? reusableDataStart + ((<usize>index) << alignof<${valueType}>()) : value.dataStart + ((<usize>index) << alignof<${valueType}>());`);
1429
1667
  out.push(` if (load<u64>(${srcPtr}) == 30399761348886638) {`);
1430
- out.push(` store<usize>(value.dataStart + ((<usize>index) << alignof<${valueType}>()), 0);`);
1668
+ out.push(" store<usize>(slot, 0);");
1431
1669
  out.push(` ${srcPtr} += 8;`);
1432
1670
  out.push(" } else {");
1433
- out.push(` ${srcPtr} = ${STRING_FIELD_DESERIALIZER}<${valueType}>(${srcPtr}, srcEnd, value.dataStart + ((<usize>index) << alignof<${valueType}>()));`);
1671
+ out.push(` ${srcPtr} = ${STRING_FIELD_DESERIALIZER}<${valueType}>(${srcPtr}, srcEnd, slot);`);
1434
1672
  out.push(" }");
1435
1673
  }
1436
1674
  else {
1437
- out.push(' if (index >= value.length) value.push("");');
1438
- out.push(` ${srcPtr} = ${STRING_FIELD_DESERIALIZER}<${valueType}>(${srcPtr}, srcEnd, value.dataStart + ((<usize>index) << alignof<${valueType}>()));`);
1675
+ out.push(' if (index >= reusableLength) value.push("");');
1676
+ out.push(` const slot = index < reusableLength ? reusableDataStart + ((<usize>index) << alignof<${valueType}>()) : value.dataStart + ((<usize>index) << alignof<${valueType}>());`);
1677
+ out.push(` ${srcPtr} = ${STRING_FIELD_DESERIALIZER}<${valueType}>(${srcPtr}, srcEnd, slot);`);
1439
1678
  }
1440
1679
  out.push(" index++;");
1441
1680
  out.push(` ${srcPtr} = JSON.Util.skipWhitespace(${srcPtr}, srcEnd);`);
@@ -1445,7 +1684,7 @@ export class JSONTransform extends Visitor {
1445
1684
  out.push(" continue;");
1446
1685
  out.push(" }");
1447
1686
  out.push(" if (code == 0x5d) {");
1448
- out.push(" value.length = index;");
1687
+ out.push(" if (index != reusableLength) value.length = index;");
1449
1688
  out.push(` ${srcPtr} += 2;`);
1450
1689
  out.push(" break;");
1451
1690
  out.push(" }");
@@ -1466,18 +1705,21 @@ export class JSONTransform extends Visitor {
1466
1705
  out.push(` store<${resolvedType}>(${outPtr}, value, ${fieldOffset});`);
1467
1706
  out.push(" }");
1468
1707
  out.push(" let index = 0;");
1708
+ out.push(" const reusableLength = value.length;");
1709
+ out.push(" const reusableDataStart = value.dataStart;");
1469
1710
  out.push(` ${srcPtr} = ${valuePtr} + 2;`);
1470
1711
  out.push(` ${srcPtr} = JSON.Util.skipWhitespace(${srcPtr}, srcEnd);`);
1471
1712
  out.push(` if (load<u16>(${srcPtr}) == 0x5d) {`);
1472
- out.push(" value.length = 0;");
1713
+ out.push(" if (value.length != 0) value.length = 0;");
1473
1714
  out.push(` ${srcPtr} += 2;`);
1474
1715
  out.push(" } else while (true) {");
1475
1716
  out.push(` let item: ${valueType};`);
1476
- out.push(" if (index < value.length) {");
1477
- out.push(" item = unchecked(value[index]);");
1717
+ out.push(" if (index < reusableLength) {");
1718
+ out.push(` const itemSlot = reusableDataStart + ((<usize>index) << alignof<${valueType}>());`);
1719
+ out.push(` item = load<${valueType}>(itemSlot);`);
1478
1720
  out.push(" if (changetype<usize>(item) == 0) {");
1479
1721
  out.push(` item = changetype<${valueType}>(__new(offsetof<nonnull<${valueType}>>(), idof<nonnull<${valueType}>>()));`);
1480
- out.push(" unchecked((value[index] = item));");
1722
+ out.push(` store<${valueType}>(itemSlot, item);`);
1481
1723
  out.push(` changetype<nonnull<${valueType}>>(item).__INITIALIZE();`);
1482
1724
  out.push(" }");
1483
1725
  out.push(" } else {");
@@ -1504,7 +1746,7 @@ export class JSONTransform extends Visitor {
1504
1746
  out.push(" continue;");
1505
1747
  out.push(" }");
1506
1748
  out.push(" if (code == 0x5d) {");
1507
- out.push(" value.length = index;");
1749
+ out.push(" if (reusableLength != index) value.length = index;");
1508
1750
  out.push(` ${srcPtr} += 2;`);
1509
1751
  out.push(" break;");
1510
1752
  out.push(" }");
@@ -1516,8 +1758,25 @@ export class JSONTransform extends Visitor {
1516
1758
  out.push("}");
1517
1759
  return out;
1518
1760
  }
1519
- out.push(` ${srcPtr} = __deserializeArrayField_SWAR<nonnull<${resolvedType}>>(${valuePtr}, srcEnd, ${outPtr}, ${fieldOffset});`);
1520
- out.push(` if (!${srcPtr}) break;`);
1761
+ if (valueType == "i64") {
1762
+ out.push(` if (load<u32>(${valuePtr}) == 0x005d005b) {`);
1763
+ out.push(` let value = load<${resolvedType}>(${outPtr}, ${fieldOffset});`);
1764
+ out.push(" if (changetype<usize>(value) == 0) {");
1765
+ out.push(` value = instantiate<nonnull<${resolvedType}>>();`);
1766
+ out.push(` store<${resolvedType}>(${outPtr}, value, ${fieldOffset});`);
1767
+ out.push(" } else if (value.length != 0) {");
1768
+ out.push(" value.length = 0;");
1769
+ out.push(" }");
1770
+ out.push(` ${srcPtr} = ${valuePtr} + 4;`);
1771
+ out.push(" } else {");
1772
+ out.push(` ${srcPtr} = __deserializeArrayField_SWAR<nonnull<${resolvedType}>>(${valuePtr}, srcEnd, ${outPtr}, ${fieldOffset});`);
1773
+ out.push(` if (!${srcPtr}) break;`);
1774
+ out.push(" }");
1775
+ }
1776
+ else {
1777
+ out.push(` ${srcPtr} = __deserializeArrayField_SWAR<nonnull<${resolvedType}>>(${valuePtr}, srcEnd, ${outPtr}, ${fieldOffset});`);
1778
+ out.push(` if (!${srcPtr}) break;`);
1779
+ }
1521
1780
  if (member.node.type.isNullable) {
1522
1781
  out.push(" }");
1523
1782
  }
@@ -1570,8 +1829,25 @@ export class JSONTransform extends Visitor {
1570
1829
  const chunkFastBlocksOptional = (blocks, _tag, _callIndent, _needsKp) => {
1571
1830
  return blocks.join("");
1572
1831
  };
1832
+ const traceOptionalObject = supportsFastOptionalPath && this.schema.members.length <= 63;
1833
+ const objectTraceHeader = traceOptionalObject
1834
+ ? " const __traceToken = JSON.Util.beginObjectTrace(dst, start, " +
1835
+ JSON.stringify(this.schema.name) +
1836
+ ");\n" +
1837
+ " const __traceActive = __traceToken != i32.MIN_VALUE;\n" +
1838
+ " const __traceHit = __traceActive && __traceToken < 0;\n" +
1839
+ " const __traceSlot = __traceHit ? ~__traceToken : __traceToken;\n" +
1840
+ " const __traceMask = __traceHit ? JSON.Util.getObjectTraceMask(__traceSlot) : <u64>0;\n" +
1841
+ " const __traceTier = __traceHit ? JSON.Util.getObjectTraceTier(__traceSlot) : <u8>0;\n" +
1842
+ " let __tracePresent: u64 = 0;\n"
1843
+ : "";
1573
1844
  DESERIALIZE_FAST += indent + "const start = srcStart;\n";
1574
1845
  DESERIALIZE_FAST += indent + "const dst = changetype<usize>(out);\n";
1846
+ if (traceOptionalObject) {
1847
+ DESERIALIZE_FAST += objectTraceHeader;
1848
+ DESERIALIZE_FAST += indent + "if (__traceTier < 2) {\n";
1849
+ indent += " ";
1850
+ }
1575
1851
  DESERIALIZE_FAST += indent + "do {\n";
1576
1852
  indent += " ";
1577
1853
  if (supportsFastOptionalPath) {
@@ -1585,25 +1861,40 @@ export class JSONTransform extends Visitor {
1585
1861
  const key = JSON.stringify(member.alias || member.name);
1586
1862
  if (key.length <= 2)
1587
1863
  throw new Error("Key cannot be empty!");
1588
- const firstKeySection = key + ":";
1589
- const nextKeySection = "," + key + ":";
1590
- const firstKeyOffset = firstKeySection.length << 1;
1591
- const nextKeyOffset = nextKeySection.length << 1;
1592
1864
  const resolvedType = stripNull(member.type);
1593
1865
  const inlineStringValue = ["string", "String"].includes(resolvedType);
1594
- const deserializerFirst = getDeserializer(member.type, "srcStart", "dst", member, inlineStringValue ? firstKeyOffset : 0, true);
1595
- const deserializerNext = getDeserializer(member.type, "srcStart", "dst", member, inlineStringValue ? nextKeyOffset : 0, true);
1866
+ const packedQuote = inlineStringValue && !member.node.type.isNullable ? '"' : "";
1867
+ const firstKeySection = key + ":" + packedQuote;
1868
+ const nextKeySection = "," + key + ":" + packedQuote;
1869
+ const firstKeyOffset = firstKeySection.length << 1;
1870
+ const nextKeyOffset = nextKeySection.length << 1;
1871
+ const trustedStringValue = packedQuote.length != 0;
1872
+ const firstValueOffset = trustedStringValue
1873
+ ? firstKeyOffset - 2
1874
+ : firstKeyOffset;
1875
+ const nextValueOffset = trustedStringValue
1876
+ ? nextKeyOffset - 2
1877
+ : nextKeyOffset;
1878
+ const deserializerFirst = getDeserializer(member.type, "srcStart", "dst", member, inlineStringValue ? firstValueOffset : 0, true);
1879
+ const deserializerNext = getDeserializer(member.type, "srcStart", "dst", member, inlineStringValue ? nextValueOffset : 0, true);
1596
1880
  const isOptional = member.flags.has(PropertyFlags.OmitNull) ||
1597
1881
  member.flags.has(PropertyFlags.OmitIf);
1882
+ const traceBit = `<u64>1 << ${i}`;
1883
+ const firstMismatch = getComparisions(firstKeySection, "srcStart", "!=").join(" || ");
1884
+ const nextMismatch = getComparisions(nextKeySection, "srcStart", "!=").join(" || ");
1885
+ const firstAbsent = traceOptionalObject
1886
+ ? `__traceHit ? (__traceMask & (${traceBit})) == 0 : (${firstMismatch})`
1887
+ : firstMismatch;
1888
+ const nextAbsent = traceOptionalObject
1889
+ ? `__traceHit ? (__traceMask & (${traceBit})) == 0 : (${nextMismatch})`
1890
+ : nextMismatch;
1598
1891
  if (!deserializerFirst.length || !deserializerNext.length) {
1599
1892
  t1opt.push(indent + "break;\n\n");
1600
1893
  continue;
1601
1894
  }
1602
1895
  let blk = indent + "if (!seenAny) {\n";
1603
1896
  indent += " ";
1604
- blk +=
1605
- indent +
1606
- `if ( // ${firstKeySection}\n${(indent += " ")}${getComparisions(firstKeySection, "srcStart", "!=").join("\n" + indent + "|| ")}\n${(indent = indent.slice(0, -2))}) {\n`;
1897
+ blk += indent + `if (${firstAbsent}) {\n`;
1607
1898
  indent += " ";
1608
1899
  if (isOptional) {
1609
1900
  blk += indent + "// optional @omitnull field omitted\n";
@@ -1616,19 +1907,20 @@ export class JSONTransform extends Visitor {
1616
1907
  indent += " ";
1617
1908
  if (!inlineStringValue)
1618
1909
  blk += indent + `srcStart += ${firstKeyOffset};\n`;
1619
- blk +=
1620
- indent +
1621
- `if (JSON.Util.isSpace(load<u16>(${inlineStringValue ? `srcStart + ${firstKeyOffset}` : "srcStart"}))) break;\n`;
1910
+ if (!trustedStringValue)
1911
+ blk +=
1912
+ indent +
1913
+ `if (JSON.Util.isSpace(load<u16>(${inlineStringValue ? `srcStart + ${firstValueOffset}` : "srcStart"}))) break;\n`;
1622
1914
  blk += indent + deserializerFirst.join("\n" + indent) + "\n";
1915
+ if (traceOptionalObject)
1916
+ blk += indent + `__tracePresent |= ${traceBit};\n`;
1623
1917
  blk += indent + "seenAny = true;\n";
1624
1918
  indent = indent.slice(0, -2);
1625
1919
  blk += indent + "}\n";
1626
1920
  indent = indent.slice(0, -2);
1627
1921
  blk += indent + "} else {\n";
1628
1922
  indent += " ";
1629
- blk +=
1630
- indent +
1631
- `if ( // ${nextKeySection}\n${(indent += " ")}${getComparisions(nextKeySection, "srcStart", "!=").join("\n" + indent + "|| ")}\n${(indent = indent.slice(0, -2))}) {\n`;
1923
+ blk += indent + `if (${nextAbsent}) {\n`;
1632
1924
  indent += " ";
1633
1925
  if (isOptional) {
1634
1926
  blk += indent + "// optional @omitnull field omitted\n";
@@ -1641,10 +1933,13 @@ export class JSONTransform extends Visitor {
1641
1933
  indent += " ";
1642
1934
  if (!inlineStringValue)
1643
1935
  blk += indent + `srcStart += ${nextKeyOffset};\n`;
1644
- blk +=
1645
- indent +
1646
- `if (JSON.Util.isSpace(load<u16>(${inlineStringValue ? `srcStart + ${nextKeyOffset}` : "srcStart"}))) break;\n`;
1936
+ if (!trustedStringValue)
1937
+ blk +=
1938
+ indent +
1939
+ `if (JSON.Util.isSpace(load<u16>(${inlineStringValue ? `srcStart + ${nextValueOffset}` : "srcStart"}))) break;\n`;
1647
1940
  blk += indent + deserializerNext.join("\n" + indent) + "\n";
1941
+ if (traceOptionalObject)
1942
+ blk += indent + `__tracePresent |= ${traceBit};\n`;
1648
1943
  indent = indent.slice(0, -2);
1649
1944
  blk += indent + "}\n";
1650
1945
  indent = indent.slice(0, -2);
@@ -1660,24 +1955,27 @@ export class JSONTransform extends Visitor {
1660
1955
  const key = JSON.stringify(member.alias || member.name);
1661
1956
  if (key.length <= 2)
1662
1957
  throw new Error("Key cannot be empty!");
1663
- const keySection = (i == 0 ? "{" : ",") + key + ":";
1664
- let blk = indent +
1665
- `if ( // ${keySection}\n${(indent += " ")}${getComparisions(keySection, "srcStart", "!=").join("\n" + indent + "|| ")}\n${(indent = indent.slice(0, -2))}) break;\n`;
1666
- const keyOffset = keySection.length << 1;
1667
1958
  const resolvedType = stripNull(member.type);
1668
1959
  const inlineStringValue = ["string", "String"].includes(resolvedType);
1960
+ const trustedStringValue = inlineStringValue && !member.node.type.isNullable;
1961
+ const keySection = (i == 0 ? "{" : ",") + key + ":" + (trustedStringValue ? '"' : "");
1962
+ let blk = indent +
1963
+ `if (${getComparisions(keySection, "srcStart", "!=").join(" || ")}) break;\n`;
1964
+ const keyOffset = keySection.length << 1;
1965
+ const valueOffset = trustedStringValue ? keyOffset - 2 : keyOffset;
1669
1966
  if (!inlineStringValue) {
1670
1967
  blk += indent + `srcStart += ${keyOffset};\n\n`;
1671
1968
  }
1672
- const deserializer = getDeserializer(member.type, "srcStart", "dst", member, inlineStringValue ? keyOffset : 0, true);
1969
+ const deserializer = getDeserializer(member.type, "srcStart", "dst", member, inlineStringValue ? valueOffset : 0, true);
1673
1970
  if (!deserializer.length) {
1674
1971
  blk += indent + "break;\n\n";
1675
1972
  t1blocks.push(blk);
1676
1973
  continue;
1677
1974
  }
1678
- blk +=
1679
- indent +
1680
- `if (JSON.Util.isSpace(load<u16>(${inlineStringValue ? `srcStart + ${keyOffset}` : "srcStart"}))) break;\n`;
1975
+ if (!trustedStringValue)
1976
+ blk +=
1977
+ indent +
1978
+ `if (JSON.Util.isSpace(load<u16>(${inlineStringValue ? `srcStart + ${valueOffset}` : "srcStart"}))) break;\n`;
1681
1979
  blk += indent + deserializer.join("\n" + indent) + "\n\n";
1682
1980
  t1blocks.push(blk);
1683
1981
  }
@@ -1686,9 +1984,18 @@ export class JSONTransform extends Visitor {
1686
1984
  DESERIALIZE_FAST +=
1687
1985
  indent + "if (load<u16>(srcStart) !== 0x7d) break; // }\n";
1688
1986
  DESERIALIZE_FAST += indent + "srcStart += 2;\n";
1987
+ if (traceOptionalObject)
1988
+ DESERIALIZE_FAST +=
1989
+ indent +
1990
+ "if (__traceActive) JSON.Util.saveObjectTrace(__traceSlot, __tracePresent, 1);\n";
1689
1991
  DESERIALIZE_FAST += indent + "return srcStart;\n";
1690
1992
  indent = indent.slice(0, -2);
1691
1993
  DESERIALIZE_FAST += indent + "} while (false);\n\n";
1994
+ if (traceOptionalObject) {
1995
+ indent = indent.slice(0, -2);
1996
+ DESERIALIZE_FAST += indent + "}\n\n";
1997
+ }
1998
+ fastPrettyTailStart = DESERIALIZE_FAST.length;
1692
1999
  const tier2Desers = this.schema.members.map((member) => getDeserializer(member.type, "srcStart", "dst", member, 0, true));
1693
2000
  const tier2Ok = tier2Desers.every((d) => d.length && !(d.length === 1 && d[0].trim() === "break;"));
1694
2001
  if (tier2Ok && !supportsFastOptionalPath) {
@@ -1709,9 +2016,7 @@ export class JSONTransform extends Visitor {
1709
2016
  blk += skip;
1710
2017
  blk +=
1711
2018
  i2 +
1712
- `if ( // ${key}\n${i2} ` +
1713
- getComparisions(key, "srcStart", "!=").join("\n" + i2 + " || ") +
1714
- `\n${i2}) break;\n`;
2019
+ `if (${getComparisions(key, "srcStart", "!=").join(" || ")}) break;\n`;
1715
2020
  blk += i2 + `srcStart += ${keyBytes};\n`;
1716
2021
  blk += skip;
1717
2022
  blk += i2 + "if (load<u16>(srcStart) != 0x3a) break; // :\n";
@@ -1740,6 +2045,16 @@ export class JSONTransform extends Visitor {
1740
2045
  const i3 = " ";
1741
2046
  DESERIALIZE_FAST += i1 + "srcStart = start;\n";
1742
2047
  DESERIALIZE_FAST += i1 + "do {\n";
2048
+ if (traceOptionalObject) {
2049
+ DESERIALIZE_FAST += i2 + "__tracePresent = 0;\n";
2050
+ DESERIALIZE_FAST +=
2051
+ i2 +
2052
+ "const __traceSeparator = __traceHit ? JSON.Util.getObjectTraceSeparator(__traceSlot) : <usize>0;\n";
2053
+ DESERIALIZE_FAST += i2 + "let __traceCandidateSeparator: usize = 0;\n";
2054
+ DESERIALIZE_FAST += i2 + "let __traceSeparatorStart: usize = 0;\n";
2055
+ DESERIALIZE_FAST += i2 + "let __traceSeparatorSeen = false;\n";
2056
+ DESERIALIZE_FAST += i2 + "let __traceCanonicalPretty = true;\n";
2057
+ }
1743
2058
  DESERIALIZE_FAST +=
1744
2059
  i2 + "srcStart = JSON.Util.skipWhitespace(srcStart, srcEnd);\n";
1745
2060
  DESERIALIZE_FAST += i2 + "if (load<u16>(srcStart) != 0x7b) break; // {\n";
@@ -1752,23 +2067,91 @@ export class JSONTransform extends Visitor {
1752
2067
  const member = this.schema.members[i];
1753
2068
  const key = JSON.stringify(member.alias || member.name);
1754
2069
  const keyBytes = key.length << 1;
2070
+ const traceBit = `<u64>1 << ${i}`;
2071
+ const keyMatches = getComparisions(key, "kp", "==").join(" && ");
2072
+ const presentCondition = traceOptionalObject
2073
+ ? `__traceHit ? (__traceMask & (${traceBit})) != 0 : (${keyMatches})`
2074
+ : keyMatches;
1755
2075
  let blk = "\n";
1756
- blk += i2 + "kp = JSON.Util.skipWhitespace(srcStart, srcEnd);\n";
1757
- if (multi && i > 0) {
2076
+ if (traceOptionalObject) {
2077
+ blk += i2 + `if (__traceHit && (${traceBit} & __traceMask) != 0) {\n`;
2078
+ blk += i3 + "kp = srcStart;\n";
2079
+ if (multi && i > 0) {
2080
+ blk += i3 + "if (seenAny) {\n";
2081
+ blk += i3 + " if (load<u16>(kp) != 0x2c) break; // ,\n";
2082
+ blk += i3 + " kp += 2;\n";
2083
+ blk += i3 + "}\n";
2084
+ }
2085
+ blk += i3 + "if (__traceTier == 3) kp += __traceSeparator;\n";
2086
+ blk += i3 + "else kp = JSON.Util.skipWhitespace(kp, srcEnd);\n";
2087
+ blk += i2 + "} else if (!__traceHit) {\n";
2088
+ blk += i3 + "__traceSeparatorStart = srcStart;\n";
2089
+ blk += i3 + "kp = JSON.Util.skipWhitespace(srcStart, srcEnd);\n";
2090
+ if (multi && i > 0) {
2091
+ blk += i3 + "if (seenAny && load<u16>(kp) == 0x2c) {\n";
2092
+ blk +=
2093
+ i3 + " if (kp != srcStart) __traceCanonicalPretty = false;\n";
2094
+ blk += i3 + " __traceSeparatorStart = kp + 2;\n";
2095
+ blk +=
2096
+ i3 +
2097
+ " kp = JSON.Util.skipWhitespace(__traceSeparatorStart, srcEnd);\n";
2098
+ blk += i3 + "}\n";
2099
+ }
2100
+ blk += i2 + "}\n";
2101
+ }
2102
+ else {
2103
+ blk += i2 + "kp = JSON.Util.skipWhitespace(srcStart, srcEnd);\n";
2104
+ if (multi && i > 0) {
2105
+ blk +=
2106
+ i2 +
2107
+ "if (seenAny && load<u16>(kp) == 0x2c) kp = JSON.Util.skipWhitespace(kp + 2, srcEnd);\n";
2108
+ }
2109
+ }
2110
+ blk += i2 + `if (${presentCondition}) {\n`;
2111
+ if (traceOptionalObject) {
2112
+ blk += i3 + "if (__traceHit && __traceTier == 3) {\n";
2113
+ blk += i3 + ` kp += ${keyBytes};\n`;
2114
+ blk += i3 + " if (load<u16>(kp) != 0x3a) break; // :\n";
2115
+ blk += i3 + " if (load<u16>(kp + 2) != 0x20) break; // space\n";
2116
+ blk += i3 + " srcStart = kp + 4;\n";
2117
+ blk += i3 + "} else {\n";
2118
+ blk += i3 + " if (!__traceHit) {\n";
2119
+ blk +=
2120
+ i3 +
2121
+ " const __traceCurrentSeparator = kp - __traceSeparatorStart;\n";
2122
+ blk += i3 + " if (__traceSeparatorSeen) {\n";
2123
+ blk +=
2124
+ i3 +
2125
+ " if (__traceCurrentSeparator != __traceCandidateSeparator) __traceCanonicalPretty = false;\n";
2126
+ blk += i3 + " } else {\n";
2127
+ blk +=
2128
+ i3 + " __traceCandidateSeparator = __traceCurrentSeparator;\n";
2129
+ blk += i3 + " __traceSeparatorSeen = true;\n";
2130
+ blk += i3 + " }\n";
2131
+ blk += i3 + " }\n";
2132
+ blk += i3 + ` kp += ${keyBytes};\n`;
2133
+ blk += i3 + " const __traceKeyEnd = kp;\n";
2134
+ blk += i3 + " kp = JSON.Util.skipWhitespace(kp, srcEnd);\n";
2135
+ blk +=
2136
+ i3 +
2137
+ " if (!__traceHit && kp != __traceKeyEnd) __traceCanonicalPretty = false;\n";
2138
+ blk += i3 + " if (load<u16>(kp) != 0x3a) break; // :\n";
2139
+ blk +=
2140
+ i3 + " srcStart = JSON.Util.skipWhitespace(kp + 2, srcEnd);\n";
1758
2141
  blk +=
1759
- i2 +
1760
- "if (seenAny && load<u16>(kp) == 0x2c) kp = JSON.Util.skipWhitespace(kp + 2, srcEnd);\n";
2142
+ i3 +
2143
+ " if (!__traceHit && (load<u16>(kp + 2) != 0x20 || srcStart != kp + 4)) __traceCanonicalPretty = false;\n";
2144
+ blk += i3 + "}\n";
2145
+ }
2146
+ else {
2147
+ blk += i3 + `kp += ${keyBytes};\n`;
2148
+ blk += i3 + "kp = JSON.Util.skipWhitespace(kp, srcEnd);\n";
2149
+ blk += i3 + "if (load<u16>(kp) != 0x3a) break; // :\n";
2150
+ blk += i3 + "srcStart = JSON.Util.skipWhitespace(kp + 2, srcEnd);\n";
1761
2151
  }
1762
- blk +=
1763
- i2 +
1764
- `if ( // ${key}\n${i2} ` +
1765
- getComparisions(key, "kp", "==").join("\n" + i2 + " && ") +
1766
- `\n${i2}) {\n`;
1767
- blk += i3 + `kp += ${keyBytes};\n`;
1768
- blk += i3 + "kp = JSON.Util.skipWhitespace(kp, srcEnd);\n";
1769
- blk += i3 + "if (load<u16>(kp) != 0x3a) break; // :\n";
1770
- blk += i3 + "srcStart = JSON.Util.skipWhitespace(kp + 2, srcEnd);\n";
1771
2152
  blk += i3 + tier2Desers[i].join("\n" + i3) + "\n";
2153
+ if (traceOptionalObject)
2154
+ blk += i3 + `__tracePresent |= ${traceBit};\n`;
1772
2155
  if (multi)
1773
2156
  blk += i3 + "seenAny = true;\n";
1774
2157
  blk += i2 + "}\n";
@@ -1780,6 +2163,137 @@ export class JSONTransform extends Visitor {
1780
2163
  i2 + "srcStart = JSON.Util.skipWhitespace(srcStart, srcEnd);\n";
1781
2164
  DESERIALIZE_FAST += i2 + "if (load<u16>(srcStart) != 0x7d) break; // }\n";
1782
2165
  DESERIALIZE_FAST += i2 + "srcStart += 2;\n";
2166
+ if (traceOptionalObject)
2167
+ DESERIALIZE_FAST +=
2168
+ i2 +
2169
+ "if (__traceActive) JSON.Util.saveObjectTrace(__traceSlot, __tracePresent, __traceHit ? __traceTier : (__traceCanonicalPretty && __traceSeparatorSeen ? 3 : 2), __traceHit ? __traceSeparator : __traceCandidateSeparator);\n";
2170
+ DESERIALIZE_FAST += i2 + "return srcStart;\n";
2171
+ DESERIALIZE_FAST += i1 + "} while (false);\n\n";
2172
+ }
2173
+ const keyedUnionAlternatives = this.schema.members.filter((member) => member.node.type.isNullable &&
2174
+ this.getSchema(stripNull(member.type)) != null).length;
2175
+ const scalarKeyedSchema = this.schema.members.every((member) => {
2176
+ const type = stripNull(member.type);
2177
+ return (INTEGER_TYPES.includes(type) ||
2178
+ FLOAT_TYPES.includes(type) ||
2179
+ ["string", "String"].includes(type) ||
2180
+ isBoolean(type));
2181
+ });
2182
+ const structKeyedSchema = this.schema.members.every((member) => {
2183
+ const type = stripNull(member.type);
2184
+ return (INTEGER_TYPES.includes(type) ||
2185
+ FLOAT_TYPES.includes(type) ||
2186
+ ["string", "String"].includes(type) ||
2187
+ isBoolean(type) ||
2188
+ this.getSchema(type) != null);
2189
+ });
2190
+ const keyedFallbackEnabled = tier2Ok &&
2191
+ this.schema.members.length > 0 &&
2192
+ this.schema.members.length <= 24 &&
2193
+ (keyedUnionAlternatives >= 2 ||
2194
+ (scalarKeyedSchema && this.schema.members.length >= 12) ||
2195
+ (structKeyedSchema && hasExplicitOptionalMembers));
2196
+ if (keyedFallbackEnabled) {
2197
+ const i1 = " ";
2198
+ const i2 = " ";
2199
+ const i3 = " ";
2200
+ const keyedResets = this.schema.members.map((member) => {
2201
+ const offset = `offsetof<this>(${JSON.stringify(member.name)})`;
2202
+ if (member.value)
2203
+ return `store<${member.type}>(dst, ${member.value}, ${offset});`;
2204
+ if (member.node.type.isNullable)
2205
+ return `store<${member.type}>(dst, null, ${offset});`;
2206
+ if (this.getSchema(member.type))
2207
+ return (`store<${member.type}>(dst, ` +
2208
+ `changetype<nonnull<${member.type}>>(__new(offsetof<nonnull<${member.type}>>(), idof<nonnull<${member.type}>>())).__INITIALIZE(), ${offset});`);
2209
+ if (["string", "String"].includes(member.type))
2210
+ return `store<${member.type}>(dst, "", ${offset});`;
2211
+ if (member.type.endsWith("[]")) {
2212
+ const element = member.type.slice(0, -2).trim();
2213
+ return `store<${member.type}>(dst, new Array<${element}>(), ${offset});`;
2214
+ }
2215
+ if (member.type.startsWith("Array<") ||
2216
+ member.type.startsWith("Map<") ||
2217
+ member.type.startsWith("Set<"))
2218
+ return `store<${member.type}>(dst, new ${member.type}(), ${offset});`;
2219
+ if (member.type.startsWith("StaticArray<")) {
2220
+ const element = member.type.slice("StaticArray<".length, -1).trim();
2221
+ return `store<${member.type}>(dst, new StaticArray<${element}>(0), ${offset});`;
2222
+ }
2223
+ return `store<${member.type}>(dst, 0, ${offset});`;
2224
+ });
2225
+ DESERIALIZE_FAST += i1 + "srcStart = start;\n";
2226
+ DESERIALIZE_FAST += i1 + "do {\n";
2227
+ DESERIALIZE_FAST +=
2228
+ i2 + "srcStart = JSON.Util.skipWhitespace(srcStart, srcEnd);\n";
2229
+ DESERIALIZE_FAST += i2 + "if (load<u16>(srcStart) != 0x7b) break; // {\n";
2230
+ DESERIALIZE_FAST += i2 + "srcStart += 2;\n";
2231
+ DESERIALIZE_FAST += i2 + "let seen: u32 = 0;\n";
2232
+ DESERIALIZE_FAST += i2 + "let complete = false;\n";
2233
+ DESERIALIZE_FAST += i2 + "while (true) {\n";
2234
+ DESERIALIZE_FAST +=
2235
+ i3 + "srcStart = JSON.Util.skipWhitespace(srcStart, srcEnd);\n";
2236
+ DESERIALIZE_FAST += i3 + "let code = load<u16>(srcStart);\n";
2237
+ DESERIALIZE_FAST += i3 + "if (code == 0x7d) {\n";
2238
+ DESERIALIZE_FAST += i3 + " srcStart += 2;\n";
2239
+ DESERIALIZE_FAST += i3 + " complete = true;\n";
2240
+ DESERIALIZE_FAST += i3 + " break;\n";
2241
+ DESERIALIZE_FAST += i3 + "}\n";
2242
+ DESERIALIZE_FAST += i3 + "if (code != 0x22) break; // opening quote\n";
2243
+ DESERIALIZE_FAST += i3 + "const keyStart = srcStart;\n";
2244
+ DESERIALIZE_FAST +=
2245
+ i3 +
2246
+ "const keyEnd = JSON.Util.scanValueEnd<string>(keyStart, srcEnd);\n";
2247
+ DESERIALIZE_FAST += i3 + "if (!keyEnd) break;\n";
2248
+ DESERIALIZE_FAST +=
2249
+ i3 + "srcStart = JSON.Util.skipWhitespace(keyEnd, srcEnd);\n";
2250
+ DESERIALIZE_FAST += i3 + "if (load<u16>(srcStart) != 0x3a) break; // :\n";
2251
+ DESERIALIZE_FAST +=
2252
+ i3 + "srcStart = JSON.Util.skipWhitespace(srcStart + 2, srcEnd);\n";
2253
+ DESERIALIZE_FAST += i3 + "const keyBytes = keyEnd - keyStart;\n";
2254
+ DESERIALIZE_FAST += i3 + "let matched = false;\n";
2255
+ for (let i = 0; i < this.schema.members.length; i++) {
2256
+ const member = this.schema.members[i];
2257
+ const key = JSON.stringify(member.alias || member.name);
2258
+ const condition = [
2259
+ `keyBytes == ${key.length << 1}`,
2260
+ ...getComparisions(key, "keyStart", "=="),
2261
+ ].join(" &&\n" + i3 + " ");
2262
+ DESERIALIZE_FAST += i3 + `if (!matched && (${condition})) {\n`;
2263
+ DESERIALIZE_FAST += i3 + " matched = true;\n";
2264
+ DESERIALIZE_FAST +=
2265
+ i3 + " " + tier2Desers[i].join("\n" + i3 + " ") + "\n";
2266
+ DESERIALIZE_FAST += i3 + ` seen |= <u32>1 << ${i};\n`;
2267
+ DESERIALIZE_FAST += i3 + "}\n";
2268
+ }
2269
+ DESERIALIZE_FAST += i3 + "if (!matched) {\n";
2270
+ if (STRICT) {
2271
+ DESERIALIZE_FAST += i3 + " break;\n";
2272
+ }
2273
+ else {
2274
+ DESERIALIZE_FAST +=
2275
+ i3 +
2276
+ " const valueEnd = JSON.Util.scanValueEnd<usize>(srcStart, srcEnd);\n";
2277
+ DESERIALIZE_FAST += i3 + " if (!valueEnd) break;\n";
2278
+ DESERIALIZE_FAST += i3 + " srcStart = valueEnd;\n";
2279
+ }
2280
+ DESERIALIZE_FAST += i3 + "}\n";
2281
+ DESERIALIZE_FAST +=
2282
+ i3 + "srcStart = JSON.Util.skipWhitespace(srcStart, srcEnd);\n";
2283
+ DESERIALIZE_FAST += i3 + "code = load<u16>(srcStart);\n";
2284
+ DESERIALIZE_FAST += i3 + "if (code == 0x7d) {\n";
2285
+ DESERIALIZE_FAST += i3 + " srcStart += 2;\n";
2286
+ DESERIALIZE_FAST += i3 + " complete = true;\n";
2287
+ DESERIALIZE_FAST += i3 + " break;\n";
2288
+ DESERIALIZE_FAST += i3 + "}\n";
2289
+ DESERIALIZE_FAST += i3 + "if (code != 0x2c) break; // comma\n";
2290
+ DESERIALIZE_FAST += i3 + "srcStart += 2;\n";
2291
+ DESERIALIZE_FAST += i2 + "}\n";
2292
+ DESERIALIZE_FAST += i2 + "if (!complete) break;\n";
2293
+ for (let i = 0; i < keyedResets.length; i++) {
2294
+ DESERIALIZE_FAST +=
2295
+ i2 + `if ((seen & (<u32>1 << ${i})) == 0) ${keyedResets[i]}\n`;
2296
+ }
1783
2297
  DESERIALIZE_FAST += i2 + "return srcStart;\n";
1784
2298
  DESERIALIZE_FAST += i1 + "} while (false);\n\n";
1785
2299
  }
@@ -1812,19 +2326,48 @@ export class JSONTransform extends Visitor {
1812
2326
  " while (srcEnd > srcStart && JSON.Util.isSpace(load<u16>(srcEnd - 2))) srcEnd -= 2;\n";
1813
2327
  DESERIALIZE +=
1814
2328
  indent +
1815
- ' if (srcStart - srcEnd == 0) throw new Error("Input string had zero length or was all whitespace");\n';
2329
+ " if (srcStart == srcEnd) return JSON.Util.failProductionParse();\n";
1816
2330
  DESERIALIZE +=
1817
2331
  indent +
1818
- " if (load<u16>(srcStart) != 123) throw new Error(\"Expected '{' at start of object at position \" + (srcEnd - srcStart).toString());\n";
2332
+ " if (load<u16>(srcStart) != 123) return JSON.Util.failProductionParse();\n";
1819
2333
  DESERIALIZE +=
1820
2334
  indent +
1821
- " if (load<u16>(srcEnd - 2) != 125) throw new Error(\"Expected '}' at end of object at position \" + (srcEnd - srcStart).toString());\n";
2335
+ " if (load<u16>(srcEnd - 2) != 125) return JSON.Util.failProductionParse();\n";
2336
+ DESERIALIZE += indent + " const objectStart = srcStart;\n";
1822
2337
  DESERIALIZE += indent + " srcStart += 2;\n\n";
1823
2338
  DESERIALIZE += indent + " while (srcStart < srcEnd) {\n";
1824
2339
  DESERIALIZE += indent + " let code = load<u16>(srcStart);\n";
2340
+ DESERIALIZE += indent + " while (JSON.Util.isSpace(code)) {\n";
2341
+ DESERIALIZE += indent + " srcStart += 2;\n";
1825
2342
  DESERIALIZE +=
1826
2343
  indent +
1827
- " while (JSON.Util.isSpace(code)) code = load<u16>(srcStart += 2);\n";
2344
+ " if (srcStart >= srcEnd) return JSON.Util.failProductionParse();\n";
2345
+ DESERIALIZE += indent + " code = load<u16>(srcStart);\n";
2346
+ DESERIALIZE += indent + " }\n";
2347
+ DESERIALIZE += indent + " if (keyStart == 0 && !isKey) {\n";
2348
+ DESERIALIZE += indent + " let prev = srcStart - 2;\n";
2349
+ DESERIALIZE +=
2350
+ indent +
2351
+ " while (prev > objectStart && JSON.Util.isSpace(load<u16>(prev))) prev -= 2;\n";
2352
+ DESERIALIZE += indent + " const prevCode = load<u16>(prev);\n";
2353
+ DESERIALIZE += indent + " if (code == 125) {\n";
2354
+ DESERIALIZE +=
2355
+ indent +
2356
+ " if (prevCode == 44) return JSON.Util.failProductionParse();\n";
2357
+ DESERIALIZE += indent + " return srcStart + 2;\n";
2358
+ DESERIALIZE += indent + " }\n";
2359
+ DESERIALIZE += indent + " if (code == 44) {\n";
2360
+ DESERIALIZE +=
2361
+ indent +
2362
+ " if (prevCode == 123 || prevCode == 44) return JSON.Util.failProductionParse();\n";
2363
+ DESERIALIZE += indent + " } else if (code == 34) {\n";
2364
+ DESERIALIZE +=
2365
+ indent +
2366
+ " if (prevCode != 123 && prevCode != 44) return JSON.Util.failProductionParse();\n";
2367
+ DESERIALIZE += indent + " } else {\n";
2368
+ DESERIALIZE += indent + " return JSON.Util.failProductionParse();\n";
2369
+ DESERIALIZE += indent + " }\n";
2370
+ DESERIALIZE += indent + " }\n";
1828
2371
  DESERIALIZE += indent + " if (keyStart == 0) {\n";
1829
2372
  DESERIALIZE +=
1830
2373
  indent + " if (code == 34 && load<u16>(srcStart - 2) !== 92) {\n";
@@ -1837,20 +2380,16 @@ export class JSONTransform extends Visitor {
1837
2380
  ' console.log("Key: " + JSON.Util.ptrToStr(keyStart, keyEnd));\n';
1838
2381
  DESERIALIZE +=
1839
2382
  indent +
1840
- " while (JSON.Util.isSpace((code = load<u16>((srcStart += 2))))) {}\n";
2383
+ " do { srcStart += 2; if (srcStart >= srcEnd) return JSON.Util.failProductionParse(); code = load<u16>(srcStart); } while (JSON.Util.isSpace(code));\n";
1841
2384
  DESERIALIZE +=
1842
2385
  indent +
1843
- " if (code !== 58) throw new Error(\"Expected ':' after key at position \" + (srcEnd - srcStart).toString());\n";
2386
+ " if (code !== 58) return JSON.Util.failProductionParse();\n";
1844
2387
  DESERIALIZE += indent + " isKey = false;\n";
1845
2388
  DESERIALIZE += indent + " } else {\n";
1846
2389
  DESERIALIZE += indent + " isKey = true;\n";
1847
2390
  DESERIALIZE += indent + " lastIndex = srcStart + 2;\n";
1848
2391
  DESERIALIZE += indent + " }\n";
1849
2392
  DESERIALIZE += indent + " }\n";
1850
- if (STRICT)
1851
- DESERIALIZE +=
1852
- indent +
1853
- ' else if (!isKey && code != 44 && code != 125) throw new Error("Expected \'\\"\' to start key in JSON object at position " + (srcEnd - srcStart).toString());\n';
1854
2393
  DESERIALIZE += indent + " srcStart += 2;\n";
1855
2394
  DESERIALIZE += indent + " } else {\n";
1856
2395
  const groupMembers = (members) => {
@@ -1953,8 +2492,18 @@ export class JSONTransform extends Visitor {
1953
2492
  const getSlowValueStore = (member, valueStart, valueEnd, prefix) => {
1954
2493
  if (member.flags.has(PropertyFlags.Lazy))
1955
2494
  return getLazyRangeStore(member, valueStart, valueEnd, prefix);
2495
+ const offset = JSON.stringify(member.name);
2496
+ const memberSchema = this.getSchema(member.type);
2497
+ if (memberSchema?.custom) {
2498
+ return (prefix +
2499
+ `store<${member.type}>(changetype<usize>(out), ${getCustomDeserializeExpression(member.type, valueStart, valueEnd)}, offsetof<this>(${offset}));\n`);
2500
+ }
2501
+ if (needsReferenceLoad(member.type)) {
2502
+ return (prefix +
2503
+ `store<${member.type}>(changetype<usize>(out), JSON.__deserialize<${member.type}>(${valueStart}, ${valueEnd}, changetype<usize>(load<${member.type}>(changetype<usize>(out), offsetof<this>(${offset})))), offsetof<this>(${offset}));\n`);
2504
+ }
1956
2505
  return (prefix +
1957
- `store<${member.type}>(changetype<usize>(out), JSON.__deserialize<${member.type}>(${valueStart}, ${valueEnd}), offsetof<this>(${JSON.stringify(member.name)}));\n`);
2506
+ `store<${member.type}>(changetype<usize>(out), JSON.__deserialize<${member.type}>(${valueStart}, ${valueEnd}), offsetof<this>(${offset}));\n`);
1958
2507
  };
1959
2508
  const getSlowBooleanStore = (member, value, valueStart, valueEnd, prefix) => {
1960
2509
  if (member.flags.has(PropertyFlags.Lazy))
@@ -1980,10 +2529,10 @@ export class JSONTransform extends Visitor {
1980
2529
  DESERIALIZE += mbElse + "if (code == 34) {\n";
1981
2530
  DESERIALIZE += " lastIndex = srcStart;\n";
1982
2531
  DESERIALIZE += " srcStart += 2;\n";
2532
+ DESERIALIZE += " let escaped = false;\n";
1983
2533
  DESERIALIZE += " while (srcStart < srcEnd) {\n";
1984
2534
  DESERIALIZE += " const code = load<u16>(srcStart);\n";
1985
- DESERIALIZE +=
1986
- " if (code == 34 && load<u16>(srcStart - 2) !== 92) {\n";
2535
+ DESERIALIZE += " if (code == 34 && !escaped) {\n";
1987
2536
  if (DEBUG > 1)
1988
2537
  DESERIALIZE +=
1989
2538
  ' console.log("Value (string, ' +
@@ -2039,6 +2588,7 @@ export class JSONTransform extends Visitor {
2039
2588
  }
2040
2589
  }, "string");
2041
2590
  DESERIALIZE += " }\n";
2591
+ DESERIALIZE += " escaped = code == 92 ? !escaped : false;\n";
2042
2592
  DESERIALIZE += " srcStart += 2;\n";
2043
2593
  DESERIALIZE += " }\n";
2044
2594
  DESERIALIZE += " }\n";
@@ -2468,6 +3018,11 @@ export class JSONTransform extends Visitor {
2468
3018
  indentDec();
2469
3019
  DESERIALIZE += ` }\n`;
2470
3020
  indentDec();
3021
+ DESERIALIZE +=
3022
+ ` if (srcStart == srcEnd && lastIndex != 0) {\n` +
3023
+ ` const lastCode = load<u16>(lastIndex);\n` +
3024
+ ` if (lastCode == 123 || lastCode == 91) return 0;\n` +
3025
+ ` }\n`;
2471
3026
  DESERIALIZE += ` return srcStart;\n}\n`;
2472
3027
  indent = " ";
2473
3028
  this.schema.byteSize += 2;
@@ -2488,11 +3043,6 @@ export class JSONTransform extends Visitor {
2488
3043
  console.log(INITIALIZE);
2489
3044
  console.log(DESERIALIZE_CUSTOM || DESERIALIZE);
2490
3045
  }
2491
- const WIDE_STRUCT_FIELD_LIMIT = 32;
2492
- if (this.schema.members.length > WIDE_STRUCT_FIELD_LIMIT) {
2493
- INITIALIZE = INITIALIZE.replace(/^@inline /, "");
2494
- DESERIALIZE_FAST = DESERIALIZE_FAST.replace(/^@inline /, "");
2495
- }
2496
3046
  const SERIALIZE_METHOD = SimpleParser.parseClassMember(SERIALIZE_CUSTOM || SERIALIZE, node);
2497
3047
  const INITIALIZE_METHOD = SimpleParser.parseClassMember(INITIALIZE, node);
2498
3048
  const DESERIALIZE_CUSTOM_METHOD = DESERIALIZE_CUSTOM
@@ -2502,6 +3052,86 @@ export class JSONTransform extends Visitor {
2502
3052
  const DESERIALIZE_FAST_METHOD = useFastPath
2503
3053
  ? SimpleParser.parseClassMember(DESERIALIZE_FAST, node)
2504
3054
  : null;
3055
+ const mapHeavySource = source
3056
+ .getClasses()
3057
+ .some((candidate) => candidate.members.some((member) => member.kind == NodeKind.FieldDeclaration &&
3058
+ toString(member.type).includes("Map<")));
3059
+ let DESERIALIZE_FAST_PRETTY_METHOD = null;
3060
+ let PRETTY_SPECIALIZED_METHOD = null;
3061
+ if (this.program.options.hasFeature(16) &&
3062
+ useFastPath &&
3063
+ mapHeavySource) {
3064
+ let prettyTail = DESERIALIZE_FAST.slice(fastPrettyTailStart);
3065
+ prettyTail = prettyTail.replaceAll(" if (load<u16>(srcStart) != 0x3a) break; // :\n" +
3066
+ " srcStart += 2;\n" +
3067
+ " srcStart = JSON.Util.skipWhitespace(srcStart, srcEnd);\n", " if (load<u16>(srcStart) != 0x3a) break; // :\n" +
3068
+ " srcStart += 2;\n" +
3069
+ " if (load<u16>(srcStart) == 0x20) srcStart += 2;\n" +
3070
+ " else srcStart = JSON.Util.skipWhitespace(srcStart, srcEnd);\n");
3071
+ const prettyFast = ("__DESERIALIZE_FAST_PRETTY<__JSON_T>(srcStart: usize, srcEnd: usize, out: __JSON_T): usize {\n" +
3072
+ " const start = srcStart;\n" +
3073
+ " const dst = changetype<usize>(out);\n" +
3074
+ objectTraceHeader +
3075
+ prettyTail)
3076
+ .replaceAll("JSON.Util.skipWhitespace(", "JSON.Util.skipPrettyWhitespace(")
3077
+ .replaceAll(".__DESERIALIZE_FAST<", ".__DESERIALIZE_FAST_PRETTY<")
3078
+ .replaceAll(".__DESERIALIZE_FAST(", ".__DESERIALIZE_FAST_PRETTY(");
3079
+ DESERIALIZE_FAST_PRETTY_METHOD = SimpleParser.parseClassMember(prettyFast, node);
3080
+ PRETTY_SPECIALIZED_METHOD = SimpleParser.parseClassMember("__DESERIALIZE_PRETTY_SPECIALIZED(): void {}", node);
3081
+ }
3082
+ else if (this.program.options.hasFeature(16) && useFastPath) {
3083
+ const prettyTail = DESERIALIZE_FAST.slice(fastPrettyTailStart);
3084
+ DESERIALIZE_FAST_PRETTY_METHOD = SimpleParser.parseClassMember("__DESERIALIZE_FAST_PRETTY<__JSON_T>(srcStart: usize, srcEnd: usize, out: __JSON_T): usize {\n" +
3085
+ " const start = srcStart;\n" +
3086
+ " const dst = changetype<usize>(out);\n" +
3087
+ objectTraceHeader +
3088
+ prettyTail +
3089
+ "}", node);
3090
+ }
3091
+ else if (useFastPath) {
3092
+ DESERIALIZE_FAST_PRETTY_METHOD = SimpleParser.parseClassMember("__DESERIALIZE_FAST_PRETTY<__JSON_T>(srcStart: usize, srcEnd: usize, out: __JSON_T): usize {\n" +
3093
+ " return this.__DESERIALIZE_FAST(srcStart, srcEnd, out);\n" +
3094
+ "}", node);
3095
+ }
3096
+ const sourceFreeDeserialize = !DESERIALIZE_CUSTOM &&
3097
+ this.schema.members.every((member) => {
3098
+ const type = stripNull(member.type);
3099
+ return (!member.flags.has(PropertyFlags.Lazy) &&
3100
+ (isPrimitive(type) ||
3101
+ isString(type) ||
3102
+ type == "Date" ||
3103
+ type == "JSON.Raw" ||
3104
+ type == "Raw"));
3105
+ });
3106
+ const SOURCE_FREE_METHOD = sourceFreeDeserialize
3107
+ ? SimpleParser.parseClassMember("__DESERIALIZE_SOURCE_FREE(): void {}", node)
3108
+ : null;
3109
+ const FULL_WRITE_METHOD = useFastPath && !supportsFastOptionalPath
3110
+ ? SimpleParser.parseClassMember("__DESERIALIZE_FULL_WRITE(): void {}", node)
3111
+ : null;
3112
+ const defaultObjectJSON = USE_DEFAULT_OBJECT_SPECIALIZATION &&
3113
+ this.program.options.hasFeature(16) &&
3114
+ useFastPath &&
3115
+ !supportsFastOptionalPath &&
3116
+ !DESERIALIZE_CUSTOM &&
3117
+ !node.extendsType &&
3118
+ !node.is(2) &&
3119
+ !source.getClasses().some((candidate) => {
3120
+ if (!candidate.extendsType)
3121
+ return false;
3122
+ const base = source.resolveExtendsName(candidate);
3123
+ return base == this.schema.name || base == node.name.text;
3124
+ })
3125
+ ? this.getDefaultObjectJSON(this.schema)
3126
+ : null;
3127
+ const DEFAULT_OBJECT_METHOD = defaultObjectJSON
3128
+ ? SimpleParser.parseClassMember(this.getDefaultMatcherSource(defaultObjectJSON), node)
3129
+ : null;
3130
+ const DEFAULT_OBJECT_FACTORY_METHOD = defaultObjectJSON
3131
+ ? SimpleParser.parseClassMember("@inline __DESERIALIZE_DEFAULT_NEW(): this {\n" +
3132
+ " return changetype<this>(__new(offsetof<this>(), idof<this>())).__INITIALIZE();\n" +
3133
+ "}", node)
3134
+ : null;
2505
3135
  if (!node.members.find((v) => v.name.text == "__SERIALIZE"))
2506
3136
  node.members.push(SERIALIZE_METHOD);
2507
3137
  if (INITIALIZE_METHOD &&
@@ -2519,6 +3149,26 @@ export class JSONTransform extends Visitor {
2519
3149
  DESERIALIZE_FAST_METHOD &&
2520
3150
  !node.members.find((v) => v.name.text == "__DESERIALIZE_FAST"))
2521
3151
  node.members.push(DESERIALIZE_FAST_METHOD);
3152
+ if (!DESERIALIZE_CUSTOM &&
3153
+ DESERIALIZE_FAST_PRETTY_METHOD &&
3154
+ !node.members.find((v) => v.name.text == "__DESERIALIZE_FAST_PRETTY"))
3155
+ node.members.push(DESERIALIZE_FAST_PRETTY_METHOD);
3156
+ if (!DESERIALIZE_CUSTOM &&
3157
+ PRETTY_SPECIALIZED_METHOD &&
3158
+ !node.members.find((v) => v.name.text == "__DESERIALIZE_PRETTY_SPECIALIZED"))
3159
+ node.members.push(PRETTY_SPECIALIZED_METHOD);
3160
+ if (SOURCE_FREE_METHOD &&
3161
+ !node.members.find((v) => v.name.text == "__DESERIALIZE_SOURCE_FREE"))
3162
+ node.members.push(SOURCE_FREE_METHOD);
3163
+ if (FULL_WRITE_METHOD &&
3164
+ !node.members.find((v) => v.name.text == "__DESERIALIZE_FULL_WRITE"))
3165
+ node.members.push(FULL_WRITE_METHOD);
3166
+ if (DEFAULT_OBJECT_METHOD &&
3167
+ !node.members.find((v) => v.name.text == "__DESERIALIZE_DEFAULT"))
3168
+ node.members.push(DEFAULT_OBJECT_METHOD);
3169
+ if (DEFAULT_OBJECT_FACTORY_METHOD &&
3170
+ !node.members.find((v) => v.name.text == "__DESERIALIZE_DEFAULT_NEW"))
3171
+ node.members.push(DEFAULT_OBJECT_FACTORY_METHOD);
2522
3172
  if (useFastPath && !DESERIALIZE_CUSTOM) {
2523
3173
  for (const chunk of fastChunkMethods) {
2524
3174
  const chunkMethod = SimpleParser.parseClassMember(chunk, node);
@@ -2530,9 +3180,16 @@ export class JSONTransform extends Visitor {
2530
3180
  }
2531
3181
  getSchema(name) {
2532
3182
  name = stripNull(name);
2533
- return (this.schemas
3183
+ const local = this.schemas
2534
3184
  .get(this.schema.node.range.source.internalPath)
2535
- .find((s) => s.name == name) || null);
3185
+ .find((s) => s.name == name) || null;
3186
+ if (local)
3187
+ return local;
3188
+ const exact = this.schema.deps.filter((schema) => schema && schema.name == name);
3189
+ if (exact.length == 1)
3190
+ return exact[0];
3191
+ const qualified = this.schema.deps.filter((schema) => schema && schema.name.endsWith("." + name));
3192
+ return qualified.length == 1 ? qualified[0] : null;
2536
3193
  }
2537
3194
  generateEmptyMethods(node) {
2538
3195
  const SERIALIZE_EMPTY = "__SERIALIZE(ptr: usize): void {\n bs.proposeSize(4);\n store<u32>(bs.offset, 8192123);\n bs.offset += 4;\n}";
@@ -2632,6 +3289,7 @@ export class JSONTransform extends Visitor {
2632
3289
  fieldHelper("deserializeUnsignedField", "__deserializeUnsignedField"),
2633
3290
  fieldHelper("deserializeFloatField", "__deserializeFloatField"),
2634
3291
  fieldHelper("deserializeStringField", "__deserializeStringField"),
3292
+ fieldHelper("deserializeStringFieldTrusted", "__deserializeStringFieldTrusted"),
2635
3293
  fieldHelper("deserializeArrayField_SWAR", "__deserializeArrayField_SWAR"),
2636
3294
  fieldHelper("deserializeMapField", "__deserializeMapField"),
2637
3295
  fieldHelper("deserializeSetField", "__deserializeSetField"),
@@ -2784,6 +3442,7 @@ export default class Transformer extends Transform {
2784
3442
  MODE_TEXT +
2785
3443
  " mode");
2786
3444
  program.registerConstantInteger("JSON_MODE", Type.i32, i64_new(MODE));
3445
+ program.registerConstantInteger("JSON_STRICT", Type.bool, STRICT ? i64_one : i64_zero);
2787
3446
  if (JSON_CACHE_CONFIG.enabled) {
2788
3447
  program.registerConstantInteger("JSON_CACHE", Type.bool, i64_one);
2789
3448
  program.registerConstantInteger("JSON_CACHE_SIZE", Type.u32, i64_new(JSON_CACHE_CONFIG.bytes));