json-as 1.3.2 → 1.3.4

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 (34) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/README.md +3 -2
  3. package/assembly/deserialize/simd/string.ts +20 -0
  4. package/assembly/deserialize/simple/map.ts +2 -0
  5. package/assembly/deserialize/simple/staticarray.ts +1 -0
  6. package/assembly/deserialize/simple/struct.ts +3 -1
  7. package/assembly/deserialize/swar/array/bool.ts +1 -0
  8. package/assembly/deserialize/swar/array/generic.ts +2 -1
  9. package/assembly/deserialize/swar/array/integer.ts +1 -0
  10. package/assembly/deserialize/swar/array/object.ts +1 -0
  11. package/assembly/deserialize/swar/array/shared.ts +18 -3
  12. package/assembly/deserialize/swar/array/string.ts +1 -0
  13. package/assembly/deserialize/swar/array/struct.ts +1 -0
  14. package/assembly/deserialize/swar/array.ts +1 -0
  15. package/assembly/deserialize/swar/string.ts +21 -2
  16. package/assembly/index.d.ts +1 -0
  17. package/assembly/index.ts +27 -22
  18. package/assembly/serialize/simd/string.ts +43 -16
  19. package/assembly/serialize/simple/array.ts +48 -6
  20. package/assembly/serialize/simple/bool.ts +12 -0
  21. package/assembly/serialize/simple/float.ts +16 -0
  22. package/assembly/serialize/simple/integer.ts +6 -0
  23. package/assembly/serialize/simple/set.ts +59 -5
  24. package/assembly/serialize/simple/staticarray.ts +57 -3
  25. package/assembly/serialize/simple/typedarray.ts +29 -9
  26. package/assembly/serialize/swar/string.ts +165 -18
  27. package/assembly/tsconfig.json +2 -2
  28. package/assembly/util/dragonbox-cache.ts +2 -1320
  29. package/assembly/util/dragonbox.ts +63 -35
  30. package/lib/as-bs.ts +26 -18
  31. package/package.json +11 -10
  32. package/transform/lib/index.d.ts.map +1 -1
  33. package/transform/lib/index.js +288 -73
  34. package/transform/lib/index.js.map +1 -1
@@ -13,7 +13,59 @@ const WRITE = process.env["JSON_WRITE"]?.trim();
13
13
  const rawValue = process.env["JSON_DEBUG"]?.trim();
14
14
  const DEBUG = rawValue === "true" ? 1 : rawValue === "false" || rawValue === "" ? 0 : isNaN(Number(rawValue)) ? 0 : Number(rawValue);
15
15
  const STRICT = process.env["JSON_STRICT"] && process.env["JSON_STRICT"] == "true";
16
- const USE_FAST_PATH = process.env["JSON_USE_FAST_PATH"]?.trim() === "1";
16
+ const DEFAULT_JSON_CACHE_BYTES = 1 << 20;
17
+ function envFlagDefaultTrue(value) {
18
+ if (!value)
19
+ return true;
20
+ switch (value.trim().toLowerCase()) {
21
+ case "0":
22
+ case "false":
23
+ case "off":
24
+ case "no":
25
+ return false;
26
+ default:
27
+ return true;
28
+ }
29
+ }
30
+ const USE_FAST_PATH = envFlagDefaultTrue(process.env["JSON_USE_FAST_PATH"]);
31
+ const THROW_FAST_PATH = process.env["JSON_FAST_PATH_THROW"]?.trim() === "1";
32
+ function parseJSONCacheConfig(value) {
33
+ if (!value)
34
+ return { enabled: false, bytes: 0 };
35
+ const raw = value.trim();
36
+ if (!raw)
37
+ return { enabled: false, bytes: 0 };
38
+ const lower = raw.toLowerCase();
39
+ if (lower === "false" || lower === "off" || lower === "no" || lower === "none" || lower === "0") {
40
+ return { enabled: false, bytes: 0 };
41
+ }
42
+ if (lower === "true" || lower === "on" || lower === "yes") {
43
+ return { enabled: true, bytes: DEFAULT_JSON_CACHE_BYTES };
44
+ }
45
+ const match = /^(\d+)\s*([kKmMgG]?[bB])?$/.exec(raw);
46
+ if (!match) {
47
+ throw new Error(`Invalid JSON_CACHE value '${value}'. Expected true/false or <int>[kb|mb|gb|KB|MB|GB].`);
48
+ }
49
+ const amount = Number(match[1]);
50
+ const suffix = match[2] || "B";
51
+ if (!Number.isFinite(amount)) {
52
+ throw new Error(`Invalid JSON_CACHE value '${value}'.`);
53
+ }
54
+ const unit = suffix[0];
55
+ const scale = unit == "k" || unit == "K" ? 1_000 : unit == "m" || unit == "M" ? 1_000_000 : unit == "g" || unit == "G" ? 1_000_000_000 : 1;
56
+ let bytes = amount * scale;
57
+ if (suffix.endsWith("b")) {
58
+ bytes = Math.ceil(bytes / 8);
59
+ }
60
+ if (bytes <= 0) {
61
+ return { enabled: false, bytes: 0 };
62
+ }
63
+ if (bytes > 0xffff_ffff) {
64
+ throw new Error(`JSON_CACHE value '${value}' is too large (max 4GB).`);
65
+ }
66
+ return { enabled: true, bytes: Math.floor(bytes) };
67
+ }
68
+ const JSON_CACHE_CONFIG = parseJSONCacheConfig(process.env["JSON_CACHE"]);
17
69
  function needsReferenceLoad(type) {
18
70
  return type == "ArrayBuffer" || type == "Int8Array" || type == "Uint8Array" || type == "Uint8ClampedArray" || type == "Int16Array" || type == "Uint16Array" || type == "Int32Array" || type == "Uint32Array" || type == "Int64Array" || type == "Uint64Array" || type == "Float32Array" || type == "Float64Array";
19
71
  }
