Repository: koxudaxi/datamodel-code-generator

Issue:
Incorrect Literal placeholder generated for Pydantic v2 discriminated union members
**Describe the bug**
When generating Pydantic v2 models from a JSON schema defining a discriminated union (`oneOf` + `discriminator`), `datamodel-codegen` generates incorrect placeholder strings (e.g., `'0#-datamodel-code-generator-#-object-#-special-#'`) within the `typing.Literal` annotations for the discriminator field in the individual member models. It should instead use the `const` value specified in the input schema.

**To Reproduce**

Example schema (`schema.json`):
```json
{
  "title": "Container",
  "type": "object",
  "properties": {
    "items_list": {
      "type": "array",
      "items": {
        "title": "Item",
        "oneOf": [
          {
            "title": "ItemA",
            "type": "object",
            "properties": {
              "item_type": {
                "const": "type_a",
                "type": "string",
                "description": "Discriminator field value for Type A"
              },
              "value_a": {
                "type": "integer",
                 "description": "Specific value for Type A"
              }
            },
            "required": ["item_type", "value_a"]
          },
          {
            "title": "ItemB",
            "type": "object",
            "properties": {
              "item_type": {
                "const": "type_b",
                "type": "string",
                "description": "Discriminator field value for Type B"
              },
              "value_b": {
                "type": "string",
                 "description": "Specific value for Type B"
              }
            },
            "required": ["item_type", "value_b"]
          }
        ],
        "discriminator": {
          "propertyName": "item_type"
        }
      }
    }
  }
}
```

Used commandline:
```bash
$ datamodel-codegen \
    --input schema.json \
    --input-file-type jsonschema \
    --output model.py \
    --output-model-type pydantic_v2.BaseModel \
    --use-annotated \
    --use-standard-collections \
    --field-constraints \
    --target-python-version 3.11
```

**Actual Behavior:** 
The generated `model.py` contains classes `ItemsList1` and `ItemsList2` (derived from the property name `items_list` containing the union) with the `item_type` field defined with incorrect `Literal` placeholders:

```python
# generated by datamodel-codegen:
#   filename:  schema.json
#   timestamp: [Timestamp will vary]

from __future__ import annotations

from typing import Annotated, Literal, Optional, Union

from pydantic import BaseModel, Field, RootModel


class ItemsList1(BaseModel):
    item_type: Annotated[
        # BUG: Incorrect Literal placeholder
        Literal['0#-datamodel-code-generator-#-object-#-special-#'],
        Field(description='Discriminator field value for Type A'),
    ]
    value_a: Annotated[int, Field(description='Specific value for Type A')]


class ItemsList2(BaseModel):
    item_type: Annotated[
        # BUG: Incorrect Literal placeholder
        Literal['1#-datamodel-code-generator-#-object-#-special-#'],
        Field(description='Discriminator field value for Type B'),
    ]
    value_b: Annotated[str, Field(description='Specific value for Type B')]


class ItemsList(RootModel[Union[ItemsList1, ItemsList2]]):
    root: Annotated[
        Union[ItemsList1, ItemsList2], Field(discriminator='item_type', title='Item')
    ]


class Container(BaseModel):
    items_list: Optional[list[ItemsList]] = None
```


**Expected behavior**
The generated Python code for the member models (`ItemsList1`, `ItemsList2`) should contain `typing.Literal` annotations reflecting the actual `const` values specified in the schema (e.g., `'type_a'`, `'type_b'`).

Example of expected code structure:
```python
# [Imports and other parts...]

class ItemsList1(BaseModel):
    item_type: Annotated[
        # EXPECTED: Correct Literal from schema const
        Literal['type_a'],
        Field(description='Discriminator field value for Type A'),
    ]
    value_a: Annotated[int, Field(description='Specific value for Type A')]


class ItemsList2(BaseModel):
    item_type: Annotated[
        # EXPECTED: Correct Literal from schema const
        Literal['type_b'],
        Field(description='Discriminator field value for Type B'),
    ]
    value_b: Annotated[str, Field(description='Specific value for Type B')]

# NOTE: ItemsList and Container definitions expected as generated.
class ItemsList(RootModel[Union[ItemsList1, ItemsList2]]):
    root: Annotated[
        Union[ItemsList1, ItemsList2], Field(discriminator='item_type', title='Item')
    ]

class Container(BaseModel):
    items_list: Optional[list[ItemsList]] = None

# [Rest of the generated code...]
```

**Version:**
 - OS: `macOS Sequoia 15.4.1 `
 - Python version: `3.12.2`
 - datamodel-code-generator version: `0.30` and have tried on `0.30.1.dev3+g4fa8ea3`

**Additional context**
It's worth noting that the `Annotated[Union[...], Field(discriminator='item_type')]` on the `ItemsList` RootModel *is* generated correctly. The issue seems specifically related to resolving the `const` value for the `Literal` annotation within the individual union member classes (`ItemsList1`, `ItemsList2`).


Discussion:
I also ran into this and have submit a pull request to fix it.

I'm going to pin my use of this module to my branch to validate that it fixes my use case (in a wider schema beyond the test case I added). Will report back here.



