datamodel-code-generator
datamodel-code-generator copied to clipboard
JSON Schema: oneOf with const
Describe the bug Does not generate an enumeration for oneOf + cost. For example, autocomplete in Jetbrains IDE works fine for this case.
To Reproduce
- create a schema
- generate
- get an incorrect result generation
Example schema:
# definition
nodeJsModeEnum:
title: NodeJS mode
type: string
description: |
A long description here.
default: npm
oneOf:
- title: npm
const: npm
- title: yarn
const: yarn
- title: npm ci
const: npm_ci
# usage
properties:
mode:
$ref: "#/definitions/nodejsModeEnum"
Used commandline:
pdm run datamodel-codegen --input config/schema/cd.schema.yaml --input-file-type jsonschema --output src/config/cd_model.py --output-model-type pydantic_v2.BaseModel
# pyproject.toml options
#[tool.datamodel-codegen]
#field-constraints = true
#snake-case-field = true
#strip-default-none = false
#target-python-version = "3.11"
Expected behavior
# expects
class NodeJsModeEnum(Enum):
npm = 'npm'
yarn = 'yarn'
npm_ci = 'npm_ci'
# actual
class NodeJsModeEnum(RootModel[str]):
root: str = Field(
..., description='...'
)
Version:
- OS: MacOS
- Python version: 3.11
- datamodel-code-generator version: 0.25.5
How to fix with problem ? @roquie
Would like to add on that - having enums defined that way are much more useful that using the plain "enum" property. That's because they allow attaching description for each enum member!
As a workaround
You can define the "enum" property as well as "oneOf" and it will generate code correctly and maintain the description of each enum member.
Example:
Given the JSON Schema:
{
"$schema": "https://json-schema.org/draft-07/schema#",
"type": "object",
"required": ["difficulty_level"],
"properties": {
"difficulty_level": {
"title": "The difficulty level of the game",
"enum": ["easy", "medium", "hard"],
"oneOf": [
{
"const": "easy",
"description": "Soft intro to game"
},
{
"const": "medium",
"description": "To moderate players"
},
{
"const": "hard",
"description": "To really advanced players who looking for challenge"
}
]
}
}
}
Note that it's important to distinguish between "title" property and "description" which work together to provide documentation for JSON file editors:
The generated code will be:
# generated by datamodel-codegen:
# filename: example-json-schema.json
# timestamp: 2025-06-30T08:50:14+00:00
from __future__ import annotations
from enum import Enum
from pydantic import BaseModel, Field
class DifficultyLevel(Enum):
easy = 'easy'
medium = 'medium'
hard = 'hard'
class Model(BaseModel):
difficulty_level: DifficultyLevel = Field(
..., title='The difficulty level of the game'
)
This achieves what you would expect - but without documentation on each enum member. Those can be added manually:
# ...
from enum import Enum
class DifficultyLevel(Enum):
easy = "easy"
"""Soft intro to game"""
medium = "medium"
"""To moderate players"""
hard = "hard"
"""To really advanced players who looking for challenge"""
# ....
There's also a useful use case where each enum member in the "oneof" receives a "title" or "description". Because you can't annotate enum members nicely like the following. Because it raises the error:
Type annotations are not allowed for enum members Pylance - [reportGeneralTypeIssues](https://github.com/microsoft/pylance-release/blob/main/docs/diagnostics/reportGeneralTypeIssues.md)
Maybe we can take a different approach for that:
Given the JSON Schema:
{
"$schema": "https://json-schema.org/draft-07/schema#",
"type": "object",
"required": ["difficulty_level"],
"properties": {
"difficulty_level": {
"title": "The difficulty level of the game",
"oneOf": [
{
"const": "easy",
"title": "Easy",
"description": "Perfect for beginners and newcomers"
},
{
"const": "medium",
"title": "Medium",
"description": "More challenging, but better rewards"
},
{
"const": "hard",
"title": "Hard",
"description": "The most rewarding, yet the most challenging"
}
]
}
}
}
The generated code would be:
# Notice `StrEnum` is used instead of plain `Enum`
class DifficultyLevel(StrEnum):
def __init__(self, _, title, description) -> None:
self.title = title
self.description = description
def __new__(cls, value, title, description):
member = str.__new__(cls, value)
member._value_ = value
member.title = title
member.description = description
return member
EASY = "easy", "Easy", "Perfect for beginners and newcomers"
MEDIUM = "medium", "Medium", "More challenging, but better rewards"
HARD = "hard", "Hard", "The most rewarding yet the most challenging"
difficulty_level: DifficultyLevel = DifficultyLevel.EASY
# This gives so much flexibility!
print(difficulty_level.title)
print(difficulty_level.description)