Skip to content

Commit 0e61504

Browse files
authored
Preserve extras in oneOf/anyOf structures during initialization of JsonSchemaObject (#2623)
1 parent aa714fc commit 0e61504

4 files changed

Lines changed: 122 additions & 8 deletions

File tree

src/datamodel_code_generator/parser/jsonschema.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -341,14 +341,17 @@ class Config:
341341
keep_untouched = (cached_property,)
342342
smart_casts = True
343343

344-
if not TYPE_CHECKING:
345-
346-
def __init__(self, **data: Any) -> None:
347-
"""Initialize JsonSchemaObject with extra fields handling."""
348-
super().__init__(**data)
349-
self.extras = {k: v for k, v in data.items() if k not in EXCLUDE_FIELD_KEYS}
350-
if "const" in data.get(self.__extra_key__, {}): # pragma: no cover
351-
self.extras["const"] = data[self.__extra_key__]["const"]
344+
def __init__(self, **data: Any) -> None:
345+
"""Initialize JsonSchemaObject with extra fields handling."""
346+
super().__init__(**data)
347+
# Restore extras from alias key (for dict -> parse_obj round-trip)
348+
alias_extras = data.get(self.__extra_key__, {})
349+
# Collect custom keys from raw data
350+
raw_extras = {k: v for k, v in data.items() if k not in EXCLUDE_FIELD_KEYS}
351+
# Merge: raw_extras takes precedence (original data is the source of truth)
352+
self.extras = {**alias_extras, **raw_extras}
353+
if "const" in alias_extras: # pragma: no cover
354+
self.extras["const"] = alias_extras["const"]
352355

353356
@cached_property
354357
def is_object(self) -> bool:
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# generated by datamodel-codegen:
2+
# filename: extras_in_oneof.json
3+
# timestamp: 2019-07-26T00:00:00+00:00
4+
5+
from __future__ import annotations
6+
7+
from typing import Optional, Union
8+
9+
from pydantic import BaseModel, Field
10+
11+
12+
class OneofProp(BaseModel):
13+
shared_prop: Optional[str] = Field(
14+
None, json_schema_extra={'x-shared': 'shared_value'}
15+
)
16+
variant_a_prop: Optional[str] = Field(
17+
None, json_schema_extra={'x-variant': 'variant_a_value'}
18+
)
19+
20+
21+
class OneofProp1(BaseModel):
22+
shared_prop: Optional[str] = Field(
23+
None, json_schema_extra={'x-shared': 'shared_value'}
24+
)
25+
variant_b_prop: Optional[int] = Field(
26+
None, json_schema_extra={'x-variant': 'variant_b_value'}
27+
)
28+
29+
30+
class AnyofProp(BaseModel):
31+
any_a_prop: Optional[str] = Field(None, json_schema_extra={'x-any': 'any_a_value'})
32+
33+
34+
class AnyofProp1(BaseModel):
35+
any_b_prop: Optional[bool] = Field(None, json_schema_extra={'x-any': 'any_b_value'})
36+
37+
38+
class ExtrasInOneOf(BaseModel):
39+
simple_prop: Optional[str] = Field(
40+
None, json_schema_extra={'x-custom': 'simple_value'}
41+
)
42+
oneof_prop: Optional[Union[OneofProp, OneofProp1]] = Field(
43+
None, json_schema_extra={'x-parent-custom': 'parent_value'}
44+
)
45+
anyof_prop: Optional[Union[AnyofProp, AnyofProp1]] = None
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-07/schema#",
3+
"id": "extras_in_oneof",
4+
"title": "ExtrasInOneOf",
5+
"type": "object",
6+
"properties": {
7+
"simple_prop": {"type": "string", "x-custom": "simple_value"},
8+
"oneof_prop": {
9+
"x-parent-custom": "parent_value",
10+
"properties": {
11+
"shared_prop": {"type": "string", "x-shared": "shared_value"}
12+
},
13+
"oneOf": [
14+
{
15+
"title": "VariantA",
16+
"type": "object",
17+
"properties": {
18+
"variant_a_prop": {"type": "string", "x-variant": "variant_a_value"}
19+
}
20+
},
21+
{
22+
"title": "VariantB",
23+
"type": "object",
24+
"properties": {
25+
"variant_b_prop": {"type": "integer", "x-variant": "variant_b_value"}
26+
}
27+
}
28+
]
29+
},
30+
"anyof_prop": {
31+
"anyOf": [
32+
{
33+
"title": "AnyVariantA",
34+
"type": "object",
35+
"properties": {
36+
"any_a_prop": {"type": "string", "x-any": "any_a_value"}
37+
}
38+
},
39+
{
40+
"title": "AnyVariantB",
41+
"type": "object",
42+
"properties": {
43+
"any_b_prop": {"type": "boolean", "x-any": "any_b_value"}
44+
}
45+
}
46+
]
47+
}
48+
}
49+
}

tests/main/jsonschema/test_main_jsonschema.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3294,3 +3294,20 @@ def test_main_jsonschema_root_model_default_value_non_root(output_file: Path) ->
32943294
"--use-annotated",
32953295
],
32963296
)
3297+
3298+
3299+
@pytest.mark.benchmark
3300+
def test_main_jsonschema_extras_in_oneof(output_file: Path) -> None:
3301+
"""Test that extras are preserved in oneOf/anyOf structures (Issue #2403)."""
3302+
run_main_and_assert(
3303+
input_path=JSON_SCHEMA_DATA_PATH / "extras_in_oneof.json",
3304+
output_path=output_file,
3305+
input_file_type="jsonschema",
3306+
assert_func=assert_file_content,
3307+
expected_file="extras_in_oneof.py",
3308+
extra_args=[
3309+
"--output-model-type",
3310+
"pydantic_v2.BaseModel",
3311+
"--field-include-all-keys",
3312+
],
3313+
)

0 commit comments

Comments
 (0)