zod-to-x 1.7.0 → 1.8.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.
- package/README.md +10 -0
- package/dist/layered-modeling/model.d.ts +1 -0
- package/dist/layered-modeling/model.js +1 -1
- package/dist/transpilers/dart/libs.d.ts +6 -0
- package/dist/transpilers/dart/libs.js +11 -0
- package/dist/transpilers/dart/options.d.ts +21 -0
- package/dist/transpilers/dart/options.js +10 -0
- package/dist/transpilers/dart/runner.d.ts +149 -0
- package/dist/transpilers/dart/runner.js +624 -0
- package/dist/transpilers/index.d.ts +1 -0
- package/dist/transpilers/index.js +3 -1
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
<img src="https://img.shields.io/badge/Python-3776AB?style=for-the-badge&logo=python&logoColor=white" alt="Python">
|
|
28
28
|
<img src="https://img.shields.io/badge/C++-00599C?style=for-the-badge&logo=cplusplus&logoColor=white" alt="C++">
|
|
29
29
|
<img src="https://img.shields.io/badge/Go-00ADD8?style=for-the-badge&logo=go&logoColor=white" alt="Go">
|
|
30
|
+
<img src="https://img.shields.io/badge/Dart-0175C2?style=for-the-badge&logo=dart&logoColor=white" alt="Dart">
|
|
30
31
|
<img src="https://img.shields.io/badge/Protobuf-4285F4?style=for-the-badge&logo=google&logoColor=white" alt="Protobuf">
|
|
31
32
|
</p>
|
|
32
33
|
|
|
@@ -52,6 +53,7 @@
|
|
|
52
53
|
- [2) Python](#2-python)
|
|
53
54
|
- [3) C++](#3-c)
|
|
54
55
|
- [4) Go](#4-go)
|
|
56
|
+
- [5) Dart](#5-dart)
|
|
55
57
|
- [Additional utils](#additional-utils)
|
|
56
58
|
- [1) `zod2JsonSchemaDefinitions`](#1-zod2jsonschemadefinitions)
|
|
57
59
|
- [2) `zod2ProtoV3`](#2-zod2protov3)
|
|
@@ -661,6 +663,14 @@ Generates idiomatic Go structs with `encoding/json` struct tags. Requires Go 1.1
|
|
|
661
663
|
- **useJsonTags**: Whether to emit `json:"fieldName"` struct tags. Defaults to `true`.
|
|
662
664
|
- [Examples](https://github.com/rroumenov/zod-to-x/blob/main/test/test_zod2go)
|
|
663
665
|
|
|
666
|
+
### 5) Dart
|
|
667
|
+
`json_annotation` + `json_serializable` are used for JSON serialization/deserialization. The `.g.dart` part file is generated externally by `dart run build_runner build`.
|
|
668
|
+
- Options:
|
|
669
|
+
- **keepKeys**: Specifies whether property names should follow the Dart naming convention (false) or remain as originally defined (true). The default is `false`.
|
|
670
|
+
- **useJsonSerialization**: When true (default), emits `@JsonSerializable()`, `@JsonEnum`, `@JsonKey` annotations and `fromJson`/`toJson` stubs. Requires `package:json_annotation`.
|
|
671
|
+
- **partFile**: Filename (without extension) for the `part '${partFile}.g.dart'` directive. Automatically set when using layered modeling. If omitted, no `part` directive is emitted.
|
|
672
|
+
- [Examples](https://github.com/rroumenov/zod-to-x/blob/main/test/test_zod2dart)
|
|
673
|
+
|
|
664
674
|
|
|
665
675
|
|
|
666
676
|
## Additional utils
|
|
@@ -45,5 +45,6 @@ export declare class Zod2XModel {
|
|
|
45
45
|
transpile(target: Target<"Zod2Ts">, opt?: TargetOpt<"Zod2Ts">, astOpt?: AstOpt): string;
|
|
46
46
|
transpile(target: Target<"Zod2Py">, opt?: TargetOpt<"Zod2Py">, astOpt?: AstOpt): string;
|
|
47
47
|
transpile(target: Target<"Zod2Go">, opt?: TargetOpt<"Zod2Go">, astOpt?: AstOpt): string;
|
|
48
|
+
transpile(target: Target<"Zod2Dart">, opt?: TargetOpt<"Zod2Dart">, astOpt?: AstOpt): string;
|
|
48
49
|
}
|
|
49
50
|
export {};
|
|
@@ -59,7 +59,7 @@ class Zod2XModel {
|
|
|
59
59
|
return this.astNodes;
|
|
60
60
|
}
|
|
61
61
|
transpile(target, opt = {}, astOpt) {
|
|
62
|
-
const transpilerInstance = new target(Object.assign(Object.assign({}, opt), { namespace: this.getLayerMetadata().namespace }));
|
|
62
|
+
const transpilerInstance = new target(Object.assign(Object.assign({}, opt), { namespace: this.getLayerMetadata().namespace, partFile: this.getLayerMetadata().file }));
|
|
63
63
|
return transpilerInstance.transpile(this.getAstNode(astOpt));
|
|
64
64
|
}
|
|
65
65
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getLibs = getLibs;
|
|
4
|
+
/**
|
|
5
|
+
* Dart package imports for json_annotation / json_serializable.
|
|
6
|
+
*/
|
|
7
|
+
function getLibs() {
|
|
8
|
+
return {
|
|
9
|
+
jsonAnnotation: "import 'package:json_annotation/json_annotation.dart';",
|
|
10
|
+
};
|
|
11
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { IZodToXOpt } from "../../core";
|
|
2
|
+
export interface IZod2DartOpt extends IZodToXOpt {
|
|
3
|
+
/**
|
|
4
|
+
* By default (false), class property names are converted to camelCase following Dart
|
|
5
|
+
* conventions. If set to true, the original property names are preserved as-is.
|
|
6
|
+
*/
|
|
7
|
+
keepKeys?: boolean;
|
|
8
|
+
/**
|
|
9
|
+
* When true (default), emits `@JsonSerializable()` annotations, `@JsonEnum`, `@JsonKey`,
|
|
10
|
+
* and `fromJson`/`toJson` delegation stubs. Requires `package:json_annotation`.
|
|
11
|
+
* The `.g.dart` part file is generated externally by `dart run build_runner build`.
|
|
12
|
+
*/
|
|
13
|
+
useJsonSerialization?: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Filename (without extension) used for the `part '${partFile}.g.dart';` directive.
|
|
16
|
+
* When using layered modeling, this is automatically set to the layer's `file` metadata.
|
|
17
|
+
* If omitted, no `part` directive is emitted.
|
|
18
|
+
*/
|
|
19
|
+
partFile?: string;
|
|
20
|
+
}
|
|
21
|
+
export declare const defaultOpts: IZod2DartOpt;
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { ASTAliasedTypes, ASTEnum, ASTIntersection, ASTNode, ASTNodes, ASTObject, ASTUnion, Zod2X } from "../../core";
|
|
2
|
+
import { IZod2DartOpt } from "./options";
|
|
3
|
+
/**
|
|
4
|
+
* Transpiler for Zod schemas to Dart classes using package:json_annotation.
|
|
5
|
+
*
|
|
6
|
+
* The `.g.dart` part file is NOT generated by this transpiler — it must be produced
|
|
7
|
+
* externally via `dart run build_runner build`.
|
|
8
|
+
*/
|
|
9
|
+
export declare class Zod2Dart extends Zod2X<IZod2DartOpt> {
|
|
10
|
+
protected readonly commentKey = "//";
|
|
11
|
+
protected lib: {
|
|
12
|
+
jsonAnnotation: string;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Maps a local struct/class name to the discriminated union names it participates in.
|
|
16
|
+
* Populated by a pre-scan of the AST before the normal transpile pass.
|
|
17
|
+
*/
|
|
18
|
+
private _unionMembers;
|
|
19
|
+
/**
|
|
20
|
+
* Names of locally-defined discriminated union abstract classes.
|
|
21
|
+
* Used to emit @JsonKey(fromJson/toJson) when such a type appears as a field.
|
|
22
|
+
*/
|
|
23
|
+
private _discriminatedUnionTypes;
|
|
24
|
+
/**
|
|
25
|
+
* Names of typedef-emitted types (alias, union typedef, struct extends).
|
|
26
|
+
* When these appear as field types in @JsonSerializable classes, we emit
|
|
27
|
+
* @JsonKey(fromJson: TypeName.fromJson) to prevent json_serializable from
|
|
28
|
+
* following the typedef chain and referencing transitive imports.
|
|
29
|
+
*/
|
|
30
|
+
private _typedefNames;
|
|
31
|
+
/**
|
|
32
|
+
* Maps a disc union typedef name (e.g. "NewDiscriminatedUnionItem") to the
|
|
33
|
+
* fromJson function name of the underlying entity-level union
|
|
34
|
+
* (e.g. "discriminatedUnionItemFromJson").
|
|
35
|
+
* Populated during pre-scan when an ASTUnion node has aliasOf set.
|
|
36
|
+
*/
|
|
37
|
+
private _discriminatedUnionDelegateFromJson;
|
|
38
|
+
/**
|
|
39
|
+
* Unified set of type names needing custom @JsonKey annotations.
|
|
40
|
+
* - Discriminated union types: fromJson = <camelName>FromJson (local function)
|
|
41
|
+
* - Typedef types: fromJson = _<camelName>FromJson (standalone wrapper)
|
|
42
|
+
* - TypedefList: list fromJson/toJson helpers
|
|
43
|
+
* _toJson helpers are emitted in runAfter() for all entries.
|
|
44
|
+
*/
|
|
45
|
+
private _usedCustomFromJsonTypes;
|
|
46
|
+
/**
|
|
47
|
+
* Maps a type name (e.g. "GenericUserEntity") to its full Dart type with
|
|
48
|
+
* template args (e.g. "GenericUserEntity<SomeDtoResult>"). Used in runAfter()
|
|
49
|
+
* to generate helpers with correct generic return types and factory calls.
|
|
50
|
+
*/
|
|
51
|
+
private _usedCustomFromJsonFullTypes;
|
|
52
|
+
/**
|
|
53
|
+
* Maps a typedef name to its full extended type (the right-hand side of the
|
|
54
|
+
* typedef). E.g. "AdminUserEntity" → "GenericUserEntity<AdminUserMetadata>".
|
|
55
|
+
* Populated in addExtendedType() when the extended type has generic args.
|
|
56
|
+
* Used in runAfter() to generate 2-arg fromJson calls for generic classes.
|
|
57
|
+
*/
|
|
58
|
+
private _typedefToExtendedType;
|
|
59
|
+
constructor(opt?: IZod2DartOpt);
|
|
60
|
+
/**
|
|
61
|
+
* Overrides transpile() to pre-scan discriminated union memberships before the normal pass.
|
|
62
|
+
* This allows transpileStruct() to emit `implements UnionName` on member classes.
|
|
63
|
+
*/
|
|
64
|
+
transpile(transpilerQueue: ASTNodes): string;
|
|
65
|
+
protected runBefore(): void;
|
|
66
|
+
protected runAfter(): void;
|
|
67
|
+
protected addImportFromFile(filename: string, _namespace: string): string;
|
|
68
|
+
protected getTypeFromExternalNamespace(_namespace: string, typeName: string): string;
|
|
69
|
+
protected getGenericTemplatesTranslation(data: ASTNode): string | undefined;
|
|
70
|
+
/**
|
|
71
|
+
* Emits an alias/extension declaration for cross-layer references.
|
|
72
|
+
*/
|
|
73
|
+
protected checkExtendedTypeInclusion(data: ASTNode, type?: "union" | "alias"): boolean;
|
|
74
|
+
protected addExtendedType(name: string, parentNamespace: string, aliasOf: string, opt?: {
|
|
75
|
+
type?: "union" | "alias";
|
|
76
|
+
isInternal?: boolean;
|
|
77
|
+
templates?: string;
|
|
78
|
+
declaredTemplates?: string;
|
|
79
|
+
}): void;
|
|
80
|
+
protected getStringType: () => string;
|
|
81
|
+
protected getBooleanType: () => string;
|
|
82
|
+
protected getAnyType: () => string;
|
|
83
|
+
protected getDateType: () => string;
|
|
84
|
+
protected getNumberType(isInt: boolean, _range: {
|
|
85
|
+
min?: number;
|
|
86
|
+
max?: number;
|
|
87
|
+
}): string;
|
|
88
|
+
/**
|
|
89
|
+
* Returns the Dart type for a literal value.
|
|
90
|
+
* Dart has no structural literal types, so we return the underlying primitive type.
|
|
91
|
+
* When a parent enum is provided, returns the enum type name (not a member expression),
|
|
92
|
+
* since Dart enum members cannot be used as types.
|
|
93
|
+
*/
|
|
94
|
+
protected getLiteralStringType(value: string | number | boolean, parentEnumNameKey?: [string, string]): string | number;
|
|
95
|
+
/** Ex: List<List<T>> for 2D array */
|
|
96
|
+
protected getArrayType(arrayType: string, arrayDeep: number): string;
|
|
97
|
+
/** Ex: Set<T> */
|
|
98
|
+
protected getSetType: (itemType: string) => string;
|
|
99
|
+
/** Ex: Map<K, V> */
|
|
100
|
+
protected getMapType: (keyType: string, valueType: string) => string;
|
|
101
|
+
/** Ex: Map<String, V> (record key is always String) */
|
|
102
|
+
protected getRecordType: (keyType: string, valueType: string) => string;
|
|
103
|
+
/** Ex: (T1, T2, T3) — Dart 3 positional record type */
|
|
104
|
+
protected getTupleType: (itemsType: string[]) => string;
|
|
105
|
+
/** Dart has no native union type; use Object */
|
|
106
|
+
protected getUnionType: (_itemsType: string[]) => string;
|
|
107
|
+
/** Dart has no native intersection type; use Object */
|
|
108
|
+
protected getIntersectionType: (_itemsType: string[]) => string;
|
|
109
|
+
protected transpileAliasedType(data: ASTAliasedTypes): void;
|
|
110
|
+
/**
|
|
111
|
+
* Emits a Dart enhanced enum with a typed `value` field and `@JsonEnum` annotation.
|
|
112
|
+
*
|
|
113
|
+
* All-string values:
|
|
114
|
+
* @JsonEnum(valueField: 'value')
|
|
115
|
+
* enum EnumItem {
|
|
116
|
+
* enum1('Enum1'),
|
|
117
|
+
* enum2('Enum2');
|
|
118
|
+
*
|
|
119
|
+
* const EnumItem(this.value);
|
|
120
|
+
* final String value;
|
|
121
|
+
* }
|
|
122
|
+
*
|
|
123
|
+
* Mixed (int + string): typedef alias with comment.
|
|
124
|
+
*/
|
|
125
|
+
protected transpileEnum(data: ASTEnum): void;
|
|
126
|
+
protected transpileStruct(data: ASTObject): void;
|
|
127
|
+
/**
|
|
128
|
+
* Dart intersection:
|
|
129
|
+
* - If the AST produced a merged `newObject`, emit it as a flat class.
|
|
130
|
+
* - Otherwise emit a typedef alias to Object with a comment.
|
|
131
|
+
*/
|
|
132
|
+
protected transpileIntersection(data: ASTIntersection): void;
|
|
133
|
+
/**
|
|
134
|
+
* Dart union:
|
|
135
|
+
* - Non-discriminated: typedef alias to Object with a comment listing member types.
|
|
136
|
+
* - Discriminated: abstract base class + factory unmarshal function using Dart 3 switch.
|
|
137
|
+
*/
|
|
138
|
+
protected transpileUnion(data: ASTUnion): void;
|
|
139
|
+
/** Renders the full body of a Dart class for an ASTObject. */
|
|
140
|
+
private _transpileStructBody;
|
|
141
|
+
/** Renders a single class field declaration with optional `@JsonKey` annotation. */
|
|
142
|
+
private _transpileMember;
|
|
143
|
+
/**
|
|
144
|
+
* Emits `fromJson` factory and `toJson` method delegation stubs.
|
|
145
|
+
* For generic classes, extra `fromJsonT` / `toJsonT` function parameters are added
|
|
146
|
+
* as required by `json_serializable` with `genericArgumentFactories: true`.
|
|
147
|
+
*/
|
|
148
|
+
private _emitFromToJson;
|
|
149
|
+
}
|
|
@@ -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;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.Zod2Go = exports.Zod2Py = exports.Zod2Cpp17 = exports.Zod2Cpp = exports.Zod2Ts = void 0;
|
|
3
|
+
exports.Zod2Dart = exports.Zod2Go = exports.Zod2Py = exports.Zod2Cpp17 = exports.Zod2Cpp = exports.Zod2Ts = void 0;
|
|
4
4
|
var runner_1 = require("./typescript/runner");
|
|
5
5
|
Object.defineProperty(exports, "Zod2Ts", { enumerable: true, get: function () { return runner_1.Zod2Ts; } });
|
|
6
6
|
var runner_2 = require("./cpp/runner");
|
|
@@ -10,3 +10,5 @@ var runner_3 = require("./python/runner");
|
|
|
10
10
|
Object.defineProperty(exports, "Zod2Py", { enumerable: true, get: function () { return runner_3.Zod2Py; } });
|
|
11
11
|
var runner_4 = require("./go/runner");
|
|
12
12
|
Object.defineProperty(exports, "Zod2Go", { enumerable: true, get: function () { return runner_4.Zod2Go; } });
|
|
13
|
+
var runner_5 = require("./dart/runner");
|
|
14
|
+
Object.defineProperty(exports, "Zod2Dart", { enumerable: true, get: function () { return runner_5.Zod2Dart; } });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zod-to-x",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"description": "Multi language types generation from Zod schemas.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"files": [
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
"test:cpp": "bash ./test/test_zod2cpp/test_cpp.sh",
|
|
17
17
|
"test:py": "bash ./test/test_zod2py/test_py.sh",
|
|
18
18
|
"test:go": "bash ./test/test_zod2go/test_go.sh",
|
|
19
|
-
"test:
|
|
19
|
+
"test:dart": "bash ./test/test_zod2dart/test_dart.sh",
|
|
20
|
+
"test:all": "npm run test:cpp && npm run test:py && npm run test:go && npm run test:dart",
|
|
20
21
|
"ts-run": "ts-node -r tsconfig-paths/register",
|
|
21
22
|
"prepare": "husky"
|
|
22
23
|
},
|
|
@@ -61,7 +62,7 @@
|
|
|
61
62
|
"husky": "^9.1.7",
|
|
62
63
|
"lint-staged": "^15.2.11",
|
|
63
64
|
"prettier": "^3.4.2",
|
|
64
|
-
"protobufjs": "7.
|
|
65
|
+
"protobufjs": "7.6.5",
|
|
65
66
|
"ts-node": "^10.9.2",
|
|
66
67
|
"tsc-alias": "^1.8.10",
|
|
67
68
|
"tsconfig-paths": "4.2.0",
|