sgcOpenAPI 2026.9.0 is the largest release the product has had. The version before it handled the shape of specification most tutorials show, and quietly degraded on everything else. This one went through the parser feature by feature against the OpenAPI 3.0, 3.1 and 3.2 specifications and against real published documents, and the result is 9 new features, 26 fixed bugs and 5 deliberate breaking changes.
The short version: the generated client is now correct for specifications that previously produced code that did not compile, or worse, code that compiled and called the wrong URL.
The Parser Now Tells You What It Could Not Do
The old parser had one way of reporting trouble, which was to raise an exception, and one way of handling everything else, which was to carry on quietly. An operation it could not generate simply was not in the output, and you found out when you went looking for a method that was not there.
Every document now comes back with a Warnings list. A missing openapi or info member, a member with the wrong JSON type, an operation that could not be generated, an unresolved path item reference and a JSON Schema keyword that is read but not yet honoured are all recorded there. The list is cleared on every read, so what you get belongs to the document you just parsed.
uses
sgcOpenAPI_Classes, sgcOpenAPI_Parser_Client_Pascal;
var
oParser: TsgcOpenAPI_Parser_Client_Pascal;
i: Integer;
begin
oParser := TsgcOpenAPI_Parser_Client_Pascal.Create;
Try
oParser.OpenAPIClassName := 'TPetStoreClient';
oParser.OpenAPINamespace := 'PetStore';
oParser.OutputFileName := 'PetStoreClient.pas';
oParser.ReadFromFile('petstore.json');
for i := 0 to oParser.Warnings.Count - 1 do
Memo1.Lines.Add('warning: ' + oParser.Warnings[i]);
oParser.SaveToFile('PetStoreClient.pas');
Finally
oParser.Free;
End;
end;
Set OutputFileName before saving. A Pascal unit only compiles when its declared name matches the base name of its file, and the generator used to name the unit after the input document, so generating MyClient.pas from petstore.json produced a unit called petstore that did not compile. The output name now wins.
It Knows Which Version It Is Reading
OpenAPI 3.0 and 3.1 disagree about keywords that share a name, and the old parser treated every document as 3.0. The clearest case is exclusiveMinimum, which is a boolean modifier on minimum in 3.0 and a number in its own right in 3.1. Reading one as the other gets the bound wrong.
The version is now parsed into a dialect, exposed as Dialect, DialectMajor and DialectMinor, and every keyword that differs is read the way its own version requires.
oParser.ReadFromFile('api.yaml');
case oParser.Dialect of
oapiDialect30: ShowMessage('OpenAPI 3.0');
oapiDialect31: ShowMessage('OpenAPI 3.1');
oapiDialect32: ShowMessage('OpenAPI 3.2');
end;
On top of that, 3.1 brings webhooks, jsonSchemaDialect and components.pathItems, the license identifier, the mutualTLS security scheme, a type declared as an array such as ["string","null"], and a schema declared as a plain boolean. All of them are supported. The JSON Schema 2020-12 keywords that the code generator does not act on yet are read into the model and reported through Warnings, so the gap is visible instead of invisible.
From 3.2 the parser supports the query operation and the additionalOperations map. A path declaring either now generates the matching methods, sent as a POST carrying an X-HTTP-Method-Override header.
Path-Level Parameters
This is the fix most users will feel. The specification lets you declare a parameter once on the path item instead of repeating it in every operation underneath, and that is the style the specification recommends and the style most public documents use. The old parser read those parameters and then dropped them.
The generated method took no arguments at all, and the request went out with the placeholder still in the URL, literally /pets/{petId}. It looked like a working client until the first call came back with a 404.
Schemas That Compose
Composition was the weakest part of the old parser, and every branch of it was wrong in a different way. allOf kept only the last of several base schemas and threw away the members of the others. oneOf merged every branch into one class, which produced duplicate fields. anyOf was not handled at all and resolved to a string. A schema declaring both properties and additionalProperties lost all of its properties.
All four now generate what the document describes. An inline object schema also gets its own class rather than degrading to a string, and items is read as a complete schema, so an array of inline objects, an array of enums and a nested array each generate the right type.
Enums That Carry the Value the Server Expects
The generated enum tables used to contain the sanitized Pascal identifier instead of the wire value, so allow-all went out as allowall and json-file as jsonfile. Every request built from one of those enums was rejected.
The tables now carry the real value, the declaration order of the specification is preserved, integer enums get a table too, and an extra Unknown member is generated so a value the server adds later does not silently decode as the first member of the list.
Property names get the same treatment from the other direction. A schema property named after a Delphi reserved word such as property, class, string or function, or two properties differing only in case such as Name and name, used to produce a unit that did not compile. The property is now renamed and the wire name is preserved with a JSONName attribute, so serialization still matches the document.
Responses, Including the Ones You Declare Only Once
The default response and the ranged responses 2XX, 4XX and 5XX were silently dropped. An API that declares its errors only through default, which is common, generated a client with no typed error at all. They are read now. When several successful responses are declared the lowest one is used, and application/json is preferred when an operation offers several media types.
Parameters on the Wire
The generated client now supports cookie parameters and the complete OpenAPI parameter serialization rules: matrix, label, simple, form, spaceDelimited, pipeDelimited and deepObject, each with explode and allowReserved. New AddArray and AddObject methods build the structured values by hand when you need to.
// query parameters built with the serialization style the document declares
oRequest.AddArray('tags', ['dog', 'cat'], True); // explode
oRequest.AddObject('filter', ['color', 'red', 'size', 'M']);
External References
A specification split across files barely worked. A reference carrying a JSON Pointer fragment such as ./common.yaml#/components/schemas/Error could not be resolved. Two files referencing each other crashed the parser. A relative reference inside a sub-document was resolved against the root document rather than against its own file. Two external files with the same base name overwrote each other, and could replace a schema belonging to the main document. And a chain of references was followed exactly one step.
All of that is fixed, and one thing is deliberately tightened: an external reference could previously read any file on the machine, ../../../credentials.json included, and copy the contents into the generated unit. External references are now confined to the directory of the main document. When a layout genuinely needs to reach outside it, the confinement is lifted explicitly.
uses
sgcOpenAPI_Bundle;
begin
// off by default: references may not leave the folder of the main document
sgcOpenAPIAllowRefsOutsideRoot := True;
end;
Files That Are Not Quite UTF-8
RFC 8259 says a JSON document is UTF-8, and plenty of published specifications are not. A file with a byte order mark used to be rejected with a UTF-8 error the moment it contained any character outside ASCII, and Chinese or Japanese text was silently replaced by question marks.
A document that is not valid UTF-8 is now read as Windows-1252 with a warning recorded, rather than failing. A UTF-16 file with a byte order mark is read correctly. The generated file is written with an explicit encoding, and a character the target encoding cannot represent is reported instead of quietly becoming a question mark.
A Command Line You Can Put in a Build Script
The command line now sets an exit code: 0 on success, and 1 to 7 for the different failures, so a build step can tell whether generation worked. Error messages always go to standard error, and the -l switch is now only for progress logging.
sgcOpenAPI -i petstore.json -o PetStoreClient.pas -c TPetStoreClient
if errorlevel 1 (
echo OpenAPI generation failed with exit code %errorlevel%
exit /b %errorlevel%
)
Three command line bugs went with it. The documented -output switch wrote the unit to a file called utput in the current directory, and because messages were suppressed the run still looked successful. Nothing at all happened when the tool ran with no console attached, which is exactly the case on a scheduled task or a build agent, and an existing output redirection was discarded. And -h printed a licence error rather than the usage text on a machine that was not activated, an invalid value for -m or -a was accepted silently, and an unknown switch was ignored.
New in this release, -r (or -remote) converts a YAML or Swagger 2.0 document through the public converter at converter.swagger.io. It is off by default, because it sends your document to a third party, so it is something you turn on knowingly.
Swagger 2.0 conversion itself was broken in two ways worth naming. Every number became a string, so a numeric default produced a unit that did not compile and the converted document was not valid OpenAPI 3.0. And a Swagger 2.0 discriminator, which is a plain string there, aborted the whole parse with an invalid typecast.
Breaking Changes
Five changes need a decision from you rather than just an upgrade.
Generated clients now verify the server certificate. They did not before, which means they accepted any certificate, including one presented by a man in the middle. To reach a self-signed or test endpoint, turn it off deliberately.
oClient.TLSOptions.VerifyCertificate := False; // test endpoints only
// certificates are trusted through the OpenSSL default paths, so a machine
// with no certificate store configured needs an explicit root
oClient.TLSOptions.RootCertFile := 'cacert.pem';
The request body is UTF-8. As RFC 8259 requires. A class now serializes an empty string as "field": "" rather than omitting it. Null values are controlled separately.
oClient.JSONIgnoreEmptyStrings := True; // previous output
oClient.JSONIgnoreNullValues := True; // default
A response no longer frees a ResponseStream you supplied. Set OwnsResponseStream to True for the old behaviour. Freeing the client from inside its own OnResponse, OnError or OnCancel handler now raises a clear error rather than hanging.
A command line switch value must be written as -name value or -name:value. The appended form without a separator, such as -x"GET /pets", is no longer accepted. That form is also what made -x match other switches beginning with x, such as -xml.
A parameter declared as an array is generated as an array. It used to be generated as a string, so the signature of the generated method changes for those operations.
Everything Else
The remaining fixes are the kind you only notice when they bite. A member with an unexpected JSON type, for example "properties": [], aborted the parse with an invalid typecast instead of being skipped. A schema of type integer with no default was given a default of 0, and a single-valued enum was treated as a constant, which removed the parameter from the generated method altogether. Reading the same document twice duplicated every path, tag, server and schema. A specification extension such as x-tagGroups placed among the paths was read as if it were a path. enum, required and tags were parsed with a comma-separated text helper, so a value containing a comma or a JSON escape was split or corrupted. A security requirement listing several schemes kept only the first, losing the requirement that all of them are satisfied. info.contact and info.license were never read at all, because of a test that could never be true. A server URL with several variables substituted the wrong value and could raise a list index error. Bundling a specification overwrote the input file with no backup and no message, and deleted every typographic apostrophe from the document. And a specification stored under a path containing a space, such as C:\My Specs\, could not resolve its external references.
Getting It
sgcOpenAPI 2026.9.0 is available now, with full source code and one year of updates. It supports Delphi 7 through Delphi 13 Florence and the matching C++ Builder versions.
Product page · Download the trial · Changelog
Questions or feedback? Get in touch, you will get a reply from the people who wrote the code.
