gofront 1.6.1 → 1.7.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.
@@ -0,0 +1,972 @@
1
+ // src/backend/js/stdlib/json.js
2
+ // CodeGen for Go encoding/json package with compile-time static encoders and decoders.
3
+
4
+ import { typedArrayConstructorForElem } from "../../../typechecker/types.js";
5
+
6
+ function safeIdent(name) {
7
+ return String(name || "").replace(/[^a-zA-Z0-9_$]/g, "_");
8
+ }
9
+
10
+ function declareCodec(fnName, params, body) {
11
+ return `var ${fnName} = typeof ${fnName} !== "undefined" ? ${fnName} : function ${fnName}(${params}) {\n${body}\n};`;
12
+ }
13
+
14
+ function typeCodecName(t, fallback = "val") {
15
+ if (!t) return fallback;
16
+ if (t.kind === "pointer" && t.base) {
17
+ return `${typeCodecName(t.base, fallback)}_ptr`;
18
+ }
19
+ if (t.name) return t.name;
20
+ if (t.kind === "slice") return `slice_${typeCodecName(t.elem, "elem")}`;
21
+ if (t.kind === "map")
22
+ return `map_${typeCodecName(t.value ?? t.elem, "elem")}`;
23
+ return fallback;
24
+ }
25
+
26
+ function unwrapNamed(t) {
27
+ return t?.kind === "named" ? t.underlying : t;
28
+ }
29
+
30
+ function isRawMessageType(t) {
31
+ return (
32
+ t?.name === "json.RawMessage" || t?.name === "encoding/json.RawMessage"
33
+ );
34
+ }
35
+
36
+ function isByteSliceType(t) {
37
+ const base = unwrapNamed(t);
38
+ return (
39
+ base?.kind === "slice" &&
40
+ (base.elem?.name === "byte" || base.elem?.name === "uint8") &&
41
+ !isRawMessageType(t)
42
+ );
43
+ }
44
+
45
+ function isJsonNullAssignable(type) {
46
+ const base = unwrapNamed(type);
47
+ return (
48
+ base?.kind === "pointer" ||
49
+ base?.kind === "slice" ||
50
+ base?.kind === "map" ||
51
+ base?.kind === "interface" ||
52
+ (base?.kind === "basic" && base.name === "any")
53
+ );
54
+ }
55
+
56
+ // Field kinds the `,string` tag option applies to (Go: bool, ints, uints, floats, string).
57
+ const QUOTABLE_KINDS = new Set([
58
+ "string",
59
+ "bool",
60
+ "int",
61
+ "int8",
62
+ "int16",
63
+ "int32",
64
+ "int64",
65
+ "uint",
66
+ "uint8",
67
+ "uint16",
68
+ "uint32",
69
+ "uint64",
70
+ "uintptr",
71
+ "byte",
72
+ "rune",
73
+ "float32",
74
+ "float64",
75
+ ]);
76
+
77
+ // reflect's names for GoFront's basic aliases, used in decode error messages.
78
+ const BASIC_DISPLAY = { any: "interface {}", byte: "uint8", rune: "int32" };
79
+
80
+ // Basic kinds decoded by the typed scalar decoders.
81
+ const SCALAR_KINDS = QUOTABLE_KINDS;
82
+
83
+ // Methods live on the named type, or on its underlying struct for struct types.
84
+ function lookupMethod(type, name) {
85
+ if (type?.kind !== "named") return null;
86
+ return type.methods?.get(name) ?? type.underlying?.methods?.get(name) ?? null;
87
+ }
88
+
89
+ export const jsonMethods = {
90
+ _genJson(fn, a, expr) {
91
+ this.useHelper("json");
92
+ this.useHelper("base64");
93
+ this.useHelper("error");
94
+ this._markJsonPtrRecvMarshalers();
95
+ const args = a();
96
+ if (fn === "Marshal") {
97
+ const argType = expr.args[0]?._type;
98
+ const enc = this._getJsonEncoder(argType);
99
+ return `__json_marshal(${enc}, ${args[0]})`;
100
+ }
101
+ if (fn === "MarshalIndent") {
102
+ const argType = expr.args[0]?._type;
103
+ const enc = this._getJsonEncoder(argType);
104
+ return `__json_marshal_indent(${enc}, ${args[0]}, ${args[1]}, ${args[2]})`;
105
+ }
106
+ if (fn === "Unmarshal") {
107
+ const targetType = expr.args[1]?._type;
108
+ const dec = this._getJsonDecoder(targetType);
109
+ return `__json_unmarshal(${dec}, ${args[0]}, ${args[1]})`;
110
+ }
111
+ if (fn === "Valid") {
112
+ return `__json_valid(${args[0]})`;
113
+ }
114
+ return undefined;
115
+ },
116
+
117
+ _getJsonEncoder(type, addr = false) {
118
+ if (!type) return "__json_enc_any";
119
+ if (isRawMessageType(type)) {
120
+ const fnName = "__json_enc_RawMessage";
121
+ if (!this._jsonCodecs.has(fnName)) {
122
+ this._jsonCodecs.set(
123
+ fnName,
124
+ declareCodec(
125
+ fnName,
126
+ "v",
127
+ ` if (v === null || v === undefined) return "null";\n var s = (typeof v === "string" ? v : new TextDecoder().decode(v)).trim();\n return s.length > 0 ? s : "null";`,
128
+ ),
129
+ );
130
+ }
131
+ return fnName;
132
+ }
133
+
134
+ // Go calls a pointer-receiver MarshalJSON only on addressable values.
135
+ const marshaler = this._jsonMarshalerInfo(type);
136
+ if (marshaler && (!marshaler.ptrRecv || addr)) {
137
+ return this._getMarshalerEncoder(type, marshaler);
138
+ }
139
+
140
+ const base = unwrapNamed(type);
141
+ if (isByteSliceType(type)) return "__json_b64";
142
+
143
+ if (base?.kind === "struct") {
144
+ const needsAddr = addr && this._jsonNeedsAddr(type);
145
+ const typeName = safeIdent((type.name ?? "AnonStruct").split(".").pop());
146
+ const fnName = `__json_enc_${typeName}${needsAddr ? "_addr" : ""}`;
147
+ if (!this._jsonCodecs.has(fnName)) {
148
+ this._jsonCodecs.set(fnName, "");
149
+ const code = this._buildStructEncoder(type, fnName, needsAddr);
150
+ this._jsonCodecs.set(fnName, code);
151
+ }
152
+ return fnName;
153
+ }
154
+
155
+ if (type.kind === "pointer" && type.base) {
156
+ const innerEnc = this._getJsonEncoder(type.base, true);
157
+ // Pointers to structs are the struct object; other pointers are { value } boxes.
158
+ const deref = unwrapNamed(type.base)?.kind === "struct" ? "v" : "v.value";
159
+ const fnName = `__json_enc_ptr_${safeIdent(typeCodecName(type.base, "val").split(".").pop())}`;
160
+ if (!this._jsonCodecs.has(fnName)) {
161
+ this._jsonCodecs.set(
162
+ fnName,
163
+ declareCodec(
164
+ fnName,
165
+ "v",
166
+ ` return (v === null || v === undefined) ? "null" : ${innerEnc}(${deref});`,
167
+ ),
168
+ );
169
+ }
170
+ return fnName;
171
+ }
172
+
173
+ if (base?.kind === "slice") {
174
+ const elemEnc = this._getJsonEncoder(base.elem, true);
175
+ const fnName = `__json_enc_slice_${safeIdent(typeCodecName(base.elem, "elem").split(".").pop())}`;
176
+ if (!this._jsonCodecs.has(fnName)) {
177
+ this._jsonCodecs.set(
178
+ fnName,
179
+ declareCodec(
180
+ fnName,
181
+ "v",
182
+ ` return __json_enc_slice(v, ${elemEnc});`,
183
+ ),
184
+ );
185
+ }
186
+ return fnName;
187
+ }
188
+
189
+ if (base?.kind === "map") {
190
+ const valType = base.value ?? base.elem;
191
+ const valEnc = this._getJsonEncoder(valType, false);
192
+ const fnName = `__json_enc_map_${safeIdent(typeCodecName(valType, "val").split(".").pop())}`;
193
+ if (!this._jsonCodecs.has(fnName)) {
194
+ this._jsonCodecs.set(
195
+ fnName,
196
+ declareCodec(fnName, "v", ` return __json_enc_map(v, ${valEnc});`),
197
+ );
198
+ }
199
+ return fnName;
200
+ }
201
+
202
+ if (base?.kind === "basic") {
203
+ return this._getBasicEncoder(base.name);
204
+ }
205
+
206
+ return "__json_enc_any";
207
+ },
208
+
209
+ // Tags pointer-receiver MarshalJSON methods so the dynamic `any` encoder can skip
210
+ // them for non-addressable values, as Go does.
211
+ _markJsonPtrRecvMarshalers() {
212
+ if (this._jsonPtrRecvMarked) return;
213
+ this._jsonPtrRecvMarked = true;
214
+ for (const [name, type] of this.checker?.types ?? []) {
215
+ if (name.includes(".") || type?.kind !== "named") continue;
216
+ if (!lookupMethod(type, "MarshalJSON")?._ptrRecv) continue;
217
+ const cls = safeIdent(name);
218
+ this._jsonCodecs.set(
219
+ `__json_ptrrecv_${cls}`,
220
+ `if (typeof ${cls} === "function" && ${cls}.prototype.MarshalJSON) ${cls}.prototype.MarshalJSON.__ptrRecv = true;`,
221
+ );
222
+ }
223
+ },
224
+
225
+ _jsonTypeName(type) {
226
+ const name = type?.name ?? "value";
227
+ if (name.includes(".")) return name;
228
+ const pkg = this.checker?.pkgName;
229
+ return pkg ? `${pkg}.${name}` : name;
230
+ },
231
+
232
+ _jsonClassName(type) {
233
+ return String(type?.name ?? "Object")
234
+ .split(".")
235
+ .pop();
236
+ },
237
+
238
+ // How to call `name` (MarshalJSON / UnmarshalJSON) on values of `type`, or null.
239
+ // Struct types dispatch on the instance; named non-struct types (raw values)
240
+ // call the static `T.$name(recv, ...)` with a { value } box for pointer receivers.
241
+ _jsonMethodInfo(type, name) {
242
+ const method = lookupMethod(type, name);
243
+ if (!method || isRawMessageType(type)) return null;
244
+ const isStruct = type.underlying?.kind === "struct";
245
+ if (!isStruct && !this._namedBoxInfo(type)) return null;
246
+ return {
247
+ ptrRecv: Boolean(method._ptrRecv),
248
+ isStruct,
249
+ cls: this._jsonClassName(type),
250
+ };
251
+ },
252
+
253
+ _jsonMarshalerInfo(type) {
254
+ return this._jsonMethodInfo(type, "MarshalJSON");
255
+ },
256
+
257
+ _jsonUnmarshalerInfo(type) {
258
+ return this._jsonMethodInfo(type, "UnmarshalJSON");
259
+ },
260
+
261
+ // UnmarshalJSON call for a target reachable through `addrExpr`: the struct
262
+ // instance for struct types, a { value } box otherwise.
263
+ _jsonUnmarshalCall(info, addrExpr, bytesExpr) {
264
+ if (info.isStruct) return `${addrExpr}.UnmarshalJSON(${bytesExpr})`;
265
+ const recv = info.ptrRecv ? addrExpr : `${addrExpr}.value`;
266
+ return `${info.cls}.$UnmarshalJSON(${recv}, ${bytesExpr})`;
267
+ },
268
+
269
+ // True when encoding an addressable `type` differs from a non-addressable one,
270
+ // i.e. it (or a value field inside it) has a pointer-receiver MarshalJSON.
271
+ _jsonNeedsAddr(type, seen = new Set()) {
272
+ if (!type || seen.has(type)) return false;
273
+ seen.add(type);
274
+ const marshaler = this._jsonMarshalerInfo(type);
275
+ if (marshaler) return marshaler.ptrRecv;
276
+ const base = unwrapNamed(type);
277
+ if (base?.kind !== "struct") return false;
278
+ const fields = this.checker?.resolveJsonFields(base) ?? [];
279
+ return fields.some((f) => this._jsonNeedsAddr(f.type, seen));
280
+ },
281
+
282
+ _getMarshalerEncoder(type, info) {
283
+ const fnName = `__json_enc_${safeIdent(info.cls)}_marshal`;
284
+ if (!this._jsonCodecs.has(fnName)) {
285
+ let call;
286
+ if (info.isStruct) {
287
+ call = ` if (v === null || v === undefined) return "null";\n var res = v.MarshalJSON();`;
288
+ } else {
289
+ const recv = info.ptrRecv ? "{ value: v }" : "v";
290
+ call = ` var res = ${info.cls}.$MarshalJSON(${recv});`;
291
+ }
292
+ this._jsonCodecs.set(
293
+ fnName,
294
+ declareCodec(
295
+ fnName,
296
+ "v",
297
+ `${call}\n return __json_marshaler_out(res, ${JSON.stringify(this._jsonTypeName(type))});`,
298
+ ),
299
+ );
300
+ }
301
+ return fnName;
302
+ },
303
+
304
+ _getUnmarshalerDecoder(type) {
305
+ const info = this._jsonUnmarshalerInfo(type);
306
+ const fnName = `__json_dec_${safeIdent(info.cls)}_unmarshal`;
307
+ if (!this._jsonCodecs.has(fnName)) {
308
+ const bytes =
309
+ "__json_raw_bytes(rawStr !== undefined ? rawStr : JSON.stringify(parsed))";
310
+ const body = info.isStruct
311
+ ? ` var inst = target && typeof target === "object" ? target : new ${info.cls}();\n var err = ${this._jsonUnmarshalCall(info, "inst", bytes)};\n if (err) return __json_user_err(err);\n if (box) box.value = inst;\n return null;`
312
+ : ` var addr = box || { value: target };\n var err = ${this._jsonUnmarshalCall(info, "addr", bytes)};\n if (err) return __json_user_err(err);\n return null;`;
313
+ this._jsonCodecs.set(
314
+ fnName,
315
+ declareCodec(fnName, "parsed, target, box, rawData, rawStr", body),
316
+ );
317
+ }
318
+ return fnName;
319
+ },
320
+
321
+ _unmarshalerElemDecoder(elemType) {
322
+ const isPtr = elemType?.kind === "pointer";
323
+ const valueType = isPtr ? elemType.base : elemType;
324
+ const info = this._jsonUnmarshalerInfo(valueType);
325
+ if (!info) return null;
326
+ let make;
327
+ if (info.isStruct) {
328
+ make = (rawExpr) =>
329
+ `(() => { var inst = new ${info.cls}(); var uerr = ${this._jsonUnmarshalCall(info, "inst", `__json_raw_bytes(${rawExpr})`)}; return uerr ? __json_user_err(uerr) : inst; })()`;
330
+ } else {
331
+ // Pointer elements are the { value } box itself; value elements its value.
332
+ const zero = this.zeroValueForType(valueType);
333
+ const result = isPtr ? "__b" : "__b.value";
334
+ make = (rawExpr) =>
335
+ `(() => { var __b = { value: ${zero} }; var uerr = ${this._jsonUnmarshalCall(info, "__b", `__json_raw_bytes(${rawExpr})`)}; return uerr ? __json_user_err(uerr) : ${result}; })()`;
336
+ }
337
+ if (isPtr)
338
+ return (v, rawExpr) => `(${v} === null ? null : ${make(rawExpr)})`;
339
+ return (_v, rawExpr) => make(rawExpr);
340
+ },
341
+
342
+ // Returns the basic kind a `,string` field is quoted as, or null if the option does not apply.
343
+ _jsonQuotedKind(f) {
344
+ if (!f.quoted) return null;
345
+ const t = f.type?.kind === "pointer" ? f.type.base : f.type;
346
+ if (this._jsonMarshalerInfo(t)) return null;
347
+ const base = unwrapNamed(t);
348
+ return base?.kind === "basic" && QUOTABLE_KINDS.has(base.name)
349
+ ? base.name
350
+ : null;
351
+ },
352
+
353
+ _genQuotedFieldEncoderExpr(f, kind) {
354
+ const isPtr = f.type?.kind === "pointer";
355
+ const basic = this._genBasicFieldExpr(kind);
356
+ const quote = (x) => {
357
+ const inner = basic(x);
358
+ return kind === "string"
359
+ ? `__json_esc_noh(${inner})`
360
+ : `('"' + ${inner} + '"')`;
361
+ };
362
+ if (!isPtr) return quote;
363
+ return (v) =>
364
+ `(${v} === null || ${v} === undefined ? "null" : ${quote(`${v}.value`)})`;
365
+ },
366
+
367
+ _getBasicEncoder(basicName) {
368
+ switch (basicName) {
369
+ case "string":
370
+ return "__json_esc";
371
+ case "bool":
372
+ return "(v => v ? 'true' : 'false')";
373
+ case "int":
374
+ case "int8":
375
+ case "int16":
376
+ case "int32":
377
+ case "uint":
378
+ case "uint8":
379
+ case "uint16":
380
+ case "uint32":
381
+ case "byte":
382
+ case "rune":
383
+ return "(v => String(Math.trunc(v)))";
384
+ case "int64":
385
+ case "uint64":
386
+ return "(v => typeof v === 'bigint' ? v.toString() : String(v))";
387
+ case "float32":
388
+ return "__json_float32";
389
+ case "float64":
390
+ return "(v => { if (!isFinite(v)) throw new Error('json: unsupported value: ' + v); return String(v); })";
391
+ default:
392
+ return "__json_enc_any";
393
+ }
394
+ },
395
+
396
+ _buildStructEncoder(structType, fnName, addr = false) {
397
+ const underlying = unwrapNamed(structType);
398
+ const fields = this.checker?.resolveJsonFields(underlying) ?? [];
399
+ const lines = [];
400
+ lines.push(
401
+ `var ${fnName} = typeof ${fnName} !== "undefined" ? ${fnName} : function ${fnName}(v) {`,
402
+ );
403
+ lines.push(` if (v === null || v === undefined) return "null";`);
404
+ lines.push(` var parts = [];`);
405
+
406
+ for (const f of fields) {
407
+ const keyLiteral = JSON.stringify(`${JSON.stringify(f.jsonKey)}:`);
408
+ const encodeVal = this._genFieldEncoderExpr(f, addr);
409
+ const isStruct = unwrapNamed(f.type)?.kind === "struct";
410
+
411
+ this._emitFieldEncoderAccess(f, lines, (valName) => {
412
+ const encoded = encodeVal(valName);
413
+ if (f.omitempty) {
414
+ return `if (!__json_is_zero(${valName}, ${isStruct})) parts.push(${keyLiteral} + ${encoded});`;
415
+ }
416
+ return `parts.push(${keyLiteral} + ${encoded});`;
417
+ });
418
+ }
419
+
420
+ lines.push(` return "{" + parts.join(",") + "}";`);
421
+ lines.push(`};`);
422
+ return lines.join("\n");
423
+ },
424
+
425
+ _emitFieldEncoderAccess(f, lines, onVal) {
426
+ const path = f.path ?? [{ name: f.goName, isPtr: false }];
427
+ if (f.isEmbedded && f.tagged) {
428
+ lines.push(
429
+ ` { var val = (v.${f.goName} !== undefined ? v.${f.goName} : v); ${onVal("val")} }`,
430
+ );
431
+ return;
432
+ }
433
+ if (path.length === 1) {
434
+ lines.push(` { var val = v.${path[0].name}; ${onVal("val")} }`);
435
+ return;
436
+ }
437
+
438
+ // Promoted fields from embedded struct
439
+ const ptrSegs = path.slice(0, -1).filter((s) => s.isPtr);
440
+ const nilChecks = ptrSegs.map((s) => `v.${s.name} === null`);
441
+
442
+ let cur = "v";
443
+ const conds = [];
444
+ for (let i = 0; i < path.length - 1; i++) {
445
+ const seg = path[i];
446
+ conds.push(`${cur}.${seg.name} != null`);
447
+ cur = `${cur}.${seg.name}`;
448
+ }
449
+ const leaf = `${cur}.${path[path.length - 1].name}`;
450
+
451
+ if (nilChecks.length > 0) {
452
+ lines.push(` if (!(${nilChecks.join(" || ")})) {`);
453
+ lines.push(
454
+ ` var val = (v.${f.goName} !== undefined ? v.${f.goName} : (${conds.join(" && ")} ? ${leaf} : undefined));`,
455
+ );
456
+ lines.push(` if (val !== undefined) { ${onVal("val")} }`);
457
+ lines.push(` }`);
458
+ } else {
459
+ lines.push(
460
+ ` { var val = (v.${f.goName} !== undefined ? v.${f.goName} : (${conds.join(" && ")} ? ${leaf} : undefined)); if (val !== undefined) { ${onVal("val")} } }`,
461
+ );
462
+ }
463
+ },
464
+
465
+ _genFieldEncoderExpr(f, addr = false) {
466
+ const quotedKind = this._jsonQuotedKind(f);
467
+ if (quotedKind) return this._genQuotedFieldEncoderExpr(f, quotedKind);
468
+ if (isRawMessageType(f.type)) {
469
+ return (v) =>
470
+ `((typeof ${v} === "string" ? ${v} : new TextDecoder().decode(${v})).trim() || "null")`;
471
+ }
472
+ // Marshalers and pointers go through the shared encoders.
473
+ const special =
474
+ f.type?.kind === "pointer" ||
475
+ (f.type?.kind === "named" && this._jsonMarshalerInfo(f.type));
476
+ if (special) {
477
+ const enc = this._getJsonEncoder(f.type, addr);
478
+ return (v) => `${enc}(${v})`;
479
+ }
480
+ if (isByteSliceType(f.type)) {
481
+ return (v) => `__json_b64(${v})`;
482
+ }
483
+ const base = unwrapNamed(f.type);
484
+ if (base?.kind === "basic") {
485
+ return this._genBasicFieldExpr(base.name);
486
+ }
487
+ if (base?.kind === "struct") {
488
+ const enc = this._getJsonEncoder(f.type, addr);
489
+ return (v) => `${enc}(${v})`;
490
+ }
491
+ if (base?.kind === "slice") {
492
+ const enc = this._getJsonEncoder(base.elem, true);
493
+ return (v) => `__json_enc_slice(${v}, ${enc})`;
494
+ }
495
+ if (base?.kind === "map") {
496
+ const enc = this._getJsonEncoder(base.value ?? base.elem, false);
497
+ return (v) => `__json_enc_map(${v}, ${enc})`;
498
+ }
499
+ return (v) => `__json_enc_any(${v})`;
500
+ },
501
+
502
+ _genBasicFieldExpr(name) {
503
+ switch (name) {
504
+ case "string":
505
+ return (v) => `__json_esc(${v})`;
506
+ case "bool":
507
+ return (v) => `(${v} ? "true" : "false")`;
508
+ case "int":
509
+ case "int8":
510
+ case "int16":
511
+ case "int32":
512
+ case "uint":
513
+ case "uint8":
514
+ case "uint16":
515
+ case "uint32":
516
+ case "byte":
517
+ case "rune":
518
+ return (v) => `String(Math.trunc(${v}))`;
519
+ case "int64":
520
+ case "uint64":
521
+ return (v) =>
522
+ `(typeof ${v} === "bigint" ? ${v}.toString() : String(${v}))`;
523
+ case "float32":
524
+ return (v) => `__json_float32(${v})`;
525
+ case "float64":
526
+ return (v) =>
527
+ `(!isFinite(${v}) ? (() => { throw new Error("json: unsupported value: " + ${v}); })() : String(${v}))`;
528
+ default:
529
+ return (v) => `__json_enc_any(${v})`;
530
+ }
531
+ },
532
+
533
+ _getJsonDecoder(targetType) {
534
+ if (!targetType) return "__json_dec_any_target";
535
+ const baseType =
536
+ targetType.kind === "pointer" ? targetType.base : targetType;
537
+ if (!baseType) return "__json_dec_any_target";
538
+
539
+ if (this._jsonUnmarshalerInfo(baseType)) {
540
+ return this._getUnmarshalerDecoder(baseType);
541
+ }
542
+
543
+ if (isRawMessageType(baseType)) {
544
+ const fnName = "__json_dec_RawMessage";
545
+ if (!this._jsonCodecs.has(fnName)) {
546
+ this._jsonCodecs.set(
547
+ fnName,
548
+ declareCodec(
549
+ fnName,
550
+ "parsed, target, box, rawData",
551
+ ` var u8 = rawData instanceof Uint8Array ? rawData : new Uint8Array(rawData);\n u8.__raw = true;\n if (box) box.value = u8;\n return null;`,
552
+ ),
553
+ );
554
+ }
555
+ return fnName;
556
+ }
557
+
558
+ if (isByteSliceType(baseType)) {
559
+ const fnName = "__json_dec_byte_slice";
560
+ if (!this._jsonCodecs.has(fnName)) {
561
+ this._jsonCodecs.set(
562
+ fnName,
563
+ declareCodec(
564
+ fnName,
565
+ "parsed, target, box",
566
+ ` if (parsed === null) { if (box) box.value = null; return null; }\n var u8 = __json_bytes_decode(parsed);\n if (u8 && u8._msg) return u8;\n if (box) box.value = u8;\n return null;`,
567
+ ),
568
+ );
569
+ }
570
+ return fnName;
571
+ }
572
+
573
+ const base = unwrapNamed(baseType);
574
+ if (base?.kind === "struct") {
575
+ const typeName = safeIdent(
576
+ (baseType.name ?? "AnonStruct").split(".").pop(),
577
+ );
578
+ const fnName = `__json_dec_${typeName}`;
579
+ if (!this._jsonCodecs.has(fnName)) {
580
+ this._jsonCodecs.set(fnName, "");
581
+ const code = this._buildStructDecoder(baseType, fnName);
582
+ this._jsonCodecs.set(fnName, code);
583
+ }
584
+ return fnName;
585
+ }
586
+
587
+ if (base?.kind === "slice") return this._getSliceDecoder(base);
588
+ if (base?.kind === "map") return this._getMapDecoder(base);
589
+ // Top-level scalar targets (`json.Unmarshal(b, &n)`) are type-checked too.
590
+ if (base?.kind === "basic" && SCALAR_KINDS.has(base.name))
591
+ return this._getScalarDecoder(baseType, base);
592
+
593
+ return "__json_dec_any_target";
594
+ },
595
+
596
+ _getSliceDecoder(base) {
597
+ const elemTypeName = typeCodecName(base.elem, "elem");
598
+ const fnName = `__json_dec_slice_${safeIdent(elemTypeName.split(".").pop())}`;
599
+ if (!this._jsonCodecs.has(fnName)) {
600
+ const elemDec = this._genElemDecoder(base.elem);
601
+ const typedCtor = typedArrayConstructorForElem(base.elem);
602
+ const initOut = typedCtor ? `new ${typedCtor}(parsed.length)` : `[]`;
603
+ const setElem = typedCtor ? `out[i] = item;` : `out.push(item);`;
604
+ const display = JSON.stringify(`[]${this._jsonDisplayType(base.elem)}`);
605
+ this._jsonCodecs.set(
606
+ fnName,
607
+ declareCodec(
608
+ fnName,
609
+ "parsed, target, box",
610
+ ` if (parsed === null) { if (box) box.value = null; return null; }\n if (!Array.isArray(parsed)) return __json_err("cannot unmarshal " + __json_type(parsed) + " into " + __json_ctx(${display}));\n var out = ${initOut};\n for (var i = 0; i < parsed.length; i++) {\n var item = ${elemDec("parsed[i]", "__json_raw_child(parsed, i)")};\n if (item && item._msg) return item;\n ${setElem}\n }\n if (box) box.value = out;\n return null;`,
611
+ ),
612
+ );
613
+ }
614
+ return fnName;
615
+ },
616
+
617
+ _getMapDecoder(base) {
618
+ const valType = base.value ?? base.elem;
619
+ const valTypeName = typeCodecName(valType, "val");
620
+ const fnName = `__json_dec_map_${safeIdent(valTypeName.split(".").pop())}`;
621
+ if (!this._jsonCodecs.has(fnName)) {
622
+ const valDec = this._genElemDecoder(valType);
623
+ const display = JSON.stringify(
624
+ `map[string]${this._jsonDisplayType(valType)}`,
625
+ );
626
+ this._jsonCodecs.set(
627
+ fnName,
628
+ declareCodec(
629
+ fnName,
630
+ "parsed, target, box",
631
+ ` if (parsed === null) { if (box) box.value = null; return null; }\n if (typeof parsed !== "object" || Array.isArray(parsed)) return __json_err("cannot unmarshal " + __json_type(parsed) + " into " + __json_ctx(${display}));\n var out = Object.create(null); out.__map = true;\n for (var k in parsed) {\n if (Object.prototype.hasOwnProperty.call(parsed, k)) {\n var item = ${valDec("parsed[k]", "__json_raw_child(parsed, k)")};\n if (item && item._msg) return item;\n out[k] = item;\n }\n }\n if (box) box.value = out;\n return null;`,
632
+ ),
633
+ );
634
+ }
635
+ return fnName;
636
+ },
637
+
638
+ _getScalarDecoder(baseType, base) {
639
+ const display = this._jsonDisplayType(baseType);
640
+ const fnName = `__json_dec_scalar_${safeIdent(display)}`;
641
+ if (!this._jsonCodecs.has(fnName)) {
642
+ this._jsonCodecs.set(
643
+ fnName,
644
+ declareCodec(
645
+ fnName,
646
+ "parsed, target, box, rawData, rawStr",
647
+ ` if (parsed === null) return null;\n var dv = ${this._jsonScalarDecodeExpr(base.name, display, "parsed", "rawStr")};\n if (dv && dv._msg) return dv;\n if (box) box.value = dv;\n return null;`,
648
+ ),
649
+ );
650
+ }
651
+ return fnName;
652
+ },
653
+
654
+ // Go-style display of a type in decode error messages (reflect's Type.String()).
655
+ _jsonDisplayType(t) {
656
+ if (!t) return "interface {}";
657
+ if (t.kind === "pointer") return `*${this._jsonDisplayType(t.base)}`;
658
+ if (t.kind === "named") return this._jsonTypeName(t);
659
+ if (t.kind === "basic") return BASIC_DISPLAY[t.name] ?? t.name;
660
+ if (t.kind === "slice") return `[]${this._jsonDisplayType(t.elem)}`;
661
+ if (t.kind === "map")
662
+ return `map[${this._jsonDisplayType(t.key)}]${this._jsonDisplayType(t.value ?? t.elem)}`;
663
+ return "interface {}";
664
+ },
665
+
666
+ // Decodes one parsed JSON scalar into `kind`; yields the value or an error (`_msg`).
667
+ _jsonScalarDecodeExpr(kind, display, v, rawExpr) {
668
+ const d = JSON.stringify(display);
669
+ const raw = rawExpr ?? "undefined";
670
+ switch (kind) {
671
+ case "string":
672
+ return `(typeof ${v} === "string" && !${v}.endsWith("__bigint") ? ${v} : __json_err("cannot unmarshal " + __json_type(${v}) + " into " + __json_ctx(${d})))`;
673
+ case "bool":
674
+ return `(typeof ${v} === "boolean" ? ${v} : __json_err("cannot unmarshal " + __json_type(${v}) + " into " + __json_ctx(${d})))`;
675
+ case "float32":
676
+ return `__json_float_decode(${v}, 32, ${d}, ${raw})`;
677
+ case "float64":
678
+ return `__json_float_decode(${v}, 64, ${d}, ${raw})`;
679
+ default:
680
+ return `__json_int_decode(${v}, ${JSON.stringify(kind)}, ${d}, ${raw})`;
681
+ }
682
+ },
683
+
684
+ _genElemDecoder(elemType) {
685
+ if (!elemType) return (v) => `__json_dec_any(${v})`;
686
+ const unmarshaler = this._unmarshalerElemDecoder(elemType);
687
+ if (unmarshaler) return unmarshaler;
688
+ if (isRawMessageType(elemType)) {
689
+ return (_v, rawExpr) => `__json_raw_bytes(${rawExpr})`;
690
+ }
691
+ if (isByteSliceType(elemType)) {
692
+ return (v) => `(${v} === null ? null : __json_bytes_decode(${v}))`;
693
+ }
694
+ const base = unwrapNamed(elemType);
695
+ if (base?.kind === "basic" && SCALAR_KINDS.has(base.name)) {
696
+ const display = this._jsonDisplayType(elemType);
697
+ return (v, rawExpr) =>
698
+ this._jsonScalarDecodeExpr(base.name, display, v, rawExpr);
699
+ }
700
+ if (elemType?.kind === "pointer" && elemType.base) {
701
+ const innerBase = unwrapNamed(elemType.base);
702
+ if (innerBase?.kind === "struct") {
703
+ const dec = this._getJsonDecoder(elemType.base);
704
+ const structName = elemType.base.name?.split(".").pop() ?? "Object";
705
+ return (v) =>
706
+ `(${v} === null ? null : (() => { var inst = new ${structName}(); var err = ${dec}(${v}, inst); return err || inst; })())`;
707
+ }
708
+ if (innerBase?.kind === "basic") {
709
+ const basicDec = this._genElemDecoder(elemType.base);
710
+ return (v, rawExpr) =>
711
+ `(${v} === null ? null : (() => { var item = ${basicDec(v, rawExpr)}; return item && item._msg ? item : { value: item, __p: true }; })())`;
712
+ }
713
+ }
714
+ if (base?.kind === "struct") {
715
+ const dec = this._getJsonDecoder(elemType);
716
+ const structName = elemType.name?.split(".").pop() ?? "Object";
717
+ return (v) =>
718
+ `(() => { var inst = new ${structName}(); var err = ${dec}(${v}, inst); return err || inst; })()`;
719
+ }
720
+ return (v) => `__json_dec_any(${v})`;
721
+ },
722
+
723
+ _buildStructDecoder(structType, fnName) {
724
+ const underlying = unwrapNamed(structType);
725
+ const fields = this.checker?.resolveJsonFields(underlying) ?? [];
726
+ const structName = structType.name?.split(".").pop() ?? "T";
727
+ const lines = [];
728
+ lines.push(
729
+ `var ${fnName} = typeof ${fnName} !== "undefined" ? ${fnName} : function ${fnName}(parsed, target, box) {`,
730
+ );
731
+ lines.push(
732
+ ` if (parsed === null) { if (box) box.value = null; return null; }`,
733
+ );
734
+ lines.push(` if (typeof parsed !== "object" || Array.isArray(parsed)) {`);
735
+ lines.push(
736
+ ` return __json_err("cannot unmarshal " + __json_type(parsed) + " into " + __json_ctx(${JSON.stringify(this._jsonDisplayType(structType))}));`,
737
+ );
738
+ lines.push(` }`);
739
+ // Go reports errors as "Go struct field <innermost struct>.<json key path>".
740
+ lines.push(` __json_structs.push(${JSON.stringify(structName)});`);
741
+ lines.push(` try {`);
742
+
743
+ for (const f of fields) {
744
+ const key = JSON.stringify(f.jsonKey);
745
+ const goName = JSON.stringify(f.goName);
746
+ lines.push(
747
+ ` { var fieldKey = __json_match_field(parsed, ${key}, ${goName}); if (fieldKey !== null) {`,
748
+ );
749
+ lines.push(` __json_path.push(${key});`);
750
+ lines.push(` try {`);
751
+ lines.push(` var val = parsed[fieldKey];`);
752
+ lines.push(` var raw = __json_raw_child(parsed, fieldKey);`);
753
+ const quotedKind = this._jsonQuotedKind(f);
754
+ if (quotedKind) {
755
+ const valueType = f.type?.kind === "pointer" ? f.type.base : f.type;
756
+ const typeStr = this._jsonDisplayType(valueType);
757
+ lines.push(
758
+ ` var q = __json_unquote(val, ${JSON.stringify(quotedKind)}, ${JSON.stringify(typeStr)}); if (q && q._msg) return q; val = q.v; raw = q.lit;`,
759
+ );
760
+ }
761
+ lines.push(` if (val !== null) {`);
762
+ this._emitFieldDecoder(f, structName, lines, "raw");
763
+ lines.push(` } else {`);
764
+ this._emitFieldNull(f, lines, "raw");
765
+ lines.push(` }`);
766
+ lines.push(` } finally { __json_path.pop(); }`);
767
+ lines.push(` }`);
768
+ lines.push(` }`);
769
+ }
770
+
771
+ lines.push(` } finally { __json_structs.pop(); }`);
772
+ lines.push(` return null;`);
773
+ lines.push(`};`);
774
+ return lines.join("\n");
775
+ },
776
+
777
+ _emitUnmarshalerField(f, lines, rawExpr) {
778
+ const isPtr = f.type?.kind === "pointer";
779
+ const valueType = isPtr ? f.type.base : f.type;
780
+ const info = this._jsonUnmarshalerInfo(valueType);
781
+ const bytes = `__json_raw_bytes(${rawExpr})`;
782
+ if (info.isStruct) {
783
+ lines.push(` var sub = target.${f.goName};`);
784
+ lines.push(
785
+ ` if (!sub) { sub = new ${info.cls}(); target.${f.goName} = sub; }`,
786
+ );
787
+ lines.push(
788
+ ` var uerr = ${this._jsonUnmarshalCall(info, "sub", bytes)};`,
789
+ );
790
+ } else if (isPtr) {
791
+ lines.push(` var sub = target.${f.goName};`);
792
+ lines.push(
793
+ ` if (!sub) { sub = { value: ${this.zeroValueForType(valueType)} }; target.${f.goName} = sub; }`,
794
+ );
795
+ lines.push(
796
+ ` var uerr = ${this._jsonUnmarshalCall(info, "sub", bytes)};`,
797
+ );
798
+ } else {
799
+ lines.push(
800
+ ` var addr = { get value() { return target.${f.goName}; }, set value(__v) { target.${f.goName} = __v; } };`,
801
+ );
802
+ lines.push(
803
+ ` var uerr = ${this._jsonUnmarshalCall(info, "addr", bytes)};`,
804
+ );
805
+ }
806
+ lines.push(` if (uerr) return __json_user_err(uerr);`);
807
+ if (f.isEmbedded && info.isStruct) {
808
+ lines.push(` Object.assign(target, sub);`);
809
+ }
810
+ },
811
+
812
+ _emitFieldDecoder(f, structName, lines, rawExpr) {
813
+ const valueType = f.type?.kind === "pointer" ? f.type.base : f.type;
814
+ if (this._jsonUnmarshalerInfo(valueType)) {
815
+ this._emitUnmarshalerField(f, lines, rawExpr);
816
+ return;
817
+ }
818
+ if (isRawMessageType(f.type)) {
819
+ lines.push(
820
+ ` var rawBytes = __json_raw_bytes(${rawExpr}); rawBytes.__raw = true;`,
821
+ );
822
+ this._emitAssign(f, "rawBytes", lines);
823
+ return;
824
+ }
825
+ if (isByteSliceType(f.type)) {
826
+ lines.push(` var u8v = __json_bytes_decode(val);`);
827
+ lines.push(` if (u8v && u8v._msg) return u8v;`);
828
+ this._emitAssign(f, "u8v", lines);
829
+ return;
830
+ }
831
+
832
+ const base = unwrapNamed(f.type);
833
+ if (base?.kind === "basic") {
834
+ this._emitBasicFieldDecoder(f, base.name, structName, lines);
835
+ return;
836
+ }
837
+
838
+ if (f.type?.kind === "pointer" && f.type.base) {
839
+ const innerBase = unwrapNamed(f.type.base);
840
+ if (innerBase?.kind === "basic") {
841
+ this._emitPointerBasicFieldDecoder(
842
+ f,
843
+ innerBase.name,
844
+ structName,
845
+ lines,
846
+ );
847
+ return;
848
+ }
849
+ if (innerBase?.kind === "struct") {
850
+ const innerName = f.type.base.name?.split(".").pop() ?? "Object";
851
+ const dec = this._getJsonDecoder(f.type.base);
852
+ lines.push(` var sub = target.${f.goName};`);
853
+ lines.push(
854
+ ` if (!sub) { sub = new ${innerName}(); target.${f.goName} = sub; }`,
855
+ );
856
+ lines.push(` var err = ${dec}(val, sub);`);
857
+ lines.push(` if (err) return err;`);
858
+ if (f.isEmbedded) {
859
+ lines.push(` Object.assign(target, sub);`);
860
+ }
861
+ return;
862
+ }
863
+ }
864
+
865
+ if (base?.kind === "struct") {
866
+ const innerName = f.type.name?.split(".").pop() ?? "Object";
867
+ const dec = this._getJsonDecoder(f.type);
868
+ lines.push(` var sub = target.${f.goName};`);
869
+ lines.push(
870
+ ` if (!sub) { sub = new ${innerName}(); target.${f.goName} = sub; }`,
871
+ );
872
+ lines.push(` var err = ${dec}(val, sub);`);
873
+ lines.push(` if (err) return err;`);
874
+ if (f.isEmbedded) {
875
+ lines.push(` Object.assign(target, sub);`);
876
+ }
877
+ return;
878
+ }
879
+
880
+ if (base?.kind === "slice") {
881
+ const dec = this._getJsonDecoder(f.type);
882
+ lines.push(` var box = { value: null };`);
883
+ lines.push(` var err = ${dec}(val, null, box);`);
884
+ lines.push(` if (err) return err;`);
885
+ this._emitAssign(f, "box.value", lines);
886
+ return;
887
+ }
888
+
889
+ if (base?.kind === "map") {
890
+ const dec = this._getJsonDecoder(f.type);
891
+ lines.push(` var box = { value: null };`);
892
+ lines.push(` var err = ${dec}(val, null, box);`);
893
+ lines.push(` if (err) return err;`);
894
+ this._emitAssign(f, "box.value", lines);
895
+ return;
896
+ }
897
+
898
+ this._emitAssign(f, "__json_dec_any(val)", lines);
899
+ },
900
+
901
+ // `*T` scalar fields: Go reports the element type in errors.
902
+ _emitPointerBasicFieldDecoder(f, basicName, _structName, lines) {
903
+ if (!SCALAR_KINDS.has(basicName)) {
904
+ this._emitAssign(f, "__json_dec_any(val)", lines);
905
+ return;
906
+ }
907
+ const display = this._jsonDisplayType(f.type.base);
908
+ lines.push(
909
+ ` var dv = ${this._jsonScalarDecodeExpr(basicName, display, "val", "raw")};`,
910
+ );
911
+ lines.push(` if (dv && dv._msg) return dv;`);
912
+ this._emitAssign(f, "{ value: dv, __p: true }", lines);
913
+ },
914
+
915
+ _emitBasicFieldDecoder(f, basicName, _structName, lines) {
916
+ if (!SCALAR_KINDS.has(basicName)) {
917
+ this._emitAssign(f, "__json_dec_any(val)", lines);
918
+ return;
919
+ }
920
+ const display = this._jsonDisplayType(f.type);
921
+ lines.push(
922
+ ` var dv = ${this._jsonScalarDecodeExpr(basicName, display, "val", "raw")};`,
923
+ );
924
+ lines.push(` if (dv && dv._msg) return dv;`);
925
+ this._emitAssign(f, "dv", lines);
926
+ },
927
+
928
+ _emitAssign(f, expr, lines) {
929
+ const path = f.path ?? [{ name: f.goName, isPtr: false }];
930
+ if (path.length === 1) {
931
+ lines.push(` target.${f.goName} = ${expr};`);
932
+ return;
933
+ }
934
+
935
+ for (let i = 0; i < path.length - 1; i++) {
936
+ const seg = path[i];
937
+ const curPath = `target.${path
938
+ .slice(0, i + 1)
939
+ .map((p) => p.name)
940
+ .join(".")}`;
941
+ const ctor = seg.typeName || seg.name;
942
+ const initVal = ctor && ctor !== "Object" ? `new ${ctor}()` : `{}`;
943
+ lines.push(` if (${curPath} == null) ${curPath} = ${initVal};`);
944
+ }
945
+ lines.push(` target.${path.map((p) => p.name).join(".")} = ${expr};`);
946
+ lines.push(` target.${f.goName} = ${expr};`);
947
+ },
948
+
949
+ _emitFieldNull(f, lines, rawExpr) {
950
+ // Go hands JSON null to UnmarshalJSON for non-pointer fields; nil pointers stay nil.
951
+ if (f.type?.kind !== "pointer" && this._jsonUnmarshalerInfo(f.type)) {
952
+ this._emitUnmarshalerField(f, lines, `"null"`);
953
+ return;
954
+ }
955
+ if (isRawMessageType(f.type)) {
956
+ lines.push(
957
+ ` var rawNull = __json_raw_bytes(${rawExpr}); rawNull.__raw = true;`,
958
+ );
959
+ this._emitAssign(f, "rawNull", lines);
960
+ return;
961
+ }
962
+ if (!isJsonNullAssignable(f.type)) return;
963
+ const path = f.path ?? [{ name: f.goName, isPtr: false }];
964
+ if (path.length === 1) {
965
+ lines.push(` target.${f.goName} = null;`);
966
+ return;
967
+ }
968
+ lines.push(
969
+ ` target.${path.map((p) => p.name).join(".")} = null; target.${f.goName} = null;`,
970
+ );
971
+ },
972
+ };