openapi-compress removes redundancy from an
OpenAPI 3.x specification. It finds
component schemas that describe the same thing, merges them into one, rewrites every
$ref that pointed at the copies, and shortens the resulting names.
Specifications that are generated rather than hand-written accumulate duplicates
fast. The same object appears as a response body, as an array element, and as a
nested property, and each occurrence gets its own component with its own unwieldy
name — GetV1PetByPetIDOkJSONResponseMedicalInfo and
ListV1PetsOkJSONResponseDataItemsMedicalInfo describing an identical shape.
This module collapses them back down. Two schemas are considered the same when the
same JSON validates against both, using
openapi-compare — so schemas
that differ only in their title or description still merge, while a difference
that changes what the schema accepts prevents it.
Optionally it goes further, merging schemas that are merely similar: if two object schemas share most of their properties, they can be widened into a single schema covering both.
- Deduplicates identical component schemas, keeping one canonical definition
- Merges similar schemas above a configurable similarity threshold
- Rewrites every
$refthroughout the document, including deep inside nested schemas - Deduplicates parameters in the same way
- Shortens the names of merged schemas, dropping generated noise like
OkJSONResponse
At the default threshold of 1.0 only equivalent schemas merge. Below that, object
schemas are scored by a weighted Jaccard index over their property names:
score = Σ weight(p) / |union of property names|
where a property present in both with the same shape scores 1.0, and one present
in both with a different shape scores 0.5. The threshold steps down gradually,
running each level until no further merges are found, so the most confident merges
always happen first.
A bare scalar is never merged: a string, number, integer or boolean with nothing
but a type and documentation, such as a component idRequest that is only
{"type": "string"}. Its name and description are all it carries, and merging by
shape would erase exactly those, giving IDs, emoji names and time zones one type.
A scalar with a constraint, such as a format or an enum, merges like any other
schema.
Of the schemas that merge, the one kept is one the specification named, else the
one with the most references, else the one with the shortest name, else the
alphabetically first: the {id} object a specification defines keeps its own
name rather than taking one derived from a single place that also used it. A
schema openapi-flatten moved out of the document and named itself carries
x-flattened-from, given its MarkOrigin; compress reads and removes it before
comparing anything, so it never keeps two schemas apart.
Examples are kept from every schema that merges, down to each property, item and alternative: where the one kept has none, it takes one from those merged into it.
A description says what a schema is used for in one place, not what shape it
has. When the schemas that merge disagree on it, each one's description moves
beside the $refs that pointed at it, and the schema kept has none, so no
reference shows another's. A reference with a description of its own keeps it.
go get -tool github.com/MarkRosemaker/openapi-compress/cmd/openapi-compressor
go get github.com/MarkRosemaker/openapi-compressimport (
"github.com/MarkRosemaker/openapi"
compress "github.com/MarkRosemaker/openapi-compress"
)
doc, err := openapi.LoadFromFile("api/openapi.json")
if err != nil {
log.Fatal(err)
}
// Exact-shape deduplication only.
if err := compress.Document(doc, compress.Config{}); err != nil {
log.Fatal(err)
}To also merge schemas that merely overlap, lower the threshold:
err := compress.Document(doc, compress.Config{
MinSimilarity: 0.8, // merge schemas sharing ≥80% of their properties
SimilarityStep: 0.05, // step down by this much per round
})| Field | Default | Purpose |
|---|---|---|
MinSimilarity |
1.0 |
Lowest similarity at which two schemas may merge. 1.0 means equivalent shapes only. |
SimilarityStep |
0.05 |
How far the threshold drops between rounds. |
SkipNameShortening |
false |
Keep the original names instead of shortening merged ones. |
Renaming a schema on its own — without compressing anything — is
openapi-edit's job, and this
module uses it internally to shorten the names of merged schemas:
err := edit.RenameSchema(doc, "OldName", "NewName")openapi-compress -spec api/openapi.json -minsim 0.8| Flag | Default | Purpose |
|---|---|---|
-spec |
api/openapi.json |
Path to the specification, rewritten in place |
-minsim |
1 |
Minimum similarity for a merge |
-simstep |
0.05 |
Threshold reduction between rounds |
If the specification was valid on the way in, the result is validated before it is written back.
| Module | Purpose |
|---|---|
| openapi | Parse, validate, and write OpenAPI 3.x specifications |
| openapi-compare | Compare specification objects — exact equality and shape equivalence |
| openapi-edit | Safe structural edits, such as renaming a schema and rewriting every $ref to it |
| openapi-flatten | Promote inline definitions into named components entries |
| openapi-compress (this module) | Deduplicate and merge equivalent component schemas |
| openapi-merge | Merge schemas that were inferred independently from different samples |
| openapi-enrich | Infer specification content from observed HTTP traffic |
| openapi-codegen | Generate Go types, clients, and servers from a specification |
This module pairs naturally with
openapi-flatten: flattening
names every inline type, which necessarily creates duplicates, and compressing
collapses them again. Run flatten first.
- Go Reference: API documentation.
Contributions are welcome — please open an issue or a pull request on GitHub.
This project is licensed under the Apache 2.0 License.
