Summary
When --reuse-model deduplicates inlined copies of the same discriminated union, the generated subclasses use the string literal 'reuse' as the discriminator const value. Two such subclasses then collide on that literal and pydantic rejects the union at import time with Value 'reuse' for discriminator '...' mapped to multiple choices.
No 'reuse' appears in the input schema — the literal is injected by the generator.
Version
datamodel-codegen 0.56.1
- Python 3.14
- Output mode:
pydantic_v2.BaseModel
Minimal repro
minimal.json — two objects, each with a signal_id property inlining the same oneOf (discriminator: source, values: catalog/agent):
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Root",
"type": "object",
"properties": {
"first_thing": {
"type": "object",
"properties": {
"signal_id": {
"title": "Signal ID",
"discriminator": {"propertyName": "source"},
"oneOf": [
{"type": "object", "properties": {"source": {"const": "catalog"}, "domain": {"type": "string"}}, "required": ["source", "domain"]},
{"type": "object", "properties": {"source": {"const": "agent"}, "url": {"type": "string"}}, "required": ["source", "url"]}
]
}
}
},
"second_thing": {
"type": "object",
"properties": {
"signal_id": {
"title": "Signal ID",
"discriminator": {"propertyName": "source"},
"oneOf": [
{"type": "object", "properties": {"source": {"const": "catalog"}, "domain": {"type": "string"}}, "required": ["source", "domain"]},
{"type": "object", "properties": {"source": {"const": "agent"}, "url": {"type": "string"}}, "required": ["source", "url"]}
]
}
}
}
}
}
datamodel-codegen \
--input minimal.json \
--input-file-type jsonschema \
--output out.py \
--output-model-type pydantic_v2.BaseModel \
--reuse-model \
--use-union-operator \
--use-standard-collections
Actual output (broken)
class SignalId(BaseModel):
source: Literal['catalog']
domain: str
class SignalId1(BaseModel):
source: Literal['agent']
url: str
class FirstThing(BaseModel):
signal_id: SignalId | SignalId1 | None = Field(None, discriminator='source', ...)
class SignalId2(SignalId):
source: Literal['reuse'] # <-- injected, does not appear in input schema
class SignalId3(SignalId1):
source: Literal['reuse'] # <-- same literal, collides with SignalId2
class SecondThing(BaseModel):
signal_id: SignalId2 | SignalId3 | None = Field(None, discriminator='source', ...)
Importing this module raises:
TypeError: Value 'reuse' for discriminator 'source' mapped to multiple choices
Expected output
Either reuse SignalId | SignalId1 directly for second_thing.signal_id (true dedup), or produce fresh numbered classes that preserve the real discriminator values (catalog/agent), as happens without --reuse-model:
# same command, --reuse-model removed
datamodel-codegen --input minimal.json --input-file-type jsonschema --output out.py --output-model-type pydantic_v2.BaseModel --use-union-operator --use-standard-collections
Produces clean SignalId/SignalId1/SignalId2/SignalId3 with correct catalog/agent literals and no 'reuse' anywhere.
Context
Hit in the AdCP Python SDK where bundled JSON Schemas inline the same discriminated union across multiple audience-targeting variants. Full context: adcontextprotocol/adcp-client-python#184.
Workaround in our repo: post-generation step walks files, deletes class XN(Parent): source: Literal['reuse'] blocks, rewrites XN references back to Parent. Happy to contribute a fix upstream once you point to the right place in the reuse-model logic.
Summary
When
--reuse-modeldeduplicates inlined copies of the same discriminated union, the generated subclasses use the string literal'reuse'as the discriminatorconstvalue. Two such subclasses then collide on that literal and pydantic rejects the union at import time withValue 'reuse' for discriminator '...' mapped to multiple choices.No
'reuse'appears in the input schema — the literal is injected by the generator.Version
datamodel-codegen 0.56.1pydantic_v2.BaseModelMinimal repro
minimal.json— two objects, each with asignal_idproperty inlining the sameoneOf(discriminator:source, values:catalog/agent):{ "$schema": "http://json-schema.org/draft-07/schema#", "title": "Root", "type": "object", "properties": { "first_thing": { "type": "object", "properties": { "signal_id": { "title": "Signal ID", "discriminator": {"propertyName": "source"}, "oneOf": [ {"type": "object", "properties": {"source": {"const": "catalog"}, "domain": {"type": "string"}}, "required": ["source", "domain"]}, {"type": "object", "properties": {"source": {"const": "agent"}, "url": {"type": "string"}}, "required": ["source", "url"]} ] } } }, "second_thing": { "type": "object", "properties": { "signal_id": { "title": "Signal ID", "discriminator": {"propertyName": "source"}, "oneOf": [ {"type": "object", "properties": {"source": {"const": "catalog"}, "domain": {"type": "string"}}, "required": ["source", "domain"]}, {"type": "object", "properties": {"source": {"const": "agent"}, "url": {"type": "string"}}, "required": ["source", "url"]} ] } } } } }Actual output (broken)
Importing this module raises:
Expected output
Either reuse
SignalId | SignalId1directly forsecond_thing.signal_id(true dedup), or produce fresh numbered classes that preserve the real discriminator values (catalog/agent), as happens without--reuse-model:# same command, --reuse-model removed datamodel-codegen --input minimal.json --input-file-type jsonschema --output out.py --output-model-type pydantic_v2.BaseModel --use-union-operator --use-standard-collectionsProduces clean
SignalId/SignalId1/SignalId2/SignalId3with correctcatalog/agentliterals and no'reuse'anywhere.Context
Hit in the AdCP Python SDK where bundled JSON Schemas inline the same discriminated union across multiple audience-targeting variants. Full context: adcontextprotocol/adcp-client-python#184.
Workaround in our repo: post-generation step walks files, deletes
class XN(Parent): source: Literal['reuse']blocks, rewritesXNreferences back toParent. Happy to contribute a fix upstream once you point to the right place in the reuse-model logic.