fpjson.schema.pascaltypes.pp 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185
  1. {
  2. This file is part of the Free Component Library
  3. Copyright (c) 2024 by Michael Van Canneyt [email protected]
  4. JSON Schema - pascal types and helpers
  5. See the file COPYING.FPC, included in this distribution,
  6. for details about the copyright.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  10. **********************************************************************}
  11. unit fpjson.schema.pascaltypes;
  12. {$mode ObjFPC}{$H+}
  13. interface
  14. uses
  15. {$IFDEF FPC_DOTTEDUNITS}
  16. System.Classes, System.SysUtils, System.Contnrs, System.StrUtils,
  17. {$ELSE}
  18. Classes, SysUtils, contnrs, StrUtils,
  19. {$ENDIF}
  20. fpjson.schema.types,
  21. fpjson.schema.schema;
  22. Type
  23. ESchemaData = Class(EJSONSchema);
  24. TPascalTypeData = Class;
  25. TSchemaCodeGenLogEvent = Procedure (aType : TEventType; const Msg : String) of object;
  26. TDependencyType = (dtNone,dtDirect,dtIndirect);
  27. TDependencyTypes = set of TDependencyType;
  28. TNameType = (ntSchema,ntPascal,ntInterface,ntImplementation,ntSerializer);
  29. TNameTypes = set of TNameType;
  30. TSerializeType = (stSerialize,stDeserialize);
  31. TSerializeTypes = set of TSerializeType;
  32. TPascalType = (ptUnknown,
  33. ptBoolean, // Boolean
  34. ptInteger, // 32-bit integer
  35. ptInt64, // 64-bit integer
  36. ptDateTime, // TDateTime
  37. ptFloat32, // Single
  38. ptFloat64, // Double
  39. ptString, // String
  40. ptEnum, // Enumerated
  41. ptJSON, // TJSONData (empty schema object)
  42. ptAnonStruct, // Anonymous Class/Record (schema object with properties)
  43. ptSchemaStruct, // Named Class/Record
  44. ptArray // Array of...
  45. );
  46. TPascalTypes = Set of TPascalType;
  47. // Aliases
  48. TPropertyType = TPascalType;
  49. TPropertyTypes = TPascalTypes;
  50. { TPascalProperty }
  51. TPascalPropertyData = class(TObject)
  52. private
  53. FSchemaName: string;
  54. FElementType: TPropertyType;
  55. FEnumValues: TStrings;
  56. FPascalName: string;
  57. FSchema: TJSONSchema;
  58. FTypeNames: Array[TNameType] of string ;
  59. FElementTypeNames: Array[TNameType] of string ;
  60. FPropertyType: TPropertyType;
  61. FTypeData: TPascalTypeData;
  62. function GetElementTypeNames(aType : TNameType): String;
  63. function GetFallBackTypeName(aPropertyType: TPropertyType): string;
  64. function GetPascalTypeName: String;
  65. procedure SetElementTypeNames(aType : TNameType; AValue: String);
  66. procedure SetEnumValues(AValue: TStrings);
  67. Function GetTypeName(aType: TNameType) : String;
  68. procedure SetTypeName(aType: TNameType; aValue : String);
  69. Public
  70. Constructor Create(const aSchemaName, aPascalName : string);
  71. Destructor Destroy; override;
  72. // schema Name of the property
  73. Property SchemaName : string Read FSchemaName Write FSchemaName;
  74. // Pascal Name of the property
  75. Property PascalName : string Read FPascalName Write FPascalName;
  76. // Indexed access to all kind of type names
  77. Property TypeNames [aType : TNameType] : String Read GetTypeName Write SetTypeName;
  78. // Type of the property
  79. Property PropertyType : TPropertyType Read FPropertyType Write FPropertyType;
  80. // If Type is ptEnum, the values. Without _empty_.
  81. Property EnumValues : TStrings Read FEnumValues Write SetEnumValues;
  82. // Pascal type name for the property (same as TypeNames[ntPascal])
  83. Property PascalTypeName : String Index ntPascal Read GetTypeName Write SetTypeName;
  84. // PropertyType = ptArray : The array element type
  85. Property ElementType : TPropertyType Read FElementType Write FElementType;
  86. // PropertyType = ptArray : The array element type name (same as ElementTypeNames[ntPascal])
  87. Property ElementTypeName : String Index ntPascal Read GetElementTypeNames Write SetElementTypeNames;
  88. // PropertyType = ptArray : The array element type names
  89. Property ElementTypeNames[aType : TNameType] : String Read GetElementTypeNames Write SetElementTypeNames;
  90. // PropertyType = ptSchemaStruct: The type data for that component.
  91. // PropertyType = ptArray and elType=ptSchemaStruct
  92. Property TypeData : TPascalTypeData Read FTypeData Write FTypeData;
  93. // The JSON Schema for this property
  94. Property Schema : TJSONSchema Read FSchema Write FSchema;
  95. end;
  96. { TPascalTypeData }
  97. TPascalTypeData = class(TObject)
  98. private
  99. FElementTypeData: TPascalTypeData;
  100. FSchemaName: String;
  101. FImplementationName: String;
  102. FIndex: Integer;
  103. FInterfaceName: String;
  104. FInterfaceUUID: String;
  105. FPascalName: String;
  106. FSchema: TJSONSChema;
  107. FDependencies : TFPObjectList;
  108. FSerializerName: String;
  109. FSerializeTypes: TSerializeTypes;
  110. FSorted : Boolean;
  111. FProperties : TFPObjectList;
  112. FType: TPascalType;
  113. function GetDependency(aIndex : Integer): TPascalTypeData;
  114. function GetDependencyCount: Integer;
  115. function GetImplementationName: String;
  116. function GetInterfaceName: String;
  117. function GetInterfaceUUID: String;
  118. function GetProperty(aIndex : Integer): TPascalPropertyData;
  119. function GetPropertyCount: Integer;
  120. function GetSerializerName: String;
  121. Protected
  122. function CreateProperty(const aSchemaName, aPascalName: string): TPascalPropertyData; virtual;
  123. Public
  124. class function ExtractFirstType(aSchema: TJSONSchema): TSchemaSimpleType;
  125. Public
  126. Constructor Create(aIndex : integer; aType : TPascalType; const aSchemaName,aPascalName : String; aSchema : TJSONSchema);
  127. destructor Destroy; override;
  128. // Sort the properties.
  129. Procedure SortProperties;
  130. // Index of property using schema name
  131. Function IndexOfProperty(const aSchemaName: string) : Integer;
  132. // Index of property using Pascal name
  133. Function IndexOfPascalProperty(const aPascalName: string) : Integer;
  134. // Find property by schema name.
  135. Function FindProperty(const aName: string) : TPascalPropertyData;
  136. // Add a property. The pascal name must not yet exist.
  137. Function AddProperty(const aSchemaName,aPascalName : String) : TPascalPropertyData;
  138. // Return the requested name
  139. function GetTypeName(aNameType : TNameType) : string;
  140. // Check whether this component depends on given component. If recurse is true, check all intermediary structures as well.
  141. function DependsOn(aData : TPascalTypeData; Recurse : Boolean) : TDependencyType;
  142. // Add aData as a type this type depends on.
  143. Procedure AddDependency(aData : TPascalTypeData);
  144. // Component has array-typed property ?
  145. Function HasArrayProperty : Boolean;
  146. // Component has object-typed property ? (SchemaComponentsonly = False -> also return array of string etc.)
  147. function HasObjectProperty(aSchemaComponentsOnly: Boolean): Boolean;
  148. // Components his component depends on
  149. Property Dependency[aIndex : Integer] : TPascalTypeData Read GetDependency;
  150. // Number of Components his component depends on
  151. Property DependencyCount : Integer Read GetDependencyCount;
  152. // Indexed access to Properties
  153. Property Properties[aIndex : Integer] : TPascalPropertyData Read GetProperty; default;
  154. // Number of properties
  155. Property PropertyCount : Integer Read GetPropertyCount;
  156. // Pascal type name for DTO (can be influenced by map). Default is schema name with prefix/suffix
  157. Property PascalName : String Read FPascalName;
  158. // Schema name.
  159. Property SchemaName : String Read FSchemaName;
  160. // Interface name. Default Pascal name + 'Intf'
  161. Property InterfaceName : String Read GetInterfaceName Write FInterfaceName;
  162. // Interface UUID.
  163. Property InterfaceUUID : String Read GetInterfaceUUID Write FInterfaceUUID;
  164. // Implemention class name. Default Pascal name + 'Obj'
  165. Property ImplementationName : String Read GetImplementationName write FImplementationName;
  166. // Name of serialized helper. Default is Pascal name + 'Serializer'
  167. Property SerializerName : String Read GetSerializerName Write FSerializerName;
  168. // Do we need to serialize/deserialize ?
  169. Property SerializeTypes : TSerializeTypes Read FSerializeTypes Write FSerializeTypes;
  170. // Schema of this component.
  171. Property Schema: TJSONSChema Read FSchema;
  172. // Was this element sorted ?
  173. Property Sorted : Boolean Read FSorted Write FSorted;
  174. // PascalType
  175. Property Pascaltype : TPascalType Read FType;
  176. // For arrays, a pointer to the element type
  177. Property ElementTypeData : TPascalTypeData Read FElementTypeData Write FElementTypeData;
  178. end;
  179. { TPascalTypeDataList }
  180. TPascalTypeDataList = Class(TFPObjectList)
  181. private
  182. function GetTypes(aIndex : Integer): TPascalTypeData;
  183. Public
  184. Procedure Add(aItem : TPascalTypeData); reintroduce;
  185. Property Types[aIndex : Integer] : TPascalTypeData Read GetTypes; default;
  186. end;
  187. TKeywordEscapeMode = (kemAmpersand,kemSuffix,kemPrefix);
  188. { TSchemaData }
  189. TSchemaData = class(TObject)
  190. private
  191. FKeywordEscapeMode: TKeywordEscapeMode;
  192. FTypeList : TPascalTypeDataList;
  193. FAliasList : TPascalTypeDataList;
  194. FTypeMap : TFPObjectHashTable;
  195. FArrayTypePrefix: string;
  196. FArrayTypeSuffix: string;
  197. FDelphiTypes: Boolean;
  198. FInterfaceTypePrefix: String;
  199. FObjectTypePrefix: string;
  200. FObjectTypeSuffix: string;
  201. FOnLog: TSchemaCodeGenLogEvent;
  202. FUseEnums: Boolean;
  203. function GetSchemaType(aIndex : Integer): TPascalTypeData;
  204. function GetSchemaTypeCount: Integer;
  205. protected
  206. // Logging
  207. procedure DoLog(Const aType : TEventType; const aMessage : String);
  208. procedure DoLog(Const aType : TEventType; const aFmt : String; aArgs : Array of const);
  209. // Override this to finish creating a type.
  210. procedure FinishAutoCreatedType(aName: string; aType: TPascalTypeData; lElementTypeData: TPascalTypeData); virtual;
  211. // Override this to determine the type name of a pascal property
  212. function GetSchemaTypeAndName(aType: TPascalTypeData; aSchema: TJSONSchema; out aPropType: TPascalType; aNameType: TNameType=ntPascal): String; virtual;
  213. // Add a new type to the type map.
  214. procedure AddToTypeMap(const aSchemaName: String; aData : TPascalTypeData); virtual; overload;
  215. // Get pascal type name based on schema name
  216. function SchemaNameToNameType(const aName: string; aNameType: TNameType): string; virtual;
  217. // Take JSONPointer reference and find pascal type data for it.
  218. function GetPascalTypeDataFromRef(const aRef: String): TPascalTypeData; virtual;
  219. // Find schema pascal type data. If AllowCreate is true, type data for Enum,Array and object properties will be created.
  220. function GetSchemaTypeData(aType: TPascalTypeData; lSchema: TJSONSchema; AllowCreate: Boolean=False): TPascalTypeData;
  221. // Add a type to the alias list
  222. Procedure AddAliasType(aType : TPascalTypeData); virtual;
  223. // Sort types in dependency order
  224. procedure SortTypes;
  225. Public
  226. Constructor Create; virtual;
  227. Destructor Destroy; override;
  228. // Create aliases for known simple types
  229. procedure DefineStandardPascalTypes;
  230. // Is the word a pascal keyword ?
  231. class function IsKeyWord(const aWord : String) : Boolean;
  232. // Escape the word if it is a pascal keyword ?
  233. function EscapeKeyWord(const aWord : string) : string;
  234. // Get the pascal name based on schema name
  235. function GetTypeMap(const aName : string): String;
  236. // Return index of named schema type (name as in OpenApi). Return -1 if not found.
  237. function IndexOfSchemaType(const aSchemaName: String): integer;
  238. // Find Pascal type data based on schema type name.
  239. function FindSchemaTypeData(const aSchemaName: String; aFormat : String = ''): TPascalTypeData;
  240. // Extract simple type from schema
  241. Function GetSchemaType(aSchema : TJSONSchema) : TSchemaSimpleType;
  242. // Extract element type from schema
  243. Function GetArrayElementType(aSchema : TJSONSchema) : TSchemaSimpleType;
  244. // Used when creating a new type. Override to create a descendant;
  245. function CreatePascalType(aIndex: integer; aType : TPascalType; const aSchemaName, aPascalName: String; aSchema: TJSONSchema): TPascalTypeData; virtual;
  246. // Add a type to the list
  247. Procedure AddType(const aSchemaName: String; aType : TPascalTypeData); virtual;
  248. // Add a type definition to the type map.
  249. procedure AddAliasToTypeMap(aType: TPascalType; const aAlias, aSchemaTypeName, aPascalTypeName: String; aSchema: TJSONSchema); overload;
  250. // Add a property to a type
  251. function AddTypeProperty(aType: TPascalTypeData; lProp: TJSONSchema; aName : string = ''; Recurse : Boolean = True): TPascalPropertyData;
  252. // Add properties to structured pascal type from aSchema. if aSchema = nil then use aType.Schema
  253. Procedure AddPropertiesToType(aType : TPascalTypeData; aSchema: TJSONSchema = Nil; Recurse : Boolean = True);
  254. // For all types, fill the depency list: contains all structured types on which the type depends (recursively).
  255. procedure CheckDependencies; virtual;
  256. // Number of types
  257. Property TypeCount : Integer Read GetSchemaTypeCount;
  258. // Indexed access to all types.
  259. Property Types[aIndex : Integer] : TPascalTypeData Read GetSchemaType; default;
  260. // Map schema type to pascal type.
  261. Property TypeMap[aSchemaName : string] : String Read GetTypeMap;
  262. // prefix for object definitions. Default T
  263. Property ObjectTypePrefix : string Read FObjectTypePrefix Write FObjectTypePrefix;
  264. // prefix for object definitions. Default empty
  265. Property ObjectTypeSuffix : string Read FObjectTypeSuffix Write FObjectTypeSuffix;
  266. // Prefix for Dto Objects
  267. Property InterfaceTypePrefix : String Read FInterfaceTypePrefix Write FInterfaceTypePrefix;
  268. // Prefix for array types
  269. Property ArrayTypePrefix : string Read FArrayTypePrefix Write FArrayTypePrefix;
  270. // Suffix for array types. Default Array
  271. Property ArrayTypeSuffix : string Read FArrayTypeSuffix Write FArrayTypeSuffix;
  272. // Use delphi types: TArray<X> instead of Array of X
  273. Property DelphiTypes : Boolean Read FDelphiTypes Write FDelphiTypes;
  274. // Use enums for enumerateds (default is to keep them as strings)
  275. Property UseEnums : Boolean Read FUseEnums Write FUseEnums;
  276. // Log callback
  277. Property OnLog : TSchemaCodeGenLogEvent Read FOnLog Write FOnLog;
  278. // how to escape keywords
  279. Property KeywordEscapeMode : TKeywordEscapeMode Read FKeywordEscapeMode Write FKeywordEscapeMode;
  280. end;
  281. implementation
  282. function CompareTypeDataOnName(Item1, Item2: Pointer): Integer;
  283. var
  284. lType1 : TPascalTypeData absolute Item1;
  285. lType2 : TPascalTypeData absolute Item2;
  286. begin
  287. Result:=CompareText(lType1.SchemaName,lType2.SchemaName);
  288. end;
  289. function CompareProperties(Item1, Item2: Pointer): Integer;
  290. var
  291. lParam1 : TPascalPropertyData absolute Item1;
  292. lParam2 : TPascalPropertyData absolute Item2;
  293. begin
  294. Result:=CompareText(lParam1.PascalName,lParam2.PascalName);
  295. end;
  296. { TPascalPropertyData }
  297. procedure TPascalPropertyData.SetEnumValues(AValue: TStrings);
  298. begin
  299. if FEnumValues=AValue then Exit;
  300. FEnumValues.Assign(AValue);
  301. end;
  302. function TPascalPropertyData.GetPascalTypeName: String;
  303. begin
  304. Result:=GetTypeName(ntPascal);
  305. end;
  306. function TPascalPropertyData.GetElementTypeNames(aType : TNameType): String;
  307. begin
  308. Result:=FElementTypeNames[aType];
  309. if Result<>'' then
  310. exit;
  311. if (PropertyType=ptArray) then
  312. begin
  313. if (ElementType=ptSchemaStruct) then
  314. Exit(TypeData.GetTypeName(aType));
  315. Result:=GetFallBackTypeName(ElementType);
  316. end;
  317. end;
  318. procedure TPascalPropertyData.SetElementTypeNames(aType : TNameType; AValue: String);
  319. begin
  320. FElementTypeNames[aType]:=aValue;
  321. end;
  322. constructor TPascalPropertyData.Create(const aSchemaName, aPascalName: string);
  323. begin
  324. FSchemaName:=aSchemaName;
  325. FPascalName:=aPascalName;
  326. FEnumValues:=TStringList.Create;
  327. end;
  328. destructor TPascalPropertyData.Destroy;
  329. begin
  330. FreeAndNil(FEnumValues);
  331. inherited Destroy;
  332. end;
  333. function TPascalPropertyData.GetTypeName(aType: TNameType): String;
  334. begin
  335. Result:=FTypeNames[aType];
  336. if Result<>'' then
  337. exit;
  338. if Assigned(FTypeData) then
  339. Exit(FTypeData.GetTypeName(aType));
  340. // Fallback
  341. Result:=GetFallBackTypeName(FPropertyType);
  342. end;
  343. function TPascalPropertyData.GetFallBackTypeName(aPropertyType: TPropertyType): string;
  344. begin
  345. Case aPropertyType of
  346. ptUnknown : Raise ESchemaData.CreateFmt('Unknown property type for property "%s"',[PascalName]);
  347. ptBoolean : Result:='boolean';
  348. ptInteger : Result:='integer';
  349. ptInt64 : Result:='Int64';
  350. ptDateTime : Result:='TDateTime';
  351. ptFloat32 : Result:='single';
  352. ptFloat64 : Result:='double';
  353. ptString : Result:='string';
  354. ptEnum : Raise ESchemaData.CreateFmt('Unknown name for enumerated property "%s"',[PascalName]);
  355. ptJSON : Result := 'string';
  356. ptAnonStruct : Raise ESchemaData.CreateFmt('Unknown name for structured property "%s"',[PascalName]);
  357. ptSchemaStruct : Raise ESchemaData.CreateFmt('Unknown name for schema-typed property "%s"',[PascalName]);
  358. end;
  359. end;
  360. procedure TPascalPropertyData.SetTypeName(aType: TNameType; aValue: String);
  361. begin
  362. FTypeNames[aType]:=aValue;
  363. end;
  364. function TPascalTypeData.GetDependencyCount: Integer;
  365. begin
  366. Result:=0;
  367. if Assigned(FDependencies) then
  368. Result:=FDependencies.Count;
  369. end;
  370. function TPascalTypeData.GetImplementationName: String;
  371. begin
  372. Result:=FImplementationName;
  373. if Result='' then
  374. begin
  375. Result:='T'+StringReplace(SchemaName,'Dto','',[rfIgnoreCase]);
  376. Result:=Result+'Obj';
  377. end;
  378. end;
  379. function TPascalTypeData.GetInterfaceName: String;
  380. begin
  381. Result:=FInterfaceName;
  382. if Result='' then
  383. Result:='I'+SchemaName;
  384. end;
  385. function TPascalTypeData.GetInterfaceUUID: String;
  386. begin
  387. if FInterfaceUUID='' then
  388. FInterfaceUUID:=TGUID.NewGuid.ToString(False);
  389. Result:=FInterfaceUUID;
  390. end;
  391. function TPascalTypeData.GetProperty(aIndex : Integer): TPascalPropertyData;
  392. begin
  393. Result:=TPascalPropertyData(FProperties[aIndex]);
  394. end;
  395. function TPascalTypeData.GetPropertyCount: Integer;
  396. begin
  397. Result:=FProperties.Count;
  398. end;
  399. function TPascalTypeData.GetSerializerName: String;
  400. begin
  401. Result:=FSerializerName;
  402. If Result='' then
  403. Result:=PascalName+'Serializer';
  404. end;
  405. function TPascalTypeData.CreateProperty(const aSchemaName,aPascalName: string): TPascalPropertyData;
  406. begin
  407. Result:=TPascalPropertyData.Create(aSchemaName,aPascalName);
  408. end;
  409. procedure TPascalTypeData.SortProperties;
  410. begin
  411. FProperties.Sort(@CompareProperties);
  412. end;
  413. function TPascalTypeData.GetDependency(aIndex : Integer): TPascalTypeData;
  414. begin
  415. if Assigned(FDependencies) then
  416. Result:=TPascalTypeData(FDependencies[aIndex])
  417. else
  418. Raise EListError.CreateFmt('List index out of bounds: %d',[aIndex]);
  419. end;
  420. constructor TPascalTypeData.Create(aIndex: integer; aType: TPascalType; const aSchemaName, aPascalName: String; aSchema: TJSONSchema
  421. );
  422. begin
  423. FIndex:=aIndex;
  424. FSchema:=ASchema;
  425. FSchemaName:=aSchemaName;
  426. FPascalName:=aPascalName;
  427. FSerializeTypes:=[stSerialize,stDeserialize];
  428. FProperties:=TFPObjectList.Create(True);
  429. FType:=aType;
  430. end;
  431. destructor TPascalTypeData.Destroy;
  432. begin
  433. FreeAndNil(FProperties);
  434. FreeAndNil(FDependencies);
  435. Inherited;
  436. end;
  437. function TPascalTypeData.IndexOfProperty(const aSchemaName: string): Integer;
  438. begin
  439. Result:=FProperties.Count-1;
  440. While (Result>=0) and Not SameText(GetProperty(Result).SchemaName,aSchemaName) do
  441. Dec(Result);
  442. end;
  443. function TPascalTypeData.IndexOfPascalProperty(const aPascalName: string): Integer;
  444. begin
  445. Result:=FProperties.Count-1;
  446. While (Result>=0) and Not SameText(GetProperty(Result).PascalName,aPascalName) do
  447. Dec(Result);
  448. end;
  449. function TPascalTypeData.FindProperty(const aName: string): TPascalPropertyData;
  450. var
  451. Idx : Integer;
  452. begin
  453. Idx:=IndexOfProperty(aName);
  454. If Idx=-1 then
  455. Result:=Nil
  456. else
  457. Result:=GetProperty(Idx);
  458. end;
  459. function TPascalTypeData.AddProperty(const aSchemaName, aPascalName: String): TPascalPropertyData;
  460. begin
  461. if IndexOfPascalProperty(aPascalName)<>-1 then
  462. Raise ESchemaData.CreateFmt('Duplicate property name : %s',[aPascalName]);
  463. Result:=CreateProperty(aSchemaName,aPascalName);
  464. FProperties.Add(Result);
  465. end;
  466. function TPascalTypeData.GetTypeName(aNameType: TNameType): string;
  467. begin
  468. Case aNameType of
  469. ntSchema: Result:=SchemaName;
  470. ntPascal: Result:=PascalName;
  471. ntInterface : Result:=InterfaceName;
  472. ntImplementation : Result:=ImplementationName;
  473. ntSerializer : Result:=SerializerName
  474. end;
  475. end;
  476. function TPascalTypeData.DependsOn(aData: TPascalTypeData; Recurse: Boolean): TDependencyType;
  477. var
  478. I : Integer;
  479. begin
  480. Result:=dtNone;
  481. if Not Assigned(FDependencies) then
  482. exit;
  483. For I:=0 to DependencyCount-1 do
  484. if (Dependency[i]=aData) then
  485. exit(dtDirect);
  486. if not Recurse then
  487. exit;
  488. For I:=0 to DependencyCount-1 do
  489. if (Dependency[i].DependsOn(aData,True)<>dtNone) then
  490. Exit(dtIndirect);
  491. end;
  492. procedure TPascalTypeData.AddDependency(aData: TPascalTypeData);
  493. begin
  494. if FDependencies=Nil then
  495. FDependencies:=TFPObjectList.Create(False);
  496. FDependencies.Add(aData);
  497. end;
  498. procedure TSchemaData.CheckDependencies;
  499. procedure CheckProps(lTop,aData : TPascalTypeData);
  500. var
  501. lPropData : TPascalTypeData;
  502. I : Integer;
  503. begin
  504. For I:=0 to aData.PropertyCount-1 do
  505. begin
  506. lPropData:=aData.Properties[I].TypeData;
  507. if Assigned(lPropData) then
  508. begin
  509. Case lPropData.Pascaltype of
  510. ptAnonStruct,ptSchemaStruct:
  511. begin
  512. lTop.AddDependency(lPropData);
  513. CheckProps(lTop,lPropData);
  514. end;
  515. ptArray:
  516. begin
  517. lPropData:=lPropData.ElementTypeData;
  518. if assigned(lPropData) and (lPropData.PascalType in [ptAnonStruct,ptSchemaStruct]) then
  519. begin
  520. lTop.AddDependency(lPropData);
  521. CheckProps(lTop,lPropData);
  522. end;
  523. end
  524. else
  525. ;
  526. end;
  527. end;
  528. end;
  529. end;
  530. var
  531. I : Integer;
  532. lData : TPascalTypeData;
  533. begin
  534. For I:=0 to TypeCount-1 do
  535. begin
  536. lData:=Types[I];
  537. CheckProps(lData,lData);
  538. end;
  539. end;
  540. class function TPascalTypeData.ExtractFirstType(aSchema : TJSONSchema): TSchemaSimpleType;
  541. begin
  542. Result:=aSchema.Validations.GetFirstType;
  543. end;
  544. function TPascalTypeData.HasArrayProperty: Boolean;
  545. var
  546. I : integer;
  547. begin
  548. Result:=False;
  549. if not Assigned(FSchema) then exit;
  550. For I:=0 to Schema.Properties.Count-1 do
  551. if (ExtractFirstType(Schema.Properties[i])=sstArray) then
  552. exit(True);
  553. end;
  554. function TPascalTypeData.HasObjectProperty(aSchemaComponentsOnly : Boolean): Boolean;
  555. var
  556. I : integer;
  557. lProp : TJSONSchema;
  558. begin
  559. Result:=False;
  560. if not Assigned(FSchema) then exit;
  561. For I:=0 to Schema.Properties.Count-1 do
  562. begin
  563. lProp:=Schema.Properties[i];
  564. if (lProp.Ref<>'') then
  565. exit(True);
  566. if (ExtractFirstType(lProp)=sstObject) and not aSchemaComponentsOnly then
  567. exit(True);
  568. end;
  569. end;
  570. { TPascalTypeDataList }
  571. function TPascalTypeDataList.GetTypes(aIndex : Integer): TPascalTypeData;
  572. begin
  573. Result:=TPascalTypeData(Items[aIndex]);
  574. end;
  575. procedure TPascalTypeDataList.Add(aItem: TPascalTypeData);
  576. begin
  577. Inherited Add(aItem);
  578. end;
  579. { TSchemaData }
  580. function TSchemaData.GetSchemaTypeCount: Integer;
  581. begin
  582. Result:=FTypeList.Count;
  583. end;
  584. function TSchemaData.GetSchemaType(aIndex : Integer): TPascalTypeData;
  585. begin
  586. Result:=FTypeList[aIndex];
  587. end;
  588. procedure TSchemaData.DoLog(const aType: TEventType; const aMessage: String);
  589. begin
  590. If Assigned(FOnLog) then
  591. FOnLog(aType,aMessage);
  592. end;
  593. procedure TSchemaData.DoLog(const aType: TEventType; const aFmt: String; aArgs: array of const);
  594. begin
  595. If Assigned(FOnLog) then
  596. FOnLog(aType,Format(aFmt,aArgs));
  597. end;
  598. // Find requested name type in API types, based on openAPI name.
  599. function TSchemaData.SchemaNameToNameType(const aName: string; aNameType: TNameType): string;
  600. var
  601. lType : TPascalTypeData;
  602. begin
  603. lType:=FindSchemaTypeData(aName);
  604. if Assigned(lType) then
  605. Result:=lType.GetTypeName(aNameType)
  606. else
  607. Result:=aName;
  608. end;
  609. function TSchemaData.GetPascalTypeDataFromRef(const aRef : String): TPascalTypeData;
  610. var
  611. P : Integer;
  612. lName : String;
  613. begin
  614. P:=RPos('/',aRef);
  615. if P=0 then
  616. P:=RPos('#',aRef);
  617. if p=0 then
  618. lName:=aRef
  619. else
  620. lName:=Copy(aRef,P+1,Length(aRef)-P);
  621. Result:=FindSchemaTypeData(lName);
  622. end;
  623. procedure TSchemaData.AddAliasType(aType: TPascalTypeData);
  624. begin
  625. FAliasList.Add(aType);
  626. end;
  627. // Determine the PascalType and pascal type name of the given schema
  628. function TSchemaData.GetSchemaTypeAndName(aType: TPascalTypeData; aSchema: TJSONSchema; out aPropType: TPascalType; aNameType : TNameType = ntPascal): String;
  629. var
  630. lTypeData : TPascalTypeData;
  631. begin
  632. lTypeData:=GetSchemaTypeData(aType,aSchema);
  633. if lTypeData=Nil then
  634. begin
  635. aPropType:=ptUnknown;
  636. Result:='';
  637. end
  638. else
  639. begin
  640. aPropType:=lTypeData.Pascaltype;
  641. Result:=lTypeData.GetTypeName(aNameType);
  642. end;
  643. end;
  644. Procedure TSchemaData.FinishAutoCreatedType(aName : string; aType: TPascalTypeData; lElementTypeData: TPascalTypeData);
  645. begin
  646. AddType(aName,aType);
  647. Case aType.Pascaltype of
  648. ptAnonStruct:
  649. AddPropertiesToType(aType,aType.Schema,True);
  650. ptArray:
  651. aType.FElementTypeData:=lElementTypeData;
  652. end;
  653. end;
  654. function TSchemaData.GetSchemaTypeData(aType: TPascalTypeData; lSchema: TJSONSchema; AllowCreate : Boolean = False) : TPascalTypeData;
  655. var
  656. lType : TSchemaSimpleType;
  657. lName,lBaseName,lPascalName : string;
  658. lFormat : String;
  659. lElTypeData : TPascalTypeData;
  660. begin
  661. LType:=lSchema.Validations.GetFirstType;
  662. Result:=Nil;
  663. if lSchema.Ref<>'' then
  664. Result:=GetPascalTypeDataFromRef(lSchema.Ref)
  665. else
  666. begin
  667. lName:='';
  668. lFormat:='';
  669. Case lType of
  670. sstNone: ;
  671. sstNull: ;
  672. sstBoolean :
  673. lName:='boolean';
  674. sstInteger :
  675. begin
  676. lName:='integer';
  677. lFormat:=lSchema.Validations.Format;
  678. end;
  679. sstNumber:
  680. begin
  681. lName:='number';
  682. end;
  683. sstString:
  684. begin
  685. if IndexText(lSchema.Validations.Format,['date','time','date-time'])>=0 then
  686. begin
  687. lName:='string';
  688. lFormat:=lSchema.Validations.Format;
  689. end
  690. else if UseEnums and lSchema.Validations.HasKeywordData(jskEnum) and (lSchema.Validations.Enum.Count>0) then
  691. begin
  692. if assigned(aType) then
  693. lBaseName:=aType.GetTypeName(ntSchema)+'_'+lSchema.Name
  694. else
  695. lBaseName:='T'+lSchema.Name;
  696. lName:='('+lBaseName+')';
  697. Result:=FindSchemaTypeData(lName);
  698. if (Result=Nil) and allowCreate then
  699. begin
  700. Result:=CreatePascalType(-1,ptEnum,lName,'T'+lBaseName,lSchema);
  701. FinishAutoCreatedType(lName,Result,Nil);
  702. end;
  703. end
  704. else
  705. begin
  706. lName:='string';
  707. end;
  708. end;
  709. sstArray:
  710. begin
  711. lElTypeData:=GetSchemaTypeData(Nil,lSchema.Items[0]);
  712. // Data.FindSchemaTypeData('Array of string')
  713. lPascalName:=ArrayTypePrefix+lElTypeData.PascalName+ArrayTypeSuffix;
  714. lName:='['+lElTypeData.SchemaName+']';
  715. Result:=FindSchemaTypeData(lName);
  716. if Result<>Nil then
  717. lName:='';
  718. if (Result=Nil) and AllowCreate then
  719. begin
  720. Result:=CreatePascalType(-1,ptArray,lName,lPascalName,lSchema);
  721. FinishAutoCreatedType(lName,Result,lElTypeData);
  722. lName:='';
  723. end;
  724. end;
  725. sstObject:
  726. begin
  727. if lSchema.Properties.Count=0 then
  728. lName:='JSON'
  729. else
  730. begin
  731. if assigned(aType) then
  732. lBaseName:=aType.GetTypeName(ntSchema)+'_'+lSchema.Name
  733. else
  734. lBaseName:='Nested_'+lSchema.Name;
  735. lName:='{'+lBaseName+'}';
  736. lPascalName:='T'+lBaseName;
  737. Result:=FindSchemaTypeData(lName);
  738. if (Result=Nil) and AllowCreate then
  739. begin
  740. Result:=CreatePascalType(-1,ptAnonStruct,lName,lPascalName,lSchema);
  741. FinishAutoCreatedType(lName,Result,lElTypeData);
  742. lName:='';
  743. end;
  744. end;
  745. end;
  746. sstAny:
  747. lname:='any';
  748. end;
  749. if lName<>'' then
  750. Result:=FindSchemaTypeData(lName,lFormat);
  751. end;
  752. end;
  753. // Add a property to the type using the schema
  754. function TSchemaData.AddTypeProperty(aType: TPascalTypeData; lProp: TJSONSchema; aName: string; Recurse: Boolean
  755. ): TPascalPropertyData;
  756. var
  757. lTypeName, lName : string;
  758. lType,lEltype : TPropertyType;
  759. I : Integer;
  760. lPropTypeData : TPascaltypeData;
  761. begin
  762. lName:=aName;
  763. if lName='' then
  764. lName:=EscapeKeyWord(lProp.Name);
  765. if lProp.Validations.TypesCount>1 then
  766. Raise ESchemaData.CreateFmt('Creating property for schema with multiple types ("%s") is not supported',[lName]);
  767. if (lProp.Validations.GetFirstType=sstArray) then
  768. if (lProp.Items.Count<>1) then
  769. Raise ESchemaData.CreateFmt('Creating array property for schema with multiple item types ("%s") is not supported',[lName])
  770. else if (lProp.Items.Count<1) then
  771. Raise ESchemaData.CreateFmt('Creating array property for schema without item types ("%s") is not supported',[lName]);
  772. lPropTypeData:=GetSchemaTypeData(aType,lProp,Recurse);
  773. if lPropTypeData=Nil then
  774. Raise ESchemaData.CreateFmt('Unknown property type for property %s',[lName]);
  775. lType:=lPropTypeData.Pascaltype;
  776. lTypeName:=lPropTypeData.GetTypeName(ntPascal);
  777. Result:=aType.AddProperty(lProp.Name,lName);
  778. Result.Schema:=lProp;
  779. Result.PropertyType:=lType;
  780. Result.TypeData:=lPropTypeData;
  781. Result.PascalTypeName:=lPropTypeData.GetTypeName(ntPascal);
  782. if (lType=ptEnum) then
  783. begin
  784. for I:=0 to lProp.Validations.Enum.Count-1 do
  785. Result.EnumValues.Add(EscapeKeyWord(lProp.Validations.Enum.Items[I].AsString));
  786. end;
  787. if (lType=ptArray) then
  788. begin
  789. Result.PascalTypeName:=lTypeName;
  790. if (lProp.Items[0].Ref<>'') then
  791. begin
  792. Result.ElementType:=ptSchemaStruct;
  793. Result.TypeData:=GetPascalTypeDataFromRef(lProp.Items[0].Ref);
  794. if Result.TypeData=Nil then
  795. Raise ESchemaData.CreateFmt('No typedata for property %s element type (Ref: %s)',[Result.PascalName,lProp.Items[0].Ref]);
  796. Result.ElementTypeName:=Result.TypeData.PascalName;
  797. end
  798. else
  799. begin
  800. Result.ElementTypeName:=GetSchemaTypeAndName(Nil,lProp.Items[0],lEltype);
  801. Result.ElementType:=lElType;
  802. end;
  803. Result.TypeNames[ntInterface]:=GetSchemaTypeAndName(Nil,lProp,lelType,ntInterface);
  804. Result.TypeNames[ntImplementation]:=GetSchemaTypeAndName(Nil,lProp,lElType,ntImplementation);
  805. end;
  806. end;
  807. procedure TSchemaData.AddPropertiesToType(aType: TPascalTypeData; aSchema: TJSONSchema; Recurse: Boolean);
  808. var
  809. I : Integer;
  810. lSchema : TJSONSchema;
  811. begin
  812. lSchema:=aSchema;
  813. if lSchema=Nil then
  814. lSchema:=aType.Schema;
  815. for I:=0 to lSchema.Properties.Count-1 do
  816. AddTypeProperty(aType,lSchema.Properties[i],'',Recurse);
  817. end;
  818. function TSchemaData.CreatePascalType(aIndex: integer; aType : TPascalType; const aSchemaName, aPascalName: String; aSchema: TJSONSchema): TPascalTypeData;
  819. begin
  820. Result:=TPascalTypeData.Create(aIndex,aType,aSchemaName,aPascalName,aSchema);
  821. end;
  822. procedure TSchemaData.AddAliasToTypeMap(aType : TPascalType; const aAlias,aSchemaTypeName, aPascalTypeName: String; aSchema: TJSONSchema);
  823. var
  824. lType : TPascalTypeData;
  825. begin
  826. lType:=CreatePascalType(-1,aType,aSchemaTypeName,aPascalTypeName,aSchema);
  827. if not (aType in [ptSchemaStruct,ptAnonStruct,ptArray]) then
  828. lType.InterfaceName:=aPascalTypeName;
  829. AddToTypeMap(aAlias,lType);
  830. AddAliasType(lType);
  831. end;
  832. constructor TSchemaData.Create;
  833. begin
  834. FTypeMap:=TFPObjectHashTable.Create(False);
  835. FTypeList:=TPascalTypeDataList.Create(True);
  836. FAliasList:=TPascalTypeDataList.Create(True);
  837. FObjectTypePrefix:='T';
  838. FObjectTypeSuffix:='';
  839. FInterfaceTypePrefix:='I';
  840. FArrayTypeSuffix:='Array';
  841. FArrayTypePrefix:='';
  842. FKeywordEscapeMode:=kemSuffix;
  843. end;
  844. destructor TSchemaData.Destroy;
  845. begin
  846. FreeAndNil(FTypeList);
  847. FreeAndNil(FAliasList);
  848. FreeAndNil(FTypeMap);
  849. inherited Destroy;
  850. end;
  851. procedure TSchemaData.DefineStandardPascalTypes;
  852. begin
  853. // typename--format
  854. AddAliasToTypeMap(ptInteger,'integer','integer','integer',Nil);
  855. AddAliasToTypeMap(ptInteger,'integer--int32','integer','integer',Nil);
  856. AddAliasToTypeMap(ptInt64,'integer--int64','integer','int64',Nil);
  857. AddAliasToTypeMap(ptString,'string','string','string',Nil);
  858. AddAliasToTypeMap(ptDateTime,'string--date','string','TDateTime',Nil);
  859. AddAliasToTypeMap(ptDateTime,'string--time','string','TDateTime',Nil);
  860. AddAliasToTypeMap(ptDateTime,'string--date-time','string','TDateTime',Nil);
  861. AddAliasToTypeMap(ptBoolean,'boolean','boolean','boolean',Nil);
  862. AddAliasToTypeMap(ptFloat64,'number','number','double',Nil);
  863. AddAliasToTypeMap(ptJSON,'JSON','object','string',Nil);
  864. AddAliasToTypeMap(ptJSON,'any','object','string',Nil);
  865. AddAliasToTypeMap(ptArray,'[string]','[string]','TStringDynArray',Nil);
  866. AddAliasToTypeMap(ptArray,'[integer]','[integer]','TIntegerDynArray',Nil);
  867. AddAliasToTypeMap(ptArray,'[integer--int64]','[integer--int64]','TInt64DynArray',Nil);
  868. AddAliasToTypeMap(ptArray,'[number]','[number]','TDoubleDynArray',Nil);
  869. AddAliasToTypeMap(ptArray,'[boolean]','[boolean]','TBooleanDynArray',Nil);
  870. end;
  871. class function TSchemaData.IsKeyWord(const aWord: String): Boolean;
  872. Const
  873. KW=';absolute;and;array;asm;begin;case;const;constructor;destructor;div;do;'+
  874. 'downto;else;end;file;for;function;goto;if;implementation;in;inherited;'+
  875. 'inline;interface;label;mod;nil;not;object;of;on;operator;or;packed;'+
  876. 'procedure;program;record;reintroduce;repeat;self;set;shl;shr;string;then;'+
  877. 'to;type;unit;until;uses;var;while;with;xor;dispose;exit;false;new;true;'+
  878. 'as;class;dispinterface;except;exports;finalization;finally;initialization;'+
  879. 'inline;is;library;on;out;packed;property;raise;resourcestring;threadvar;try;'+
  880. 'private;published;length;setlength;';
  881. begin
  882. Result:=Pos(';'+lowercase(aWord)+';',KW)<>0;
  883. end;
  884. function TSchemaData.EscapeKeyWord(const aWord: string): string;
  885. begin
  886. Result:=aWord;
  887. if IsKeyWord(Result) then
  888. case KeywordEscapeMode of
  889. kemSuffix : Result:=Result+'_';
  890. kemPrefix : Result:='_'+Result;
  891. kemAmpersand : Result:='&'+Result;
  892. end;
  893. end;
  894. function TSchemaData.GetTypeMap(const aName: string): String;
  895. begin
  896. Result:=SchemaNameToNameType(aName,ntPascal);
  897. end;
  898. // Find Pascal type data based on schema name
  899. function TSchemaData.FindSchemaTypeData(const aSchemaName: String; aFormat: String): TPascalTypeData;
  900. var
  901. lName : string;
  902. begin
  903. lName:=aSchemaName;
  904. if aFormat<>'' then
  905. lName:=lName+'--'+aFormat;
  906. Result:=TPascalTypeData(FTypeMap.Items[lName]);
  907. end;
  908. function TSchemaData.IndexOfSchemaType(const aSchemaName: String): integer;
  909. begin
  910. Result:=FTypeList.Count-1;
  911. While (Result>=0) and (GetSchemaType(Result).SchemaName<>aSchemaName) do
  912. Dec(Result);
  913. end;
  914. function TSchemaData.GetSchemaType(aSchema: TJSONSchema): TSchemaSimpleType;
  915. begin
  916. if aSchema=Nil then
  917. Result:=sstNone
  918. else
  919. Result:=TPascalTypeData.ExtractFirstType(aSchema);
  920. end;
  921. function TSchemaData.GetArrayElementType(aSchema: TJSONSchema): TSchemaSimpleType;
  922. begin
  923. Result:=sstNone;
  924. if GetSchemaType(aSchema)=sstArray then
  925. Result:=GetSchemaType(aSchema.Items[0]);
  926. end;
  927. procedure TSchemaData.AddType(const aSchemaName: String; aType: TPascalTypeData);
  928. begin
  929. FTypeList.Add(aType);
  930. addToTypeMap(aSchemaName,aType);
  931. end;
  932. procedure TSchemaData.AddToTypeMap(const aSchemaName: String; aData: TPascalTypeData);
  933. begin
  934. if FTypeMap.Items[aSchemaName]=Nil then
  935. FTypeMap.Add(aSchemaName,aData);
  936. end;
  937. procedure TSchemaData.SortTypes;
  938. Procedure AddToList(aList : TPascalTypeDataList; aType : TPascalTypeData);
  939. var
  940. I : integer;
  941. begin
  942. if aType.Sorted then
  943. exit;
  944. for I:=0 to aType.DependencyCount-1 do
  945. AddToList(aList,aType.Dependency[i]);
  946. aList.Add(aType);
  947. aType.Sorted:=True;
  948. end;
  949. var
  950. lTmpList,lSortedList : TPascalTypeDataList;
  951. i : integer;
  952. begin
  953. FTypeList.Sort(@CompareTypeDataOnName);
  954. lSortedList:=TPascalTypeDataList.Create(False);
  955. try
  956. lTmpList:=lSortedList;
  957. For I:=0 to FTypeList.Count-1 do
  958. AddToList(lSortedList,TPascalTypeData(FTypeList[i]));
  959. lTmpList:=FTypeList;
  960. FTypeList:=lSortedList;
  961. FTypeList.OwnsObjects:=True;
  962. lSortedList:=lTmpList;
  963. lSortedList.OwnsObjects:=False;
  964. finally
  965. lSortedList.Free;
  966. end;
  967. end;
  968. end.