Patch:
diff --git a/src/datamodel_code_generator/parser/base.py b/src/datamodel_code_generator/parser/base.py
index a6642ba27..413e56981 100644
--- a/src/datamodel_code_generator/parser/base.py
+++ b/src/datamodel_code_generator/parser/base.py
@@ -772,19 +772,31 @@ def check_paths(
                                     continue
                             type_names.append(name)
 
-                    # Check the main discriminator model path
-                    if mapping:
-                        check_paths(discriminator_model, mapping)  # pyright: ignore[reportArgumentType]
-
-                        # Check the base_classes if they exist
-                        if len(type_names) == 0:
-                            for base_class in discriminator_model.base_classes:
-                                check_paths(base_class.reference, mapping)  # pyright: ignore[reportArgumentType]
-                    else:
-                        type_names = [discriminator_model.path.split("/")[-1]]
+                    # First try to get the discriminator value from the const field
+                    for discriminator_field in discriminator_model.fields:
+                        if field_name not in {discriminator_field.original_name, discriminator_field.name}:
+                            continue
+                        if discriminator_field.extras.get("const"):
+                            type_names = [discriminator_field.extras["const"]]
+                            break
+
+                    # If no const value found, try to get it from the mapping
+                    if not type_names:
+                        # Check the main discriminator model path
+                        if mapping:
+                            check_paths(discriminator_model, mapping)  # pyright: ignore[reportArgumentType]
+
+                            # Check the base_classes if they exist
+                            if len(type_names) == 0:
+                                for base_class in discriminator_model.base_classes:
+                                    check_paths(base_class.reference, mapping)  # pyright: ignore[reportArgumentType]
+                        else:
+                            type_names = [discriminator_model.path.split("/")[-1]]
+
                     if not type_names:  # pragma: no cover
                         msg = f"Discriminator type is not found. {data_type.reference.path}"
                         raise RuntimeError(msg)
+
                     has_one_literal = False
                     for discriminator_field in discriminator_model.fields:
                         if field_name not in {discriminator_field.original_name, discriminator_field.name}:


Test patch:
diff --git a/tests/data/expected/main/jsonschema/discriminator_no_mapping.py b/tests/data/expected/main/jsonschema/discriminator_no_mapping.py
new file mode 100644
index 000000000..5ca009df7
--- /dev/null
+++ b/tests/data/expected/main/jsonschema/discriminator_no_mapping.py
@@ -0,0 +1,21 @@
+# generated by datamodel-codegen:
+#   filename:  discriminator_no_mapping.json
+#   timestamp: 2019-07-26T00:00:00+00:00
+
+from __future__ import annotations
+
+from typing import Literal, Union
+
+from pydantic import BaseModel, Field
+
+
+class Cat(BaseModel):
+    pet_type: Literal['cat']
+
+
+class Dog(BaseModel):
+    pet_type: Literal['dog']
+
+
+class Animal(BaseModel):
+    pet: Union[Cat, Dog] = Field(..., discriminator='pet_type', title='Pet')
diff --git a/tests/data/jsonschema/discriminator_no_mapping.json b/tests/data/jsonschema/discriminator_no_mapping.json
new file mode 100644
index 000000000..e641defdf
--- /dev/null
+++ b/tests/data/jsonschema/discriminator_no_mapping.json
@@ -0,0 +1,47 @@
+{
+  "$defs": {
+    "Cat": {
+      "properties": {
+        "pet_type": {
+          "const": "cat"
+        }
+      },
+      "required": [
+        "pet_type"
+      ],
+      "type": "object"
+    },
+    "Dog": {
+      "properties": {
+        "pet_type": {
+          "const": "dog"
+        }
+      },
+      "required": [
+        "pet_type"
+      ],
+      "type": "object"
+    }
+  },
+  "properties": {
+    "pet": {
+      "discriminator": {
+        "propertyName": "pet_type"
+      },
+      "oneOf": [
+        {
+          "$ref": "#/$defs/Cat"
+        },
+        {
+          "$ref": "#/$defs/Dog"
+        }
+      ],
+      "title": "Pet"
+    }
+  },
+  "required": [
+    "pet"
+  ],
+  "title": "Animal",
+  "type": "object"
+} 
\ No newline at end of file
diff --git a/tests/main/jsonschema/test_main_jsonschema.py b/tests/main/jsonschema/test_main_jsonschema.py
index 68a57c36d..2e8a21ad4 100644
--- a/tests/main/jsonschema/test_main_jsonschema.py
+++ b/tests/main/jsonschema/test_main_jsonschema.py
@@ -2657,6 +2657,28 @@ def test_main_jsonschema_discriminator_literals(output_model: str, expected_outp
         assert output_file.read_text() == (EXPECTED_JSON_SCHEMA_PATH / expected_output).read_text()
 
 
+@freeze_time("2019-07-26")
+@pytest.mark.skipif(
+    int(black.__version__.split(".")[0]) < 24,
+    reason="Installed black doesn't support the new style",
+)
+def test_main_jsonschema_discriminator_literals_with_no_mapping(min_version: str) -> None:
+    with TemporaryDirectory() as output_dir:
+        output_file: Path = Path(output_dir) / "output.py"
+        return_code: Exit = main([
+            "--input",
+            str(JSON_SCHEMA_DATA_PATH / "discriminator_no_mapping.json"),
+            "--output",
+            str(output_file),
+            "--output-model-type",
+            "pydantic_v2.BaseModel",
+            "--target-python",
+            min_version,
+        ])
+        assert return_code == Exit.OK
+        assert output_file.read_text() == (EXPECTED_JSON_SCHEMA_PATH / "discriminator_no_mapping.py").read_text()
+
+
 @pytest.mark.parametrize(
     ("output_model", "expected_output"),
     [