@@ -23,6 +75,9 @@ function getSerializeCall(type, realName) {
23
75
  }
24
76
  return needsReferenceLoad(type) ? `JSON.__serialize<${type}>(changetype<${type}>(load<usize>(ptr, offsetof<this>(${JSON.stringify(realName)}))));\n` : `JSON.__serialize<${type}>(load<${type}>(ptr, offsetof<this>(${JSON.stringify(realName)})));\n`;
25
77
  }
78
+ function isRawType(type) {
79
+ return type == "JSON.Raw" || type == "Raw";
80
+ }
26
81
  const CUSTOM_JSON_KINDS = new Set(["any", "string", "number", "object", "array", "boolean", "null", "any | null", "string | null", "number | null", "object | null", "array | null", "boolean | null"]);
27
82
  function parseCustomJsonKind(method, decoratorName) {
28
83
  const decorator = method.decorators?.find((v) => v.name.text.toLowerCase() == decoratorName);
@@ -446,7 +501,7 @@ export class JSONTransform extends Visitor {
446
501
  mem.type = type;
447
502
  mem.value = value;
448
503
  mem.node = member;
449
- mem.byteSize = sizeof(mem.type);
504
+ mem.byteSize = estimatedSerializedByteSize(mem.type, source, this.parser);
450
505
  mem.custom = schema.deps.some((dep) => dep?.name == stripNull(type) && dep.custom);
451
506
  this.schema.byteSize += mem.byteSize;
452
507
  if (member.decorators) {
@@ -630,8 +685,14 @@ export class JSONTransform extends Visitor {
630
685
  sortedMembers.null.push(member);
631
686
  if (isString(type) || type == "Date")
632
687
  sortedMembers.string.push(member);
633
- else if (type == "JSON.Raw")
688
+ else if (isRawType(type)) {
689
+ sortedMembers.string.push(member);
690
+ sortedMembers.number.push(member);
691
+ sortedMembers.boolean.push(member);
692
+ sortedMembers.null.push(member);
634
693
  sortedMembers.object.push(member);
694
+ sortedMembers.array.push(member);
695
+ }
635
696
  else if (isBoolean(type) || type.startsWith("JSON.Box<bool"))
636
697
  sortedMembers.boolean.push(member);
637
698
  else if (isPrimitive(type) || type.startsWith("JSON.Box<") || isEnum(type, this.sources.get(this.schema.node.range.source), this.parser))
@@ -671,6 +732,11 @@ export class JSONTransform extends Visitor {
671
732
  const FLOAT_TYPES = ["f32", "f64"];
672
733
  const INTEGER_TYPES = [...UNSIGNED_INTEGER_TYPES, ...SIGNED_INTEGER_TYPES];
673
734
  const STRING_FIELD_DESERIALIZER = codegenMode === JSONMode.SIMD ? "deserializeStringField_SIMD" : "deserializeStringField_SWAR";
735
+ const getArrayValueType = (type) => {
736
+ if (!type.startsWith("Array<") && !type.startsWith("StaticArray<"))
737
+ return null;
738
+ return stripNull(type.slice(type.indexOf("<") + 1, -1).trim());
739
+ };
674
740
  const getDeserializer = (type, srcPtr, outPtr, member, keyOffset = 0, fastPath = false) => {
675
741
  const out = [];
676
742
  const resolvedType = stripNull(type);
@@ -706,7 +772,7 @@ export class JSONTransform extends Visitor {
706
772
  out.push(` if (load<u16>(${valuePtr}) != 0x22) break;`);
707
773
  out.push(` let dateEnd = ${valuePtr} + 2;`);
708
774
  out.push(` while (dateEnd < srcEnd) {`);
709
- out.push(" if (load<u16>(dateEnd) == 0x22 && load<u16>(dateEnd - 2) != 0x5c) break;");
775
+ out.push(" if (load<u16>(dateEnd) == 0x22 && JSON.Util.isUnescapedQuote(dateEnd)) break;");
710
776
  out.push(" dateEnd += 2;");
711
777
  out.push(" }");
712
778
  out.push(" if (dateEnd >= srcEnd) break;");
@@ -751,7 +817,7 @@ export class JSONTransform extends Visitor {
751
817
  }
752
818
  out.push("}");
753
819
  }
754
- else if (resolvedType == "JSON.Raw") {
820
+ else if (isRawType(resolvedType)) {
755
821
  out.push("{");
756
822
  out.push(` const valueStart = ${srcPtr};`);
757
823
  out.push(" let depth: i32 = 0;");
@@ -759,7 +825,7 @@ export class JSONTransform extends Visitor {
759
825
  out.push(` while (${srcPtr} < srcEnd) {`);
760
826
  out.push(` const code = load<u16>(${srcPtr});`);
761
827
  out.push(" if (inString) {");
762
- out.push(` if (code == 0x22 && load<u16>(${srcPtr} - 2) != 0x5c) inString = false;`);
828
+ out.push(` if (code == 0x22 && JSON.Util.isUnescapedQuote(${srcPtr})) inString = false;`);
763
829
  out.push(` ${srcPtr} += 2;`);
764
830
  out.push(" continue;");
765
831
  out.push(" }");
@@ -800,7 +866,24 @@ export class JSONTransform extends Visitor {
800
866
  }
801
867
  else if (resolvedSchema && !resolvedSchema.custom) {
802
868
  if (fastPath) {
803
- out.push("break;");
869
+ out.push("{");
870
+ if (member.node.type.isNullable) {
871
+ out.push(` if (load<u64>(${valuePtr}) == 30399761348886638) {`);
872
+ out.push(` store<${resolvedType}>(${outPtr}, changetype<${resolvedType}>(0), ${fieldOffset});`);
873
+ out.push(` ${srcPtr} = ${valuePtr} + 8;`);
874
+ out.push(" } else {");
875
+ }
876
+ out.push(` let value = load<${resolvedType}>(${outPtr}, ${fieldOffset});`);
877
+ out.push(` if (changetype<usize>(value) == 0) {`);
878
+ out.push(` value = changetype<${resolvedType}>(__new(offsetof<nonnull<${resolvedType}>>(), idof<nonnull<${resolvedType}>>()));`);
879
+ out.push(` store<${resolvedType}>(${outPtr}, value, ${fieldOffset});`);
880
+ out.push(" }");
881
+ out.push(` ${srcPtr} = changetype<nonnull<${resolvedType}>>(value).__DESERIALIZE_FAST<${resolvedType}>(${valuePtr}, srcEnd, value);`);
882
+ out.push(` if (!${srcPtr}) break;`);
883
+ if (member.node.type.isNullable) {
884
+ out.push(" }");
885
+ }
886
+ out.push("}");
804
887
  return out;
805
888
  }
806
889
  out.push("{");
@@ -830,6 +913,7 @@ export class JSONTransform extends Visitor {
830
913
  out.push("}");
831
914
  }
832
915
  else if (resolvedType.startsWith("Array<")) {
916
+ const valueType = getArrayValueType(resolvedType);
833
917
  out.push("{");
834
918
  if (member.node.type.isNullable) {
835
919
  out.push(` if (load<u64>(${valuePtr}) == 30399761348886638) {`);
@@ -837,23 +921,96 @@ export class JSONTransform extends Visitor {
837
921
  out.push(` ${srcPtr} = ${valuePtr} + 8;`);
838
922
  out.push(" } else {");
839
923
  }
840
- out.push(` if (load<u16>(${valuePtr}) == 0x5b && load<u16>(${valuePtr}, 2) == 0x5d) {`);
841
- out.push(` let value = load<${resolvedType}>(${outPtr}, ${fieldOffset});`);
842
- if (member.node.type.isNullable) {
843
- out.push(` if (changetype<usize>(value) == 0) {`);
844
- out.push(` value = changetype<${resolvedType}>(instantiate<nonnull<${resolvedType}>>());`);
845
- out.push(` store<${resolvedType}>(${outPtr}, value, ${fieldOffset});`);
924
+ if (fastPath && valueType && ["string", "String"].includes(valueType)) {
925
+ out.push(` if (load<u16>(${valuePtr}) != 0x5b) break;`);
926
+ out.push(` let value = load<${resolvedType}>(${outPtr}, ${fieldOffset});`);
927
+ out.push(" if (changetype<usize>(value) == 0) {");
928
+ out.push(` value = instantiate<nonnull<${resolvedType}>>();`);
929
+ out.push(` store<${resolvedType}>(${outPtr}, value, ${fieldOffset});`);
930
+ out.push(" }");
931
+ out.push(" let index = 0;");
932
+ out.push(` ${srcPtr} = ${valuePtr} + 2;`);
933
+ out.push(` if (load<u16>(${srcPtr}) == 0x5d) {`);
934
+ out.push(" value.length = 0;");
935
+ out.push(` ${srcPtr} += 2;`);
936
+ out.push(" } else while (true) {");
937
+ out.push(' if (index >= value.length) value.push("");');
938
+ out.push(` ${srcPtr} = ${STRING_FIELD_DESERIALIZER}<${valueType}>(${srcPtr}, srcEnd, value.dataStart + ((<usize>index) << alignof<${valueType}>()));`);
939
+ out.push(" index++;");
940
+ out.push(` const code = load<u16>(${srcPtr});`);
941
+ out.push(" if (code == 0x2c) {");
942
+ out.push(` ${srcPtr} += 2;`);
943
+ out.push(" continue;");
944
+ out.push(" }");
945
+ out.push(" if (code == 0x5d) {");
946
+ out.push(" value.length = index;");
947
+ out.push(` ${srcPtr} += 2;`);
948
+ out.push(" break;");
846
949
  out.push(" }");
950
+ out.push(" break;");
951
+ out.push(" }");
952
+ if (member.node.type.isNullable) {
953
+ out.push(" }");
954
+ }
955
+ out.push("}");
956
+ return out;
847
957
  }
958
+ const valueSchema = valueType ? this.getSchema(valueType) : null;
959
+ if (fastPath && valueType && valueSchema && !valueSchema.custom) {
960
+ out.push(` if (load<u16>(${valuePtr}) != 0x5b) break;`);
961
+ out.push(` let value = load<${resolvedType}>(${outPtr}, ${fieldOffset});`);
962
+ out.push(" if (changetype<usize>(value) == 0) {");
963
+ out.push(` value = instantiate<nonnull<${resolvedType}>>();`);
964
+ out.push(` store<${resolvedType}>(${outPtr}, value, ${fieldOffset});`);
965
+ out.push(" }");
966
+ out.push(" let index = 0;");
967
+ out.push(` ${srcPtr} = ${valuePtr} + 2;`);
968
+ out.push(` if (load<u16>(${srcPtr}) == 0x5d) {`);
969
+ out.push(" value.length = 0;");
970
+ out.push(` ${srcPtr} += 2;`);
971
+ out.push(" } else while (true) {");
972
+ out.push(` let item: ${valueType};`);
973
+ out.push(" if (index < value.length) {");
974
+ out.push(" item = unchecked(value[index]);");
975
+ out.push(" if (changetype<usize>(item) == 0) {");
976
+ out.push(` item = changetype<${valueType}>(__new(offsetof<nonnull<${valueType}>>(), idof<nonnull<${valueType}>>()));`);
977
+ out.push(" unchecked((value[index] = item));");
978
+ out.push(" }");
979
+ out.push(" } else {");
980
+ out.push(` item = changetype<${valueType}>(__new(offsetof<nonnull<${valueType}>>(), idof<nonnull<${valueType}>>()));`);
981
+ out.push(" value.push(item);");
982
+ out.push(" }");
983
+ out.push(` ${srcPtr} = changetype<nonnull<${valueType}>>(item).__DESERIALIZE_FAST<${valueType}>(${srcPtr}, srcEnd, item);`);
984
+ out.push(` if (!${srcPtr}) break;`);
985
+ out.push(" index++;");
986
+ out.push(` const code = load<u16>(${srcPtr});`);
987
+ out.push(" if (code == 0x2c) {");
988
+ out.push(` ${srcPtr} += 2;`);
989
+ out.push(" continue;");
990
+ out.push(" }");
991
+ out.push(" if (code == 0x5d) {");
992
+ out.push(" value.length = index;");
993
+ out.push(` ${srcPtr} += 2;`);
994
+ out.push(" break;");
995
+ out.push(" }");
996
+ out.push(" break;");
997
+ out.push(" }");
998
+ if (member.node.type.isNullable) {
999
+ out.push(" }");
1000
+ }
1001
+ out.push("}");
1002
+ return out;
1003
+ }
1004
+ out.push(` let value = load<${resolvedType}>(${outPtr}, ${fieldOffset});`);
1005
+ out.push(` if (changetype<usize>(value) == 0) {`);
1006
+ out.push(` value = changetype<${resolvedType}>(instantiate<nonnull<${resolvedType}>>());`);
1007
+ out.push(` store<${resolvedType}>(${outPtr}, value, ${fieldOffset});`);
1008
+ out.push(" }");
1009
+ out.push(` if (load<u16>(${valuePtr}) == 0x5b && load<u16>(${valuePtr}, 2) == 0x5d) {`);
848
1010
  out.push(" value.length = 0;");
849
1011
  out.push(` ${srcPtr} = ${valuePtr} + 4;`);
850
1012
  out.push(" } else {");
851
- if (member.node.type.isNullable) {
852
- out.push(` ${srcPtr} = deserializeArrayField_SWAR<${resolvedType}>(${valuePtr}, srcEnd, ${outPtr}, ${fieldOffset});`);
853
- }
854
- else {
855
- out.push(` ${srcPtr} = deserializeArrayInto_SWAR<${resolvedType}>(${valuePtr}, srcEnd, load<${resolvedType}>(${outPtr}, ${fieldOffset}));`);
856
- }
1013
+ out.push(` ${srcPtr} = deserializeArrayInto_SWAR<${resolvedType}>(${valuePtr}, srcEnd, value);`);
857
1014
  out.push(` if (!${srcPtr}) break;`);
858
1015
  out.push(" }");
859
1016
  if (member.node.type.isNullable) {
@@ -865,6 +1022,16 @@ export class JSONTransform extends Visitor {
865
1022
  if (member.node.type.isNullable) {
866
1023
  out.push(`${srcPtr} = deserializeMapField<${resolvedType}>(${srcPtr}, srcEnd, ${outPtr}, ${fieldOffset});`);
867
1024
  }
1025
+ else if (fastPath) {
1026
+ out.push("{");
1027
+ out.push(` let value = load<${resolvedType}>(${outPtr}, ${fieldOffset});`);
1028
+ out.push(" if (changetype<usize>(value) == 0) {");
1029
+ out.push(` value = new ${resolvedType}();`);
1030
+ out.push(` store<${resolvedType}>(${outPtr}, value, ${fieldOffset});`);
1031
+ out.push(" }");
1032
+ out.push(` ${srcPtr} = deserializeMapInto<${resolvedType}>(${srcPtr}, srcEnd, value);`);
1033
+ out.push("}");
1034
+ }
868
1035
  else {
869
1036
  out.push(`${srcPtr} = deserializeMapInto<${resolvedType}>(${srcPtr}, srcEnd, load<${resolvedType}>(${outPtr}, ${fieldOffset}));`);
870
1037
  }
@@ -874,6 +1041,16 @@ export class JSONTransform extends Visitor {
874
1041
  if (member.node.type.isNullable) {
875
1042
  out.push(`${srcPtr} = deserializeSetField<${resolvedType}>(${srcPtr}, srcEnd, ${outPtr}, ${fieldOffset});`);
876
1043
  }
1044
+ else if (fastPath) {
1045
+ out.push("{");
1046
+ out.push(` let value = load<${resolvedType}>(${outPtr}, ${fieldOffset});`);
1047
+ out.push(" if (changetype<usize>(value) == 0) {");
1048
+ out.push(` value = new ${resolvedType}();`);
1049
+ out.push(` store<${resolvedType}>(${outPtr}, value, ${fieldOffset});`);
1050
+ out.push(" }");
1051
+ out.push(` ${srcPtr} = deserializeSetInto<${resolvedType}>(${srcPtr}, srcEnd, value);`);
1052
+ out.push("}");
1053
+ }
877
1054
  else {
878
1055
  out.push(`${srcPtr} = deserializeSetInto<${resolvedType}>(${srcPtr}, srcEnd, load<${resolvedType}>(${outPtr}, ${fieldOffset}));`);
879
1056
  }
@@ -893,6 +1070,7 @@ export class JSONTransform extends Visitor {
893
1070
  };
894
1071
  indent = " ";
895
1072
  DESERIALIZE_FAST += indent + "const start = srcStart;\n";
1073
+ DESERIALIZE_FAST += indent + "const dst = changetype<usize>(out);\n";
896
1074
  DESERIALIZE_FAST += indent + "do {\n";
897
1075
  indent += " ";
898
1076
  if (supportsFastOptionalPath) {
@@ -910,8 +1088,8 @@ export class JSONTransform extends Visitor {
910
1088
  const nextKeyOffset = nextKeySection.length << 1;
911
1089
  const resolvedType = stripNull(member.type);
912
1090
  const inlineStringValue = ["string", "String"].includes(resolvedType);
913
- const deserializerFirst = getDeserializer(member.type, "srcStart", "changetype<usize>(out)", member, inlineStringValue ? firstKeyOffset : 0, true);
914
- const deserializerNext = getDeserializer(member.type, "srcStart", "changetype<usize>(out)", member, inlineStringValue ? nextKeyOffset : 0, true);
1091
+ const deserializerFirst = getDeserializer(member.type, "srcStart", "dst", member, inlineStringValue ? firstKeyOffset : 0, true);
1092
+ const deserializerNext = getDeserializer(member.type, "srcStart", "dst", member, inlineStringValue ? nextKeyOffset : 0, true);
915
1093
  const isOptional = member.flags.has(PropertyFlags.OmitNull) || member.flags.has(PropertyFlags.OmitIf);
916
1094
  if (!deserializerFirst.length || !deserializerNext.length) {
917
1095
  DESERIALIZE_FAST += indent + "break;\n\n";
@@ -973,7 +1151,7 @@ export class JSONTransform extends Visitor {
973
1151
  if (!inlineStringValue) {
974
1152
  DESERIALIZE_FAST += indent + `srcStart += ${keyOffset};\n\n`;
975
1153
  }
976
- const deserializer = getDeserializer(member.type, "srcStart", "changetype<usize>(out)", member, inlineStringValue ? keyOffset : 0, true);
1154
+ const deserializer = getDeserializer(member.type, "srcStart", "dst", member, inlineStringValue ? keyOffset : 0, true);
977
1155
  if (!deserializer.length) {
978
1156
  DESERIALIZE_FAST += indent + "break;\n\n";
979
1157
  continue;
@@ -986,9 +1164,14 @@ export class JSONTransform extends Visitor {
986
1164
  DESERIALIZE_FAST += indent + "return srcStart;\n";
987
1165
  indent = indent.slice(0, -2);
988
1166
  DESERIALIZE_FAST += indent + "} while (false);\n\n";
989
- DESERIALIZE_FAST += indent + "if (isDefined(out.__INITIALIZE)) out.__INITIALIZE();\n";
990
- DESERIALIZE_FAST += indent + "const end = JSON.Util.scanValueEnd(start, srcEnd);\n";
991
- DESERIALIZE_FAST += indent + "return out.__DESERIALIZE_SLOW(start, end ? end : srcEnd, out);";
1167
+ if (THROW_FAST_PATH) {
1168
+ DESERIALIZE_FAST += indent + "const failAt = srcStart ? srcStart : start;\n";
1169
+ DESERIALIZE_FAST += indent + "const failEnd = failAt + 160 < srcEnd ? failAt + 160 : srcEnd;\n";
1170
+ DESERIALIZE_FAST += indent + `throw new Error("Fast path failed for ${this.schema.name} at char offset " + ((failAt - start) >> 1).toString() + " near: " + JSON.Util.ptrToStr(failAt, failEnd));`;
1171
+ }
1172
+ else {
1173
+ DESERIALIZE_FAST += indent + "return 0;";
1174
+ }
992
1175
  indent = indent.slice(0, -2);
993
1176
  DESERIALIZE_FAST += indent + "}";
994
1177
  DESERIALIZE += indent + " let keyStart: usize = 0;\n";
@@ -1007,7 +1190,7 @@ export class JSONTransform extends Visitor {
1007
1190
  DESERIALIZE += indent + " let code = load<u16>(srcStart);\n";
1008
1191
  DESERIALIZE += indent + " while (JSON.Util.isSpace(code)) code = load<u16>(srcStart += 2);\n";
1009
1192
  DESERIALIZE += indent + " if (keyStart == 0) {\n";
1010
- DESERIALIZE += indent + " if (code == 34 && load<u16>(srcStart - 2) !== 92) {\n";
1193
+ DESERIALIZE += indent + " if (code == 34 && JSON.Util.isUnescapedQuote(srcStart)) {\n";
1011
1194
  DESERIALIZE += indent + " if (isKey) {\n";
1012
1195
  DESERIALIZE += indent + " keyStart = lastIndex;\n";
1013
1196
  DESERIALIZE += indent + " keyEnd = srcStart;\n";
@@ -1112,7 +1295,7 @@ export class JSONTransform extends Visitor {
1112
1295
  DESERIALIZE += " srcStart += 2;\n";
1113
1296
  DESERIALIZE += " while (srcStart < srcEnd) {\n";
1114
1297
  DESERIALIZE += " const code = load<u16>(srcStart);\n";
1115
- DESERIALIZE += " if (code == 34 && load<u16>(srcStart - 2) !== 92) {\n";
1298
+ DESERIALIZE += " if (code == 34 && JSON.Util.isUnescapedQuote(srcStart)) {\n";
1116
1299
  if (DEBUG > 1)
1117
1300
  DESERIALIZE += ' console.log("Value (string, ' + ++id + '): " + JSON.Util.ptrToStr(lastIndex, srcStart + 2));';
1118
1301
  generateGroups(sortedMembers.string, (group) => {
@@ -1211,7 +1394,7 @@ export class JSONTransform extends Visitor {
1211
1394
  DESERIALIZE += " const code = load<u16>(srcStart);\n";
1212
1395
  DESERIALIZE += " if (code == 34) {\n";
1213
1396
  DESERIALIZE += " srcStart += 2;\n";
1214
- DESERIALIZE += " while (!(load<u16>(srcStart) == 34 && load<u16>(srcStart - 2) != 92)) srcStart += 2;\n";
1397
+ DESERIALIZE += " while (!(load<u16>(srcStart) == 34 && JSON.Util.isUnescapedQuote(srcStart))) srcStart += 2;\n";
1215
1398
  DESERIALIZE += " } else if (code == 125) {\n";
1216
1399
  DESERIALIZE += " if (--depth == 0) {\n";
1217
1400
  DESERIALIZE += " srcStart += 2;\n";
@@ -1265,7 +1448,7 @@ export class JSONTransform extends Visitor {
1265
1448
  DESERIALIZE += " const code = load<u16>(srcStart);\n";
1266
1449
  DESERIALIZE += " if (code == 34) {\n";
1267
1450
  DESERIALIZE += " srcStart += 2;\n";
1268
- DESERIALIZE += " while (!(load<u16>(srcStart) == 34 && load<u16>(srcStart - 2) != 92)) srcStart += 2;\n";
1451
+ DESERIALIZE += " while (!(load<u16>(srcStart) == 34 && JSON.Util.isUnescapedQuote(srcStart))) srcStart += 2;\n";
1269
1452
  DESERIALIZE += " } else if (code == 93) {\n";
1270
1453
  DESERIALIZE += " if (--depth == 0) {\n";
1271
1454
  DESERIALIZE += " srcStart += 2;\n";
@@ -1321,16 +1504,11 @@ export class JSONTransform extends Visitor {
1321
1504
  const first = group[0];
1322
1505
  const fName = first.alias || first.name;
1323
1506
  DESERIALIZE += indent + " if (" + (first.generic ? "isBoolean<" + first.type + ">() && " : "") + getComparison(fName) + ") { // " + fName + "\n";
1324
- if (first.type.startsWith("JSON.Box<bool") || first.type.startsWith("JSON.Box<boolean") || first.type.startsWith("Box<bool") || first.type.startsWith("Box<boolean")) {
1325
- DESERIALIZE +=
1326
- indent +
1327
- " store<" +
1328
- first.type +
1329
- ">(changetype<usize>(out), changetype<" +
1330
- first.type +
1331
- ">(JSON.Box.from<bool>(true)), offsetof<this>(" +
1332
- JSON.stringify(first.name) +
1333
- "));\n";
1507
+ if (isRawType(first.type)) {
1508
+ DESERIALIZE += indent + " store<" + first.type + ">(changetype<usize>(out), JSON.__deserialize<" + first.type + ">(srcStart - 8, srcStart), offsetof<this>(" + JSON.stringify(first.name) + "));\n";
1509
+ }
1510
+ else if (first.type.startsWith("JSON.Box<bool") || first.type.startsWith("JSON.Box<boolean") || first.type.startsWith("Box<bool") || first.type.startsWith("Box<boolean")) {
1511
+ DESERIALIZE += indent + " store<" + first.type + ">(changetype<usize>(out), changetype<" + first.type + ">(JSON.Box.from<bool>(true)), offsetof<this>(" + JSON.stringify(first.name) + "));\n";
1334
1512
  }
1335
1513
  else {
1336
1514
  DESERIALIZE += indent + " store<boolean>(changetype<usize>(out), true, offsetof<this>(" + JSON.stringify(first.name) + "));\n";
@@ -1343,16 +1521,11 @@ export class JSONTransform extends Visitor {
1343
1521
  const mem = group[i];
1344
1522
  const memName = mem.alias || mem.name;
1345
1523
  DESERIALIZE += indent + " else if (" + (mem.generic ? "isBoolean<" + mem.type + ">() && " : "") + getComparison(memName) + ") { // " + memName + "\n";
1346
- if (mem.type.startsWith("JSON.Box<bool") || mem.type.startsWith("JSON.Box<boolean") || mem.type.startsWith("Box<bool") || mem.type.startsWith("Box<boolean")) {
1347
- DESERIALIZE +=
1348
- indent +
1349
- " store<" +
1350
- mem.type +
1351
- ">(changetype<usize>(out), changetype<" +
1352
- mem.type +
1353
- ">(JSON.Box.from<bool>(true)), offsetof<this>(" +
1354
- JSON.stringify(mem.name) +
1355
- "));\n";
1524
+ if (isRawType(mem.type)) {
1525
+ DESERIALIZE += indent + " store<" + mem.type + ">(changetype<usize>(out), JSON.__deserialize<" + mem.type + ">(srcStart - 8, srcStart), offsetof<this>(" + JSON.stringify(mem.name) + "));\n";
1526
+ }
1527
+ else if (mem.type.startsWith("JSON.Box<bool") || mem.type.startsWith("JSON.Box<boolean") || mem.type.startsWith("Box<bool") || mem.type.startsWith("Box<boolean")) {
1528
+ DESERIALIZE += indent + " store<" + mem.type + ">(changetype<usize>(out), changetype<" + mem.type + ">(JSON.Box.from<bool>(true)), offsetof<this>(" + JSON.stringify(mem.name) + "));\n";
1356
1529
  }
1357
1530
  else {
1358
1531
  DESERIALIZE += indent + " store<boolean>(changetype<usize>(out), true, offsetof<this>(" + JSON.stringify(mem.name) + "));\n";
@@ -1391,16 +1564,11 @@ export class JSONTransform extends Visitor {
1391
1564
  const first = group[0];
1392
1565
  const fName = first.alias || first.name;
1393
1566
  DESERIALIZE += indent + " if (" + (first.generic ? "isBoolean<" + first.type + ">() && " : "") + getComparison(fName) + ") { // " + fName + "\n";
1394
- if (first.type.startsWith("JSON.Box<bool") || first.type.startsWith("JSON.Box<boolean") || first.type.startsWith("Box<bool") || first.type.startsWith("Box<boolean")) {
1395
- DESERIALIZE +=
1396
- indent +
1397
- " store<" +
1398
- first.type +
1399
- ">(changetype<usize>(out), changetype<" +
1400
- first.type +
1401
- ">(JSON.Box.from<bool>(false)), offsetof<this>(" +
1402
- JSON.stringify(first.name) +
1403
- "));\n";
1567
+ if (isRawType(first.type)) {
1568
+ DESERIALIZE += indent + " store<" + first.type + ">(changetype<usize>(out), JSON.__deserialize<" + first.type + ">(srcStart - 10, srcStart), offsetof<this>(" + JSON.stringify(first.name) + "));\n";
1569
+ }
1570
+ else if (first.type.startsWith("JSON.Box<bool") || first.type.startsWith("JSON.Box<boolean") || first.type.startsWith("Box<bool") || first.type.startsWith("Box<boolean")) {
1571
+ DESERIALIZE += indent + " store<" + first.type + ">(changetype<usize>(out), changetype<" + first.type + ">(JSON.Box.from<bool>(false)), offsetof<this>(" + JSON.stringify(first.name) + "));\n";
1404
1572
  }
1405
1573
  else {
1406
1574
  DESERIALIZE += indent + " store<boolean>(changetype<usize>(out), false, offsetof<this>(" + JSON.stringify(first.name) + "));\n";
@@ -1413,16 +1581,11 @@ export class JSONTransform extends Visitor {
1413
1581
  const mem = group[i];
1414
1582
  const memName = mem.alias || mem.name;
1415
1583
  DESERIALIZE += indent + " else if (" + (mem.generic ? "isBoolean<" + mem.type + ">() && " : "") + getComparison(memName) + ") { // " + memName + "\n";
1416
- if (mem.type.startsWith("JSON.Box<bool") || mem.type.startsWith("JSON.Box<boolean") || mem.type.startsWith("Box<bool") || mem.type.startsWith("Box<boolean")) {
1417
- DESERIALIZE +=
1418
- indent +
1419
- " store<" +
1420
- mem.type +
1421
- ">(changetype<usize>(out), changetype<" +
1422
- mem.type +
1423
- ">(JSON.Box.from<bool>(false)), offsetof<this>(" +
1424
- JSON.stringify(mem.name) +
1425
- "));\n";
1584
+ if (isRawType(mem.type)) {
1585
+ DESERIALIZE += indent + " store<" + mem.type + ">(changetype<usize>(out), JSON.__deserialize<" + mem.type + ">(srcStart - 10, srcStart), offsetof<this>(" + JSON.stringify(mem.name) + "));\n";
1586
+ }
1587
+ else if (mem.type.startsWith("JSON.Box<bool") || mem.type.startsWith("JSON.Box<boolean") || mem.type.startsWith("Box<bool") || mem.type.startsWith("Box<boolean")) {
1588
+ DESERIALIZE += indent + " store<" + mem.type + ">(changetype<usize>(out), changetype<" + mem.type + ">(JSON.Box.from<bool>(false)), offsetof<this>(" + JSON.stringify(mem.name) + "));\n";
1426
1589
  }
1427
1590
  else {
1428
1591
  DESERIALIZE += indent + " store<boolean>(changetype<usize>(out), false, offsetof<this>(" + JSON.stringify(mem.name) + "));\n";
@@ -1460,7 +1623,12 @@ export class JSONTransform extends Visitor {
1460
1623
  const first = group[0];
1461
1624
  const fName = first.alias || first.name;
1462
1625
  DESERIALIZE += indent + " if (" + (first.generic ? "isNullable<" + first.type + ">() && " : "") + getComparison(fName) + ") { // " + fName + "\n";
1463
- DESERIALIZE += indent + " store<usize>(changetype<usize>(out), 0, offsetof<this>(" + JSON.stringify(first.name) + "));\n";
1626
+ if (isRawType(first.type) && !first.node.type.isNullable) {
1627
+ DESERIALIZE += indent + " store<" + first.type + ">(changetype<usize>(out), JSON.__deserialize<" + first.type + ">(srcStart - 8, srcStart), offsetof<this>(" + JSON.stringify(first.name) + "));\n";
1628
+ }
1629
+ else {
1630
+ DESERIALIZE += indent + " store<usize>(changetype<usize>(out), 0, offsetof<this>(" + JSON.stringify(first.name) + "));\n";
1631
+ }
1464
1632
  DESERIALIZE += indent + " srcStart += 2;\n";
1465
1633
  DESERIALIZE += indent + " keyStart = 0;\n";
1466
1634
  DESERIALIZE += indent + " break;\n";
@@ -1469,7 +1637,12 @@ export class JSONTransform extends Visitor {
1469
1637
  const mem = group[i];
1470
1638
  const memName = mem.alias || mem.name;
1471
1639
  DESERIALIZE += indent + " else if (" + (mem.generic ? "isNullable<" + mem.type + ">() && " : "") + getComparison(memName) + ") { // " + memName + "\n";
1472
- DESERIALIZE += indent + " store<usize>(changetype<usize>(out), 0, offsetof<this>(" + JSON.stringify(mem.name) + "));\n";
1640
+ if (isRawType(mem.type) && !mem.node.type.isNullable) {
1641
+ DESERIALIZE += indent + " store<" + mem.type + ">(changetype<usize>(out), JSON.__deserialize<" + mem.type + ">(srcStart - 8, srcStart), offsetof<this>(" + JSON.stringify(mem.name) + "));\n";
1642
+ }
1643
+ else {
1644
+ DESERIALIZE += indent + " store<usize>(changetype<usize>(out), 0, offsetof<this>(" + JSON.stringify(mem.name) + "));\n";
1645
+ }
1473
1646
  DESERIALIZE += indent + " srcStart += 2;\n";
1474
1647
  DESERIALIZE += indent + " keyStart = 0;\n";
1475
1648
  DESERIALIZE += indent + " break;\n";
@@ -1793,8 +1966,9 @@ export default class Transformer extends Transform {
1793
1966
  }
1794
1967
  }
1795
1968
  program.registerConstantInteger("JSON_MODE", Type.i32, i64_new(MODE));
1796
- if (process.env["JSON_CACHE"]?.trim().toLowerCase() === "true" || process.env["JSON_CACHE"]?.trim().toLowerCase() === "1") {
1969
+ if (JSON_CACHE_CONFIG.enabled) {
1797
1970
  program.registerConstantInteger("JSON_CACHE", Type.bool, i64_one);
1971
+ program.registerConstantInteger("JSON_CACHE_SIZE", Type.u32, i64_new(JSON_CACHE_CONFIG.bytes));
1798
1972
  }
1799
1973
  }
1800
1974
  afterParse(parser) {
@@ -1961,15 +2135,56 @@ function sizeof(type) {
1961
2135
  return 20;
1962
2136
  else if (type == "i32")
1963
2137
  return 22;
2138
+ else if (type == "usize")
2139
+ return 40;
2140
+ else if (type == "isize")
2141
+ return 42;
1964
2142
  else if (type == "u64")
1965
2143
  return 40;
1966
2144
  else if (type == "i64")
1967
- return 40;
2145
+ return 42;
2146
+ else if (type == "f32")
2147
+ return 34;
2148
+ else if (type == "f64")
2149
+ return 66;
1968
2150
  else if (type == "bool" || type == "boolean")
1969
2151
  return 10;
1970
2152
  else
1971
2153
  return 0;
1972
2154
  }
2155
+ function estimatedSerializedByteSize(type, source, parser) {
2156
+ const trimmed = type.trim();
2157
+ const baseType = stripNull(trimmed);
2158
+ const nullable = trimmed != baseType;
2159
+ let estimated = sizeof(baseType);
2160
+ if (estimated == 0) {
2161
+ if (isEnum(baseType, source, parser)) {
2162
+ estimated = 22;
2163
+ }
2164
+ else if (baseType == "Date") {
2165
+ estimated = 52;
2166
+ }
2167
+ else if (isString(baseType)) {
2168
+ estimated = 4;
2169
+ }
2170
+ else if (isArray(baseType) || baseType.startsWith("Map<")) {
2171
+ estimated = 4;
2172
+ }
2173
+ else if (baseType == "JSON.Obj" || baseType == "Obj" || baseType == "JSON.Raw" || baseType == "Raw" || baseType == "JSON.Value" || baseType == "Value") {
2174
+ estimated = 4;
2175
+ }
2176
+ else if (baseType == "ArrayBuffer" || needsReferenceLoad(baseType)) {
2177
+ estimated = 4;
2178
+ }
2179
+ else {
2180
+ estimated = 4;
2181
+ }
2182
+ }
2183
+ if (nullable) {
2184
+ estimated = Math.max(estimated, 8);
2185
+ }
2186
+ return estimated;
2187
+ }
1973
2188
  function isPrimitive(type) {
1974
2189
  const primitiveTypes = ["u8", "u16", "u32", "u64", "i8", "i16", "i32", "i64", "f32", "f64", "bool", "boolean"];
1975
2190
  return primitiveTypes.some((v) => type.startsWith(v));