Repository navigation
Description
A schema containing only const, without an explicit type, generates an alias to any. This affects all constants: scalars, arrays and objects.
The reproduction below compares scalar const schemas with their explicitly typed equivalents.
Reproduced against main at commit 833a968f84b4b0ec7c2f3fb5bb119e3c449f7a4f (v2.8.1-0.20260924063949-833a968f84b4). The scalar reproduction below also fails on the latest release, v2.8.0.
Save as spec.yaml:
openapi: 3.1.0
info:
title: Scalar const inference reproduction
version: "1"
paths: {}
components:
schemas:
StringConst:
const: active
TypedStringConst:
type: string
const: active
IntegerConst:
const: 0
TypedIntegerConst:
type: integer
const: 0
BooleanConst:
const: false
TypedBooleanConst:
type: boolean
const: false
NumberConst:
const: 1.5
TypedNumberConst:
type: number
const: 1.5Save as config.yaml:
package: repro
output: types.gen.go
generate:
models: true
output-options:
skip-prune: trueskip-prune: true retains the unreferenced schemas. Other settings use their defaults.
Run in a directory containing those two files:
go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen@833a968f84b4b0ec7c2f3fb5bb119e3c449f7a4f \
--config=config.yaml spec.yamlGeneration succeeds, but produces different representations for each equivalent pair:
| Constant | Without explicit type |
With explicit type |
|---|---|---|
active |
type StringConst = any |
type TypedStringConst string |
0 |
type IntegerConst = any |
type TypedIntegerConst int |
false |
type BooleanConst = any |
type TypedBooleanConst bool |
1.5 |
type NumberConst = any |
type TypedNumberConst float32 |
Only the explicitly typed schemas receive enum constants and Valid() methods.
On v2.8.0, the untyped schemas generate interface{} instead of any; the typed controls behave as above.
Additional checks with the scalar reproduction on the pinned main commit:
- Explicit
compatibility.schema-merging-behavior: v1,v2, and experimentalv3: same result as the default configuration. output-options.skip-enum-via-oneof: true: same result.output-options.skip-enum-validate: true: untyped schemas still generateany; the typed controls retain their scalar types and constants, withValid()methods disabled as expected.- Setting
output-options.type-mapping.number.default.type: float64changesTypedNumberConsttofloat64, whileNumberConstremainsany. - Changing the document version to
openapi: 3.1.1: same result.
Expected: scalar const schemas should receive the same typing and singleton-enum support as their explicitly typed counterparts, respecting configured Go type mappings.
Non-scalar constraints may not be expressible through Go types alone, but scalars already have supported representations.
Under OpenAPI 3.1's JSON Schema semantics, const requires equality with the specified value. Adding type to the scalar examples above does not change which values they accept. OpenAPI Schema Object, JSON Schema const.
The same behavior occurs inside object properties: a property containing only const: active becomes an any field.
In generateGoSchema, a missing type reaches the generic-schema branch, which selects any and returns before the scalar const/enum handler. Generic-schema branch, const/enum handler.
Related: #2520 added inference for the oneOf + title + const pattern. This report concerns standalone const schemas without type and reproduces after that change.
The reproduction is provided under Apache-2.0.
Activity
mromaszewicz commentedon Sep 26, 2026
I'm not sure how to fix this reliably. Untyped constants and enums which map to
anywork in terms of serialization and deserialization, but what breaks is parameter binding in the runtime package. I can fix that by simply passing through the string and assigning that to theany.One thing I considered is to infer the data type of the const/enum from its values, and that's where it gets annoying. Say you have a typeless enum with values [3, "4", "false", 9.7]`. What is the correct type? I realize that's contrived, but it's an example of what I hit.
Even with numbers, it's a little tricky, if I have
[3, 4.2], then I pick float, which can represent both, but then, when someone puts a number greated than 2^53 in there, JSON can't marshal it without precision loss, and it would have been better for it to be a string.I think users should provide types here, and I think current behavior of using
anyis ok. I guess if theValid()method is important, we could create a new type, still backed byanyand validate values.