Repository: koxudaxi/datamodel-code-generator

Issue:
Elastic's Kibana API interaction with datamodel-code-generator not working
**Describe the bug**

While trying to generate a pydantic code model with datamodel-codegen for Kibana's  API, command didn't work.
API is available here (https://www.elastic.co/docs/api/doc/kibana)

```
pydantic_core._pydantic_core.ValidationError: 1 validation error for JsonSchemaObject
required.2
  Input should be a valid string [type=string_type, input_value={'anyOf': ['to', 'cc', 'bcc']}, input_type=dict]
    For further information visit https://errors.pydantic.dev/2.10/v/string_type
```

Honestly I'm not able to figure out whether Kibana's OpenAPI export is malformed or is there a problem with the datamodel-code-generator, since the error is not very explicative, so this may not be a bug.


**To Reproduce**

Used commandline:

Local:
```
datamodel-codegen  --input ~/Downloads/kibana-openapi-source.json 
```

Remote
```
$ datamodel-codegen --url "https://www.elastic.co/docs/api/doc/kibana/v8.json"
```


**Expected behavior**
Not getting an error

**Version:**
 - OS: Ubuntu 24
 - Python version: 3.10
 - datamodel-code-generator version: 0.26.5




Discussion:
These are the errors I currently get with the newest OpenAPI specifications provided by Elastic:

```
❯ uvx --from "datamodel-code-generator[http]" datamodel-codegen --url https://www.elastic.co/docs/api/doc/kibana.json --input-file-type openapi --disable-warnings --output model.py
Traceback (most recent call last):
  File "/home/user/.cache/uv/archive-v0/UCkDNN2DAHUrkULTwK1Qb/lib/python3.8/site-packages/datamodel_code_generator/__main__.py", line 473, in main
    generate(
  File "/home/user/.cache/uv/archive-v0/UCkDNN2DAHUrkULTwK1Qb/lib/python3.8/site-packages/datamodel_code_generator/__init__.py", line 480, in generate
    results = parser.parse()
  File "/home/user/.cache/uv/archive-v0/UCkDNN2DAHUrkULTwK1Qb/lib/python3.8/site-packages/datamodel_code_generator/parser/base.py", line 1178, in parse
    self.parse_raw()
  File "/home/user/.cache/uv/archive-v0/UCkDNN2DAHUrkULTwK1Qb/lib/python3.8/site-packages/datamodel_code_generator/parser/openapi.py", line 582, in parse_raw
    self.parse_raw_obj(
  File "/home/user/.cache/uv/archive-v0/UCkDNN2DAHUrkULTwK1Qb/lib/python3.8/site-packages/datamodel_code_generator/parser/jsonschema.py", line 1526, in parse_raw_obj
    self.parse_obj(name, self.SCHEMA_OBJECT_TYPE.parse_obj(raw), path)
  File "/home/user/.cache/uv/archive-v0/UCkDNN2DAHUrkULTwK1Qb/lib/python3.8/site-packages/pydantic/main.py", line 1222, in parse_obj
    return cls.model_validate(obj)
  File "/home/user/.cache/uv/archive-v0/UCkDNN2DAHUrkULTwK1Qb/lib/python3.8/site-packages/pydantic/main.py", line 627, in model_validate
    return cls.__pydantic_validator__.validate_python(
pydantic_core._pydantic_core.ValidationError: 1 validation error for JsonSchemaObject
required
  Input should be a valid list [type=list_type, input_value=None, input_type=NoneType]
    For further information visit https://errors.pydantic.dev/2.10/v/list_type
```

It is the same output with `uvx --from "datamodel-code-generator[http]" datamodel-codegen --url https://www.elastic.co/docs/api/doc/kibana.yaml --input-file-type openapi --disable-warnings --output model.py`.



Patch:
diff --git a/src/datamodel_code_generator/parser/jsonschema.py b/src/datamodel_code_generator/parser/jsonschema.py
index 67f4c275e..2a0e39367 100644
--- a/src/datamodel_code_generator/parser/jsonschema.py
+++ b/src/datamodel_code_generator/parser/jsonschema.py
@@ -211,6 +211,33 @@ def validate_ref(cls, value: Any) -> Any:  # noqa: N805
             return value.replace("#", "#/")
         return value
 
+    @field_validator("required", mode="before")
+    def validate_required(cls, value: Any) -> Any:  # noqa: N805
+        if value is None:
+            return []
+        if isinstance(value, list):  # noqa: PLR1702
+            # Filter to only include valid strings, excluding invalid objects
+            required_fields: list[str] = []
+            for item in value:
+                if isinstance(item, str):
+                    required_fields.append(item)
+
+                # In some cases, the required field can include "anyOf", "oneOf", or "allOf" as a dict (#2297)
+                elif isinstance(item, dict):
+                    for key, val in item.items():
+                        if isinstance(val, list):
+                            # If 'anyOf' or "oneOf" is present, we won't include it in required fields
+                            if key in {"anyOf", "oneOf"}:
+                                continue
+
+                            if key == "allOf":
+                                # If 'allOf' is present, we include them as required fields
+                                required_fields.extend(sub_item for sub_item in val if isinstance(sub_item, str))
+
+            value = required_fields
+
+        return value
+
     items: Optional[Union[list[JsonSchemaObject], JsonSchemaObject, bool]] = None  # noqa: UP007, UP045
     uniqueItems: Optional[bool] = None  # noqa: N815, UP045
     type: Optional[Union[str, list[str]]] = None  # noqa: UP007, UP045
@@ -1552,7 +1579,7 @@ def parse_raw_obj(
         raw: dict[str, Any],
         path: list[str],
     ) -> None:
-        self.parse_obj(name, self.SCHEMA_OBJECT_TYPE.parse_obj(raw), path)
+        self.parse_obj(name, self.SCHEMA_OBJECT_TYPE.model_validate(raw), path)
 
     def parse_obj(
         self,


Test patch:
diff --git a/tests/data/expected/parser/openapi/openapi_parser_parse_allof_required_fields/output.py b/tests/data/expected/parser/openapi/openapi_parser_parse_allof_required_fields/output.py
new file mode 100644
index 000000000..692c4a228
--- /dev/null
+++ b/tests/data/expected/parser/openapi/openapi_parser_parse_allof_required_fields/output.py
@@ -0,0 +1,11 @@
+from __future__ import annotations
+
+from typing import List
+
+from pydantic import BaseModel, Field
+
+
+class EmailMessage(BaseModel):
+    message: str = Field(..., description='The email message text.')
+    subject: str = Field(..., description='The subject line of the email.')
+    to: List[str] = Field(..., description='A list of email addresses.')
diff --git a/tests/data/expected/parser/openapi/openapi_parser_parse_anyof_required/output.py b/tests/data/expected/parser/openapi/openapi_parser_parse_anyof_required/output.py
new file mode 100644
index 000000000..e1a740434
--- /dev/null
+++ b/tests/data/expected/parser/openapi/openapi_parser_parse_anyof_required/output.py
@@ -0,0 +1,17 @@
+from __future__ import annotations
+
+from typing import List, Optional
+
+from pydantic import BaseModel, Field
+
+
+class EmailMessage(BaseModel):
+    bcc: Optional[List[str]] = Field(
+        None, description='A list of "blind carbon copy" email addresses.'
+    )
+    cc: Optional[List[str]] = Field(
+        None, description='A list of "carbon copy" email addresses.'
+    )
+    message: str = Field(..., description='The email message text.')
+    subject: str = Field(..., description='The subject line of the email.')
+    to: Optional[List[str]] = Field(None, description='A list of email addresses.')
diff --git a/tests/data/expected/parser/openapi/openapi_parser_parse_required_null/output.py b/tests/data/expected/parser/openapi/openapi_parser_parse_required_null/output.py
new file mode 100644
index 000000000..c51c4be3c
--- /dev/null
+++ b/tests/data/expected/parser/openapi/openapi_parser_parse_required_null/output.py
@@ -0,0 +1,17 @@
+from __future__ import annotations
+
+from enum import Enum
+from typing import Optional
+
+from pydantic import BaseModel, Field
+
+
+class Type(Enum):
+    my_first_object = 'my_first_object'
+    my_second_object = 'my_second_object'
+    my_third_object = 'my_third_object'
+
+
+class ObjectBase(BaseModel):
+    name: Optional[str] = Field(None, description='Name of the object')
+    type: Optional[Type] = Field(None, description='Object type')
diff --git a/tests/data/openapi/allof_required_fields.yaml b/tests/data/openapi/allof_required_fields.yaml
new file mode 100644
index 000000000..fe0eb532c
--- /dev/null
+++ b/tests/data/openapi/allof_required_fields.yaml
@@ -0,0 +1,25 @@
+openapi: "3.0.0"
+components:
+  schemas:
+    EmailMessage:
+      title: Email message
+      description: |
+        An email message. There must be at least one recipient in `to`, `cc`, or `bcc`.
+      type: object
+      required:
+        - allOf:
+            - message
+            - subject
+            - to
+      properties:
+        message:
+          type: string
+          description: The email message text.
+        subject:
+          type: string
+          description: The subject line of the email.
+        to:
+          type: array
+          description: A list of email addresses.
+          items:
+            type: string
diff --git a/tests/data/openapi/anyof_required.yaml b/tests/data/openapi/anyof_required.yaml
new file mode 100644
index 000000000..993e21b6c
--- /dev/null
+++ b/tests/data/openapi/anyof_required.yaml
@@ -0,0 +1,37 @@
+openapi: "3.0.0"
+components:
+  schemas:
+    EmailMessage:
+      title: Email message
+      description: |
+        An email message. There must be at least one recipient in `to`, `cc`, or `bcc`.
+      type: object
+      required:
+        - message
+        - subject
+        - anyOf:
+            - to
+            - cc
+            - bcc
+      properties:
+        bcc:
+          type: array
+          items:
+            type: string
+          description: A list of "blind carbon copy" email addresses.
+        cc:
+          type: array
+          items:
+            type: string
+          description: A list of "carbon copy" email addresses.
+        message:
+          type: string
+          description: The email message text.
+        subject:
+          type: string
+          description: The subject line of the email.
+        to:
+          type: array
+          description: A list of email addresses.
+          items:
+            type: string
diff --git a/tests/data/openapi/required_null.yaml b/tests/data/openapi/required_null.yaml
new file mode 100644
index 000000000..a007e13fa
--- /dev/null
+++ b/tests/data/openapi/required_null.yaml
@@ -0,0 +1,18 @@
+openapi: "3.0.0"
+components:
+  schemas:
+    ObjectBase:
+      description: Object schema
+      type: object
+      properties:
+        name:
+          description: Name of the object
+          type: string
+        type:
+          description: Object type
+          type: string
+          enum:
+            - my_first_object
+            - my_second_object
+            - my_third_object
+      required: null
diff --git a/tests/parser/test_openapi.py b/tests/parser/test_openapi.py
index 58bc8d091..263909a97 100644
--- a/tests/parser/test_openapi.py
+++ b/tests/parser/test_openapi.py
@@ -372,6 +372,14 @@ def test_openapi_parser_parse_anyof(tmp_path: Path, monkeypatch: pytest.MonkeyPa
     assert parser.parse() == (EXPECTED_OPEN_API_PATH / "openapi_parser_parse_anyof" / "output.py").read_text()
 
 
+def test_openapi_parser_parse_anyof_required(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
+    monkeypatch.chdir(tmp_path)
+    parser = OpenAPIParser(
+        Path(DATA_PATH / "anyof_required.yaml"),
+    )
+    assert parser.parse() == (EXPECTED_OPEN_API_PATH / "openapi_parser_parse_anyof_required" / "output.py").read_text()
+
+
 def test_openapi_parser_parse_nested_anyof(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
     monkeypatch.chdir(tmp_path)
     parser = OpenAPIParser(
@@ -415,6 +423,17 @@ def test_openapi_parser_parse_allof(tmp_path: Path, monkeypatch: pytest.MonkeyPa
     assert parser.parse() == (EXPECTED_OPEN_API_PATH / "openapi_parser_parse_allof" / "output.py").read_text()
 
 
+def test_openapi_parser_parse_allof_required_fields(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
+    monkeypatch.chdir(tmp_path)
+    parser = OpenAPIParser(
+        Path(DATA_PATH / "allof_required_fields.yaml"),
+    )
+    assert (
+        parser.parse()
+        == (EXPECTED_OPEN_API_PATH / "openapi_parser_parse_allof_required_fields" / "output.py").read_text()
+    )
+
+
 def test_openapi_parser_parse_alias(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
     monkeypatch.chdir(tmp_path)
     parser = OpenAPIParser(
@@ -501,6 +520,12 @@ def test_openapi_parser_parse_remote_ref(tmp_path: Path, monkeypatch: pytest.Mon
     assert parser.parse() == expected_file.read_text()
 
 
+def test_openapi_parser_parse_required_null(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
+    monkeypatch.chdir(tmp_path)
+    parser = OpenAPIParser(source=Path(DATA_PATH / "required_null.yaml"))
+    assert parser.parse() == (EXPECTED_OPEN_API_PATH / "openapi_parser_parse_required_null" / "output.py").read_text()
+
+
 def test_openapi_model_resolver(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
     monkeypatch.chdir(tmp_path)
     parser = OpenAPIParser(source=(DATA_PATH / "api.yaml"))