Repository: koxudaxi/datamodel-code-generator

Issue:
JSON Schema Discriminator With External Reference Support
**Is your feature request related to a problem? Please describe.**
While I love the project for most use cases, I'm always frustrated when trying to generate Pydantic models from JSON Schemas that utilize field discriminators with external references, as it results in errors and hinders my ability to work efficiently.

**Describe the solution you'd like**
I would like the `datamodel-codegen` tool to support parsing and generating Pydantic models for JSON Schemas that contain field discriminators referencing external schema files (e.g., `$ref`: "./type1.json" or likewise references in the `discriminator.mapping`). This feature would allow me to effectively utilize JSON Schema's external reference mechanism, enabling more flexible and reusable schema designs.

**Describe alternatives you've considered**
I've considered alternative solutions such as manually parsing and preprocessing the JSON Schema to construct a schema where all external references are resolved internally within a `$def` object. However, this approach would be time-consuming, error-prone for a widespread application, and may not provide the same level of performance and maintainability as a dedicated tool `datamodel-codegen`.

**Additional context**
Showcase of a breaking data model generation:
`datamodel-codegen --input schema.json`
`schema.json:
```json
{
  "properties": {
    "inner": {
      "discriminator": {
        "mapping": {
          "a": "./type1.json",
          "b": "./type2.json"
        },
        "propertyName": "type_"
      },
      "oneOf": [
        {
          "$ref": "./type1.json"
        },
        {
          "$ref": "./type2.json"
        }
      ],
      "title": "Inner"
    }
  },
  "required": [
    "inner"
  ],
  "title": "Response",
  "type": "object"
}
```
`type1.json`:
```json
{
  "properties": {
    "type_": {
      "const": "a",
      "default": "a",
      "title": "Type "
    }
  },
  "title": "Type1",
  "type": "object"
}
```
`type2.json`:
```json
{
  "properties": {
    "type_": {
      "const": "b",
      "default": "b",
      "title": "Type "
    }
  },
  "title": "Type2",
  "type": "object"
}
```


Discussion:

Resolving patch:
diff --git a/datamodel_code_generator/parser/base.py b/datamodel_code_generator/parser/base.py
--- a/datamodel_code_generator/parser/base.py
+++ b/datamodel_code_generator/parser/base.py
@@ -778,8 +778,10 @@ def __apply_discriminator_type(
                                 discriminator_model.path.split('#/')[-1]
                                 != path.split('#/')[-1]
                             ):
-                                # TODO: support external reference
-                                continue
+                                if '#' in path or discriminator_model.path[
+                                    :-1
+                                ] != path.lstrip('./'):
+                                    continue
                             type_names.append(name)
                     else:
                         type_names = [discriminator_model.path.split('/')[-1]]


Test patch:
diff --git a/tests/data/expected/main/discriminator_with_external_reference/output.py b/tests/data/expected/main/discriminator_with_external_reference/output.py
new file mode 100644
--- /dev/null
+++ b/tests/data/expected/main/discriminator_with_external_reference/output.py
@@ -0,0 +1,26 @@
+# generated by datamodel-codegen:
+#   filename:  schema.json
+#   timestamp: 2019-07-26T00:00:00+00:00
+
+from __future__ import annotations
+
+from typing import Union
+
+from pydantic import BaseModel, Field
+from typing_extensions import Literal
+
+
+class Type1(BaseModel):
+    type_: Literal['a'] = Field('a', title='Type ')
+
+
+class Type2(BaseModel):
+    type_: Literal['b'] = Field('b', title='Type ')
+
+
+class Type3(BaseModel):
+    type_: Literal['c'] = Field('c', title='Type ')
+
+
+class Response(BaseModel):
+    inner: Union[Type1, Type2, Type3] = Field(..., discriminator='type_', title='Inner')
diff --git a/tests/data/jsonschema/discriminator_with_external_reference/artificial_folder/type1.json b/tests/data/jsonschema/discriminator_with_external_reference/artificial_folder/type1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/jsonschema/discriminator_with_external_reference/artificial_folder/type1.json
@@ -0,0 +1,11 @@
+{
+  "properties": {
+    "type_": {
+      "const": "a",
+      "default": "a",
+      "title": "Type "
+    }
+  },
+  "title": "Type1",
+  "type": "object"
+}
\ No newline at end of file
diff --git a/tests/data/jsonschema/discriminator_with_external_reference/schema.json b/tests/data/jsonschema/discriminator_with_external_reference/schema.json
new file mode 100644
--- /dev/null
+++ b/tests/data/jsonschema/discriminator_with_external_reference/schema.json
@@ -0,0 +1,44 @@
+{
+  "$def": {
+    "Type3": {
+      "properties": {
+        "type_": {
+          "const": "c",
+          "default": "c",
+          "title": "Type "
+        }
+      },
+      "title": "Type3",
+      "type": "object"
+    }
+  },
+  "properties": {
+    "inner": {
+      "discriminator": {
+        "mapping": {
+          "a": "./artificial_folder/type1.json",
+          "b": "./type2.json",
+          "c": "#/$def/Type3"
+        },
+        "propertyName": "type_"
+      },
+      "oneOf": [
+        {
+          "$ref": "./artificial_folder/type1.json"
+        },
+        {
+          "$ref": "./type2.json"
+        },
+        {
+          "$ref": "#/$def/Type3"
+        }
+      ],
+      "title": "Inner"
+    }
+  },
+  "required": [
+    "inner"
+  ],
+  "title": "Response",
+  "type": "object"
+}
diff --git a/tests/data/jsonschema/discriminator_with_external_reference/type2.json b/tests/data/jsonschema/discriminator_with_external_reference/type2.json
new file mode 100644
--- /dev/null
+++ b/tests/data/jsonschema/discriminator_with_external_reference/type2.json
@@ -0,0 +1,11 @@
+{
+  "properties": {
+    "type_": {
+      "const": "b",
+      "default": "b",
+      "title": "Type "
+    }
+  },
+  "title": "Type2",
+  "type": "object"
+}
\ No newline at end of file
diff --git a/tests/test_main.py b/tests/test_main.py
--- a/tests/test_main.py
+++ b/tests/test_main.py
@@ -6123,6 +6123,35 @@ def test_main_jsonschema_discriminator_literals():
         )
 
 
+@freeze_time('2019-07-26')
+def test_main_jsonschema_external_discriminator():
+    with TemporaryDirectory() as output_dir:
+        output_file: Path = Path(output_dir) / 'output.py'
+        return_code: Exit = main(
+            [
+                '--input',
+                str(
+                    JSON_SCHEMA_DATA_PATH
+                    / 'discriminator_with_external_reference'
+                    / 'schema.json'
+                ),
+                '--output',
+                str(output_file),
+                '--output-model-type',
+                'pydantic_v2.BaseModel',
+            ]
+        )
+        assert return_code == Exit.OK
+        assert (
+            output_file.read_text()
+            == (
+                EXPECTED_MAIN_PATH
+                / 'discriminator_with_external_reference'
+                / 'output.py'
+            ).read_text()
+        )
+
+
 @freeze_time('2019-07-26')
 @pytest.mark.skipif(
     black.__version__.split('.')[0] == '19',