zod-to-x 2.2.1-dev.0 → 2.4.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,624 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.Zod2Dart = void 0;
7
+ const case_1 = __importDefault(require("case"));
8
+ const core_1 = require("../../core");
9
+ const options_1 = require("./options");
10
+ const libs_1 = require("./libs");
11
+ /**
12
+ * Transpiler for Zod schemas to Dart classes using package:json_annotation.
13
+ *
14
+ * The `.g.dart` part file is NOT generated by this transpiler — it must be produced
15
+ * externally via `dart run build_runner build`.
16
+ */
17
+ class Zod2Dart extends core_1.Zod2X {
18
+ constructor(opt = {}) {
19
+ super(Object.assign(Object.assign({}, options_1.defaultOpts), opt));
20
+ this.commentKey = "//";
21
+ /**
22
+ * Maps a local struct/class name to the discriminated union names it participates in.
23
+ * Populated by a pre-scan of the AST before the normal transpile pass.
24
+ */
25
+ this._unionMembers = new Map();
26
+ /**
27
+ * Names of locally-defined discriminated union abstract classes.
28
+ * Used to emit @JsonKey(fromJson/toJson) when such a type appears as a field.
29
+ */
30
+ this._discriminatedUnionTypes = new Set();
31
+ /**
32
+ * Names of typedef-emitted types (alias, union typedef, struct extends).
33
+ * When these appear as field types in @JsonSerializable classes, we emit
34
+ * @JsonKey(fromJson: TypeName.fromJson) to prevent json_serializable from
35
+ * following the typedef chain and referencing transitive imports.
36
+ */
37
+ this._typedefNames = new Set();
38
+ /**
39
+ * Maps a disc union typedef name (e.g. "NewDiscriminatedUnionItem") to the
40
+ * fromJson function name of the underlying entity-level union
41
+ * (e.g. "discriminatedUnionItemFromJson").
42
+ * Populated during pre-scan when an ASTUnion node has aliasOf set.
43
+ */
44
+ this._discriminatedUnionDelegateFromJson = new Map();
45
+ /**
46
+ * Unified set of type names needing custom @JsonKey annotations.
47
+ * - Discriminated union types: fromJson = <camelName>FromJson (local function)
48
+ * - Typedef types: fromJson = _<camelName>FromJson (standalone wrapper)
49
+ * - TypedefList: list fromJson/toJson helpers
50
+ * _toJson helpers are emitted in runAfter() for all entries.
51
+ */
52
+ this._usedCustomFromJsonTypes = new Map();
53
+ /**
54
+ * Maps a type name (e.g. "GenericUserEntity") to its full Dart type with
55
+ * template args (e.g. "GenericUserEntity<SomeDtoResult>"). Used in runAfter()
56
+ * to generate helpers with correct generic return types and factory calls.
57
+ */
58
+ this._usedCustomFromJsonFullTypes = new Map();
59
+ /**
60
+ * Maps a typedef name to its full extended type (the right-hand side of the
61
+ * typedef). E.g. "AdminUserEntity" → "GenericUserEntity<AdminUserMetadata>".
62
+ * Populated in addExtendedType() when the extended type has generic args.
63
+ * Used in runAfter() to generate 2-arg fromJson calls for generic classes.
64
+ */
65
+ this._typedefToExtendedType = new Map();
66
+ // ── Primitive type methods ──────────────────────────────────────────────
67
+ this.getStringType = () => "String";
68
+ this.getBooleanType = () => "bool";
69
+ this.getAnyType = () => "dynamic";
70
+ this.getDateType = () => "DateTime";
71
+ /** Ex: Set<T> */
72
+ this.getSetType = (itemType) => `Set<${itemType}>`;
73
+ /** Ex: Map<K, V> */
74
+ this.getMapType = (keyType, valueType) => `Map<${keyType}, ${valueType}>`;
75
+ /** Ex: Map<String, V> (record key is always String) */
76
+ this.getRecordType = (keyType, valueType) => `Map<${keyType}, ${valueType}>`;
77
+ /** Ex: (T1, T2, T3) — Dart 3 positional record type */
78
+ this.getTupleType = (itemsType) => `(${itemsType.join(", ")})`;
79
+ /** Dart has no native union type; use Object */
80
+ this.getUnionType = (_itemsType) => "Object";
81
+ /** Dart has no native intersection type; use Object */
82
+ this.getIntersectionType = (_itemsType) => "Object";
83
+ this.lib = (0, libs_1.getLibs)();
84
+ }
85
+ /**
86
+ * Overrides transpile() to pre-scan discriminated union memberships before the normal pass.
87
+ * This allows transpileStruct() to emit `implements UnionName` on member classes.
88
+ */
89
+ transpile(transpilerQueue) {
90
+ this._unionMembers.clear();
91
+ this._discriminatedUnionTypes.clear();
92
+ this._typedefNames.clear();
93
+ this._usedCustomFromJsonTypes.clear();
94
+ this._usedCustomFromJsonFullTypes.clear();
95
+ this._discriminatedUnionDelegateFromJson.clear();
96
+ this._typedefToExtendedType.clear();
97
+ transpilerQueue.nodes.forEach((node) => {
98
+ if (node instanceof core_1.ASTUnion && node.discriminantKey) {
99
+ // Track the union name for @JsonKey emission
100
+ this._discriminatedUnionTypes.add(node.name);
101
+ // If this is a typedef alias of another disc union (aliasOf set),
102
+ // record which entity-level fromJson function to delegate to.
103
+ const aliasOf = node.aliasOf;
104
+ if (aliasOf) {
105
+ this._discriminatedUnionDelegateFromJson.set(node.name, `${case_1.default.camel(aliasOf)}FromJson`);
106
+ }
107
+ node.options.forEach((opt) => {
108
+ var _a;
109
+ const name = opt.name;
110
+ // Only track types local to this file (no parentFile = not imported)
111
+ if (name && !opt.parentFile) {
112
+ const existing = (_a = this._unionMembers.get(name)) !== null && _a !== void 0 ? _a : [];
113
+ existing.push(node.name);
114
+ this._unionMembers.set(name, existing);
115
+ }
116
+ });
117
+ }
118
+ // Detect cross-file nodes that are generic type instantiations:
119
+ // e.g. AdminUserEntity (parentFile=app, description="GenericUserEntity",
120
+ // templatesTranslation=[{aliasOf:"AdminUserMetadata"}])
121
+ // → store "AdminUserEntity" → "GenericUserEntity<AdminUserMetadata>" in
122
+ // _typedefToExtendedType so runAfter() can generate 2-arg fromJson helpers.
123
+ const n = node;
124
+ if (n.parentFile &&
125
+ typeof n.description === "string" &&
126
+ n.description.length > 0 &&
127
+ Array.isArray(n.templatesTranslation) &&
128
+ n.templatesTranslation.length > 0) {
129
+ const typeArgs = n.templatesTranslation
130
+ .map((t) => t.aliasOf || t.name || "")
131
+ .filter(Boolean)
132
+ .join(", ");
133
+ if (typeArgs && n.name) {
134
+ this._typedefToExtendedType.set(n.name, `${n.description}<${typeArgs}>`);
135
+ }
136
+ }
137
+ });
138
+ return super.transpile(transpilerQueue);
139
+ }
140
+ runBefore() {
141
+ if (this.opt.useJsonSerialization !== false) {
142
+ this.preImports.add(this.lib.jsonAnnotation);
143
+ if (this.opt.partFile) {
144
+ this.postImports.add(`part '${this.opt.partFile}.g.dart';`);
145
+ }
146
+ }
147
+ }
148
+ runAfter() {
149
+ var _a, _b;
150
+ // Emit helper functions for field types that need custom @JsonKey annotations.
151
+ // These helpers prevent json_serializable from following typedef chains and generating
152
+ // code that references transitively-imported types not directly in scope.
153
+ if (this.opt.useJsonSerialization !== false) {
154
+ for (const [typeName, kind] of this._usedCustomFromJsonTypes) {
155
+ const fnBase = case_1.default.camel(typeName);
156
+ // Use full type (including generics) if available, e.g. GenericUserEntity<SomeDtoResult>
157
+ const fullType = (_a = this._usedCustomFromJsonFullTypes.get(typeName)) !== null && _a !== void 0 ? _a : typeName;
158
+ // Extract type argument for generic classes: "Foo<Bar>" → "Bar"
159
+ const genericArgMatch = fullType.match(/^([^<]+)<(.+)>$/);
160
+ const baseTypeName = genericArgMatch ? genericArgMatch[1] : fullType;
161
+ const typeArg = genericArgMatch ? genericArgMatch[2] : undefined;
162
+ const isGeneric = typeArg !== undefined;
163
+ if (kind === "discUnion") {
164
+ // Abstract class: toJson helper only (fromJson is the named union function)
165
+ this.push0(`Map<String, dynamic> _${fnBase}ToJson(${fullType} value) =>`);
166
+ this.push1(`(value as dynamic).toJson() as Map<String, dynamic>;\n`);
167
+ }
168
+ else if (kind === "typedef") {
169
+ // Use full extended type if it has generics (e.g. AdminUserEntity → GenericUserEntity<AdminUserMetadata>)
170
+ const extendedType = (_b = this._usedCustomFromJsonFullTypes.get(typeName)) !== null && _b !== void 0 ? _b : this._typedefToExtendedType.get(typeName);
171
+ const genericArgMatch = extendedType === null || extendedType === void 0 ? void 0 : extendedType.match(/^([^<]+)<(.+)>$/);
172
+ const extBaseType = genericArgMatch ? genericArgMatch[1] : undefined;
173
+ const extTypeArg = genericArgMatch ? genericArgMatch[2] : undefined;
174
+ if (isGeneric) {
175
+ // Direct generic field (e.g. GenericUserEntity<SomeDtoResult>): factory pattern
176
+ this.push0(`${fullType} _${fnBase}FromJson(Object? json) =>`);
177
+ this.push1(`${baseTypeName}.fromJson(json as Map<String, dynamic>, (j) => ${typeArg}.fromJson(j as Map<String, dynamic>));\n`);
178
+ this.push0(`Map<String, dynamic> _${fnBase}ToJson(${fullType} value) =>`);
179
+ this.push1(`value.toJson((v) => v.toJson());\n`);
180
+ }
181
+ else if (extBaseType && extTypeArg) {
182
+ // Typedef of a generic class (e.g. AdminUserEntity = GenericUserEntity<AdminUserMetadata>)
183
+ this.push0(`${typeName} _${fnBase}FromJson(Object? json) =>`);
184
+ this.push1(`${extBaseType}.fromJson(json as Map<String, dynamic>, (j) => ${extTypeArg}.fromJson(j as Map<String, dynamic>));\n`);
185
+ this.push0(`Map<String, dynamic> _${fnBase}ToJson(${typeName} value) =>`);
186
+ this.push1(`value.toJson((v) => v.toJson());\n`);
187
+ }
188
+ else {
189
+ // Non-generic class: simple fromJson + toJson helpers
190
+ this.push0(`${typeName} _${fnBase}FromJson(Object? json) =>`);
191
+ this.push1(`${typeName}.fromJson(json as Map<String, dynamic>);\n`);
192
+ this.push0(`Map<String, dynamic> _${fnBase}ToJson(${typeName} value) =>`);
193
+ this.push1(`(value as dynamic).toJson() as Map<String, dynamic>;\n`);
194
+ }
195
+ }
196
+ else if (kind === "typedefList") {
197
+ // List<typedef>: list fromJson + toJson helpers
198
+ this.push0(`List<${typeName}> _${fnBase}ListFromJson(Object? json) =>`);
199
+ this.push1(`(json as List<dynamic>).map((e) => ${typeName}.fromJson(e as Map<String, dynamic>)).toList();\n`);
200
+ this.push0(`List<Map<String, dynamic>> _${fnBase}ListToJson(List<${typeName}> list) =>`);
201
+ this.push1(`list.map((e) => (e as dynamic).toJson() as Map<String, dynamic>).toList();\n`);
202
+ }
203
+ }
204
+ }
205
+ this._usedCustomFromJsonTypes.clear();
206
+ this._usedCustomFromJsonFullTypes.clear();
207
+ }
208
+ // ── Import / namespace ──────────────────────────────────────────────────
209
+ addImportFromFile(filename, _namespace) {
210
+ const base = filename.endsWith(".dart") ? filename.slice(0, -5) : filename;
211
+ return `import './${base}.dart';`;
212
+ }
213
+ getTypeFromExternalNamespace(_namespace, typeName) {
214
+ return typeName;
215
+ }
216
+ // ── Generics ────────────────────────────────────────────────────────────
217
+ getGenericTemplatesTranslation(data) {
218
+ if ((data instanceof core_1.ASTObject || data instanceof core_1.ASTDefinition) &&
219
+ data.templatesTranslation.length > 0) {
220
+ return ("<" +
221
+ data.templatesTranslation
222
+ .map((t) => {
223
+ if (this.isExternalTypeImport(t)) {
224
+ this.addExternalTypeImport(t);
225
+ return this.getTypeFromExternalNamespace(t.parentNamespace, t.aliasOf);
226
+ }
227
+ else {
228
+ return t.aliasOf;
229
+ }
230
+ })
231
+ .join(", ") +
232
+ ">");
233
+ }
234
+ }
235
+ // ── Layered modeling ────────────────────────────────────────────────────
236
+ /**
237
+ * Emits an alias/extension declaration for cross-layer references.
238
+ */
239
+ checkExtendedTypeInclusion(data, type) {
240
+ const isStruct = data instanceof core_1.ASTObject ||
241
+ (data instanceof core_1.ASTIntersection && data.newObject !== undefined);
242
+ const translatedTemplates = this.getGenericTemplatesTranslation(data);
243
+ const templates = translatedTemplates || undefined;
244
+ const declaredTemplates = !translatedTemplates && data instanceof core_1.ASTObject && data.templates.size > 0
245
+ ? `<${[...data.templates].join(", ")}>`
246
+ : undefined;
247
+ if (this.isExternalTypeImport(data)) {
248
+ if (data.aliasOf) {
249
+ this.addExtendedType(data.name, data.parentNamespace, data.aliasOf, {
250
+ type: isStruct ? undefined : type,
251
+ templates,
252
+ declaredTemplates,
253
+ });
254
+ this.addExternalTypeImport(data);
255
+ }
256
+ return true;
257
+ }
258
+ else if (data.aliasOf) {
259
+ this.addExtendedType(data.name, data.parentNamespace, data.aliasOf, {
260
+ type: isStruct ? undefined : type,
261
+ isInternal: true,
262
+ templates,
263
+ declaredTemplates,
264
+ });
265
+ return true;
266
+ }
267
+ return false;
268
+ }
269
+ addExtendedType(name, parentNamespace, aliasOf, opt) {
270
+ var _a, _b;
271
+ const extendedType = (opt === null || opt === void 0 ? void 0 : opt.isInternal)
272
+ ? aliasOf
273
+ : this.getTypeFromExternalNamespace(parentNamespace, aliasOf);
274
+ const templates = (_a = opt === null || opt === void 0 ? void 0 : opt.templates) !== null && _a !== void 0 ? _a : "";
275
+ const declaredTemplates = (_b = opt === null || opt === void 0 ? void 0 : opt.declaredTemplates) !== null && _b !== void 0 ? _b : "";
276
+ if ((opt === null || opt === void 0 ? void 0 : opt.type) === "alias" || (opt === null || opt === void 0 ? void 0 : opt.type) === "union") {
277
+ this._typedefNames.add(name);
278
+ this.push0(`typedef ${name}${declaredTemplates} = ${extendedType}${templates};\n`);
279
+ }
280
+ else {
281
+ // Dart 3: use typedef for empty class extensions to keep json_serializable happy.
282
+ // `class B extends A {}` without @JsonSerializable is rejected by build_runner.
283
+ // A typedef is valid Dart 3 syntax and correctly aliases the type for serialization.
284
+ this._typedefNames.add(name);
285
+ this.push0(`typedef ${name}${declaredTemplates} = ${extendedType}${templates};\n`);
286
+ }
287
+ // Track generic extended types so runAfter() can generate 2-arg fromJson calls.
288
+ const fullExtended = `${extendedType}${templates !== null && templates !== void 0 ? templates : ""}`;
289
+ if (fullExtended.includes("<")) {
290
+ this._typedefToExtendedType.set(name, fullExtended);
291
+ }
292
+ }
293
+ getNumberType(isInt, _range) {
294
+ // Dart has a single `int` type regardless of range
295
+ return isInt ? "int" : "double";
296
+ }
297
+ /**
298
+ * Returns the Dart type for a literal value.
299
+ * Dart has no structural literal types, so we return the underlying primitive type.
300
+ * When a parent enum is provided, returns the enum type name (not a member expression),
301
+ * since Dart enum members cannot be used as types.
302
+ */
303
+ getLiteralStringType(value, parentEnumNameKey) {
304
+ if (parentEnumNameKey) {
305
+ // Return the enum type name only — member access is not a valid Dart type
306
+ return parentEnumNameKey[0];
307
+ }
308
+ if (typeof value === "boolean")
309
+ return "bool";
310
+ if (typeof value === "number") {
311
+ return Number.isInteger(value) ? "int" : "double";
312
+ }
313
+ return "String";
314
+ }
315
+ // ── Composite type methods ──────────────────────────────────────────────
316
+ /** Ex: List<List<T>> for 2D array */
317
+ getArrayType(arrayType, arrayDeep) {
318
+ let output = `List<${arrayType}>`;
319
+ for (let i = 0; i < arrayDeep - 1; i++) {
320
+ output = `List<${output}>`;
321
+ }
322
+ return output;
323
+ }
324
+ // ── Transpile methods ───────────────────────────────────────────────────
325
+ transpileAliasedType(data) {
326
+ if (this.checkExtendedTypeInclusion(data, "alias"))
327
+ return;
328
+ this.addComment(data.description);
329
+ let resolvedType;
330
+ if (data instanceof core_1.ASTArray) {
331
+ resolvedType = this.getAttributeType(data.item);
332
+ }
333
+ else {
334
+ resolvedType = this.getAttributeType(data);
335
+ }
336
+ this.push0(`typedef ${data.name} = ${resolvedType};\n`);
337
+ }
338
+ /**
339
+ * Emits a Dart enhanced enum with a typed `value` field and `@JsonEnum` annotation.
340
+ *
341
+ * All-string values:
342
+ * @JsonEnum(valueField: 'value')
343
+ * enum EnumItem {
344
+ * enum1('Enum1'),
345
+ * enum2('Enum2');
346
+ *
347
+ * const EnumItem(this.value);
348
+ * final String value;
349
+ * }
350
+ *
351
+ * Mixed (int + string): typedef alias with comment.
352
+ */
353
+ transpileEnum(data) {
354
+ if (this.checkExtendedTypeInclusion(data, "alias"))
355
+ return;
356
+ this.addComment(data.description);
357
+ const allStrings = data.values.every(([, v]) => typeof v === "string");
358
+ const allInts = data.values.every(([, v]) => typeof v === "number");
359
+ if (allStrings || allInts) {
360
+ const valueType = allStrings ? "String" : "int";
361
+ const q = allStrings ? "'" : "";
362
+ if (this.opt.useJsonSerialization !== false) {
363
+ this.push0(`@JsonEnum(valueField: 'value')`);
364
+ }
365
+ this.push0(`enum ${data.name} {`);
366
+ data.values.forEach(([key, value], index) => {
367
+ const memberName = case_1.default.camel(key);
368
+ const comma = index < data.values.length - 1 ? "," : ";";
369
+ this.push1(`${memberName}(${q}${value}${q})${comma}`);
370
+ });
371
+ this.push0("");
372
+ this.push1(`const ${data.name}(this.value);`);
373
+ this.push1(`final ${valueType} value;`);
374
+ this.push0(`}\n`);
375
+ }
376
+ else {
377
+ // Mixed-type enum: fall back to typedef alias + comment
378
+ this.push0(`typedef ${data.name} = dynamic;`);
379
+ this.push0(`// ${data.name}: mixed-type enum — no single Dart base type`);
380
+ data.values.forEach(([key, value]) => {
381
+ const constName = case_1.default.camel(key);
382
+ const v = typeof value === "string" ? `'${value}'` : `${value}`;
383
+ this.push0(`// const ${constName} = ${v};`);
384
+ });
385
+ this.push0("");
386
+ }
387
+ }
388
+ transpileStruct(data) {
389
+ if (this.checkExtendedTypeInclusion(data))
390
+ return;
391
+ this.addComment(data.description);
392
+ this._transpileStructBody(data);
393
+ }
394
+ /**
395
+ * Dart intersection:
396
+ * - If the AST produced a merged `newObject`, emit it as a flat class.
397
+ * - Otherwise emit a typedef alias to Object with a comment.
398
+ */
399
+ transpileIntersection(data) {
400
+ if (this.checkExtendedTypeInclusion(data))
401
+ return;
402
+ this.addComment(data.description);
403
+ if (data.newObject) {
404
+ this._transpileStructBody(data.newObject);
405
+ }
406
+ else {
407
+ const leftType = this.getAttributeType(data.left);
408
+ const rightType = this.getAttributeType(data.right);
409
+ this.push0(`typedef ${data.name} = Object; // Intersection of: ${leftType}, ${rightType}\n`);
410
+ }
411
+ }
412
+ /**
413
+ * Dart union:
414
+ * - Non-discriminated: typedef alias to Object with a comment listing member types.
415
+ * - Discriminated: abstract base class + factory unmarshal function using Dart 3 switch.
416
+ */
417
+ transpileUnion(data) {
418
+ if (this.checkExtendedTypeInclusion(data, "union"))
419
+ return;
420
+ this.addComment(data.description);
421
+ const optionNames = data.options.map(this.getAttributeType.bind(this));
422
+ if (data.discriminantKey) {
423
+ this.push0(`// Discriminated union on '${data.discriminantKey}'.`);
424
+ this.push0(`// Possible types: ${optionNames.join(", ")}`);
425
+ this.push0(`abstract class ${data.name} {}\n`);
426
+ // Emit factory unmarshal function when all members have a discriminant value
427
+ const optionsData = data.options.map((opt, i) => {
428
+ var _a;
429
+ return ({
430
+ typeName: optionNames[i],
431
+ discriminantValue: (_a = opt.constraints) === null || _a === void 0 ? void 0 : _a.discriminantValue,
432
+ });
433
+ });
434
+ const allHaveDiscriminantValue = optionsData.every((o) => o.discriminantValue !== undefined);
435
+ if (allHaveDiscriminantValue && this.opt.useJsonSerialization !== false) {
436
+ const fnName = case_1.default.camel(data.name);
437
+ this.push0(`${data.name} ${fnName}FromJson(Map<String, dynamic> json) {`);
438
+ this.push1(`return switch (json['${data.discriminantKey}']) {`);
439
+ for (const opt of optionsData) {
440
+ const dv = opt.discriminantValue;
441
+ const isNumber = !isNaN(Number(dv));
442
+ const isBool = dv === "true" || dv === "false";
443
+ const caseVal = isNumber || isBool ? dv : `'${dv}'`;
444
+ // For generic types like Foo<Bar>, use 2-arg factory fromJson(json, Bar.fromJson)
445
+ const typeNameStr = opt.typeName;
446
+ const genericMatch = typeNameStr.match(/^([^<]+)<(.+)>$/);
447
+ if (genericMatch) {
448
+ const baseType = genericMatch[1];
449
+ const typeArgStr = genericMatch[2];
450
+ this.push2(`${caseVal} => ${baseType}.fromJson(json, (j) => ${typeArgStr}.fromJson(j as Map<String, dynamic>)),`);
451
+ }
452
+ else {
453
+ this.push2(`${caseVal} => ${typeNameStr}.fromJson(json),`);
454
+ }
455
+ }
456
+ const escapedKey = data.discriminantKey.replace(/"/g, '\\"');
457
+ this.push2(`_ => throw Exception("Unknown ${data.name} discriminant: \${json['${escapedKey}']}")`);
458
+ this.push1(`};`);
459
+ this.push0(`}\n`);
460
+ }
461
+ }
462
+ else {
463
+ this.push0(`// ${data.name} is a union of: ${optionNames.join(", ")}`);
464
+ this.push0(`typedef ${data.name} = Object;\n`);
465
+ }
466
+ }
467
+ // ── Private helpers ─────────────────────────────────────────────────────
468
+ /** Renders the full body of a Dart class for an ASTObject. */
469
+ _transpileStructBody(data) {
470
+ var _a, _b;
471
+ const isGeneric = data.templates.size > 0;
472
+ const templateStr = isGeneric ? `<${[...data.templates].join(", ")}>` : "";
473
+ const unionNames = (_b = this._unionMembers.get((_a = data.name) !== null && _a !== void 0 ? _a : "")) !== null && _b !== void 0 ? _b : [];
474
+ const implementsClause = unionNames.length > 0 ? ` implements ${unionNames.join(", ")}` : "";
475
+ const useJson = this.opt.useJsonSerialization !== false;
476
+ if (useJson) {
477
+ if (isGeneric) {
478
+ this.push0(`@JsonSerializable(explicitToJson: true, genericArgumentFactories: true)`);
479
+ }
480
+ else {
481
+ this.push0(`@JsonSerializable(explicitToJson: true)`);
482
+ }
483
+ }
484
+ this.push0(`class ${data.name}${templateStr}${implementsClause} {`);
485
+ const properties = Object.entries(data.properties);
486
+ if (properties.length === 0) {
487
+ this.push0(`}`);
488
+ if (useJson) {
489
+ this._emitFromToJson(data);
490
+ }
491
+ this.push0("");
492
+ return;
493
+ }
494
+ // Field declarations
495
+ for (const [key, value] of properties) {
496
+ this._transpileMember(key, value);
497
+ }
498
+ // Constructor
499
+ this.push0("");
500
+ this.push1(`${data.name}({`);
501
+ for (const [key, value] of properties) {
502
+ const fieldName = this.opt.keepKeys === true ? key : case_1.default.camel(key);
503
+ const isOpt = value.isOptional || value.isNullable;
504
+ if (isOpt) {
505
+ this.push2(`this.${fieldName},`);
506
+ }
507
+ else {
508
+ this.push2(`required this.${fieldName},`);
509
+ }
510
+ }
511
+ this.push1(`});`);
512
+ if (useJson) {
513
+ this._emitFromToJson(data);
514
+ }
515
+ this.push0(`}\n`);
516
+ }
517
+ /** Renders a single class field declaration with optional `@JsonKey` annotation. */
518
+ _transpileMember(key, memberNode) {
519
+ var _a, _b;
520
+ const fieldName = this.opt.keepKeys === true ? key : case_1.default.camel(key);
521
+ const isOpt = memberNode.isOptional || memberNode.isNullable;
522
+ const varType = this.getAttributeType(memberNode);
523
+ if (memberNode.description && !memberNode.name && !this.isTranspilerable(memberNode)) {
524
+ this.addComment(memberNode.description, `\n${this.indent[1]}`);
525
+ }
526
+ const useJson = this.opt.useJsonSerialization !== false;
527
+ let annotationEmitted = false;
528
+ if (useJson) {
529
+ // Only @JsonSerializable classes (ASTObject) need @JsonKey to prevent json_serializable
530
+ // from following the typedef chain to a type not directly imported.
531
+ // Primitives (String, int, double, bool, DateTime), collections (List, Map, Set,
532
+ // Tuple), enums, and dynamic are all handled natively by json_serializable.
533
+ const instanceType = memberNode.instanceType;
534
+ const isClassLike = instanceType === "ASTObject";
535
+ // Cross-file reference that is a class-like type: needs @JsonKey wrapper.
536
+ const memberIsAlias = memberNode.parentFile !== undefined && isClassLike;
537
+ const isList = ((_a = memberNode.arrayDimension) !== null && _a !== void 0 ? _a : 0) > 0;
538
+ // Actual type name (before array/optional wrapping)
539
+ const nodeName = memberNode.name;
540
+ const baseType = varType.replace(/\?$/, "").replace(/^List<(.+)>$/, "$1");
541
+ const localTypeName = baseType.includes(".") ? baseType.split(".").pop() : baseType;
542
+ if (this._discriminatedUnionTypes.has(localTypeName) && !isList) {
543
+ // Abstract discriminated union — use local or delegate fromJson function.
544
+ // If this union is a typedef alias of another (aliasOf set), delegate to
545
+ // the entity-level fromJson so we don't define a duplicate dispatcher.
546
+ this._usedCustomFromJsonTypes.set(localTypeName, "discUnion");
547
+ const fnBase = case_1.default.camel(localTypeName);
548
+ const fromJsonFn = (_b = this._discriminatedUnionDelegateFromJson.get(localTypeName)) !== null && _b !== void 0 ? _b : `${fnBase}FromJson`;
549
+ const namePart = fieldName !== key ? `name: '${key}', ` : "";
550
+ this.push1(`@JsonKey(${namePart}fromJson: ${fromJsonFn}, toJson: _${fnBase}ToJson)`);
551
+ annotationEmitted = true;
552
+ }
553
+ else if (this._typedefNames.has(localTypeName) && !isList && isClassLike) {
554
+ // Locally-defined typedef of a class — use standalone fromJson helper to
555
+ // prevent json_serializable from following the chain to an un-imported type.
556
+ this._usedCustomFromJsonTypes.set(localTypeName, "typedef");
557
+ const fnBase = case_1.default.camel(localTypeName);
558
+ const namePart = fieldName !== key ? `name: '${key}', ` : "";
559
+ this.push1(`@JsonKey(${namePart}fromJson: _${fnBase}FromJson, toJson: _${fnBase}ToJson)`);
560
+ annotationEmitted = true;
561
+ }
562
+ else if (memberIsAlias) {
563
+ // Cross-file class alias not in _typedefNames — same treatment (including lists).
564
+ const typeName = nodeName || localTypeName;
565
+ const fnBase = case_1.default.camel(typeName);
566
+ const kind = isList ? "typedefList" : "typedef";
567
+ this._usedCustomFromJsonTypes.set(typeName, kind);
568
+ // Store full type (including generics) for helper generation in runAfter()
569
+ const fullType = varType.replace(/\?$/, "");
570
+ if (fullType !== typeName) {
571
+ this._usedCustomFromJsonFullTypes.set(typeName, fullType);
572
+ }
573
+ const namePart = fieldName !== key ? `name: '${key}', ` : "";
574
+ if (isList) {
575
+ this.push1(`@JsonKey(${namePart}fromJson: _${fnBase}ListFromJson, toJson: _${fnBase}ListToJson)`);
576
+ }
577
+ else {
578
+ this.push1(`@JsonKey(${namePart}fromJson: _${fnBase}FromJson, toJson: _${fnBase}ToJson)`);
579
+ }
580
+ annotationEmitted = true;
581
+ }
582
+ }
583
+ if (!annotationEmitted && fieldName !== key) {
584
+ // @JsonKey is needed when the Dart field name differs from the original JSON key
585
+ this.push1(`@JsonKey(name: '${key}')`);
586
+ }
587
+ const typeStr = isOpt ? `${varType}?` : varType;
588
+ this.push1(`final ${typeStr} ${fieldName};`);
589
+ }
590
+ /**
591
+ * Emits `fromJson` factory and `toJson` method delegation stubs.
592
+ * For generic classes, extra `fromJsonT` / `toJsonT` function parameters are added
593
+ * as required by `json_serializable` with `genericArgumentFactories: true`.
594
+ */
595
+ _emitFromToJson(data) {
596
+ const className = data.name;
597
+ const templates = [...data.templates];
598
+ const isGeneric = templates.length > 0;
599
+ if (isGeneric) {
600
+ const fromParams = templates
601
+ .map((t) => `${t} Function(Object? json) fromJson${t}`)
602
+ .join(", ");
603
+ const toParams = templates
604
+ .map((t) => `Object? Function(${t} value) toJson${t}`)
605
+ .join(", ");
606
+ const fromArgs = templates.map((t) => `fromJson${t}`).join(", ");
607
+ const toArgs = templates.map((t) => `toJson${t}`).join(", ");
608
+ this.push0("");
609
+ this.push1(`factory ${className}.fromJson(Map<String, dynamic> json, ${fromParams}) =>`);
610
+ this.push2(`_\$${className}FromJson(json, ${fromArgs});`);
611
+ this.push0("");
612
+ this.push1(`Map<String, dynamic> toJson(${toParams}) =>`);
613
+ this.push2(`_\$${className}ToJson(this, ${toArgs});`);
614
+ }
615
+ else {
616
+ this.push0("");
617
+ this.push1(`factory ${className}.fromJson(Map<String, dynamic> json) =>`);
618
+ this.push2(`_\$${className}FromJson(json);`);
619
+ this.push0("");
620
+ this.push1(`Map<String, dynamic> toJson() => _\$${className}ToJson(this);`);
621
+ }
622
+ }
623
+ }
624
+ exports.Zod2Dart = Zod2Dart;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Go standard library and common package import strings.
3
+ */
4
+ export declare function getLibs(): {
5
+ timePackage: string;
6
+ jsonPackage: string;
7
+ fmtPackage: string;
8
+ };
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getLibs = getLibs;
4
+ /**
5
+ * Go standard library and common package import strings.
6
+ */
7
+ function getLibs() {
8
+ return {
9
+ timePackage: `"time"`,
10
+ jsonPackage: `"encoding/json"`,
11
+ fmtPackage: `"fmt"`,
12
+ };
13
+ }
@@ -0,0 +1,17 @@
1
+ import { IZodToXOpt } from "../../core";
2
+ export interface IZod2GoOpt extends IZodToXOpt {
3
+ /**
4
+ * The Go package name for the generated file. Default: "models"
5
+ */
6
+ packageName?: string;
7
+ /**
8
+ * By default (false), struct field names are exported (PascalCase) and JSON tags use the
9
+ * original property name. If set to true, original property names are preserved as-is.
10
+ */
11
+ keepKeys?: boolean;
12
+ /**
13
+ * Whether to emit `json:"fieldName"` struct tags. Default: true
14
+ */
15
+ useJsonTags?: boolean;
16
+ }
17
+ export declare const defaultOpts: IZod2GoOpt;