Repository: litestar-org/litestar

Issue:
Bug: CLI Schema Export fails
### Description

Exporting the Litestar schema via CLI fails presumably because the `json.dumps` can't serialize certain field types such as `UUID` or `datetime`.

> [!IMPORTANT]  
> This is only when `create_examples` is `True`

### URL to code causing the issue

_No response_

### MCVE

```python
from datetime import datetime
from litestar import Litestar, get
from litestar.openapi import OpenAPIConfig

@get()
async def something(date: datetime) -> None:
    return None

app = Litestar([something], openapi_config=OpenAPIConfig('example', '0.0.1', True))
```

or

```python
from __future__ import annotations
from datetime import datetime
from uuid import UUID
from litestar import Litestar, get
from litestar.openapi import OpenAPIConfig
from pydantic import BaseModel, Field

class FileSchema(BaseModel):
    id: UUID
    updated: datetime

@get()
async def something() -> FileSchema | None:
    return None

app = Litestar([something], openapi_config=OpenAPIConfig("example", "0.0.1", True))
```


### Steps to reproduce

```bash
1. Create mcve.py
2. `litestar schema openapi --output schema.json`
3. See error
```


### Screenshots

_No response_

### Logs

_No response_

### Litestar Version

2.x

### Platform

- [ ] Linux
- [ ] Mac
- [ ] Windows
- [X] Other (Please specify in the description above)

<!-- POLAR PLEDGE BADGE START -->
---
> [!NOTE]  
> While we are open for sponsoring on [GitHub Sponsors](https://github.com/sponsors/litestar-org/) and 
> [OpenCollective](https://opencollective.com/litestar), we also utilize [Polar.sh](https://polar.sh/) to engage in pledge-based sponsorship.
>
> Check out all issues funded or available for funding [on our Polar.sh Litestar dashboard](https://polar.sh/litestar-org)
> * If you would like to see an issue prioritized, make a pledge towards it!
> * We receive the pledge once the issue is completed & verified
> * This, along with engagement in the community, helps us know which features are a priority to our users.

<a href="https://polar.sh/litestar-org/litestar/issues/2575">
<picture>
  <source media="(prefers-color-scheme: dark)" srcset="https://polar.sh/api/github/litestar-org/litestar/issues/2575/pledge.svg?darkmode=1">
  <img alt="Fund with Polar" src="https://polar.sh/api/github/litestar-org/litestar/issues/2575/pledge.svg">
</picture>
</a>
<!-- POLAR PLEDGE BADGE END -->



Discussion:

Resolving patch:
diff --git a/litestar/cli/commands/schema.py b/litestar/cli/commands/schema.py
--- a/litestar/cli/commands/schema.py
+++ b/litestar/cli/commands/schema.py
@@ -1,7 +1,7 @@
-from json import dumps
 from pathlib import Path
 from typing import TYPE_CHECKING
 
+import msgspec
 from yaml import dump as dump_yaml
 
 from litestar import Litestar
@@ -9,6 +9,7 @@
     convert_openapi_to_typescript,
 )
 from litestar.cli._utils import JSBEAUTIFIER_INSTALLED, RICH_CLICK_INSTALLED, LitestarCLIException, LitestarGroup
+from litestar.serialization import encode_json, get_serializer
 
 if TYPE_CHECKING or not RICH_CLICK_INSTALLED:  # pragma: no cover
     from click import Path as ClickPath
@@ -31,6 +32,27 @@ def schema_group() -> None:
     """Manage server-side OpenAPI schemas."""
 
 
+def _generate_openapi_schema(app: Litestar, output: Path) -> None:
+    """Generate an OpenAPI Schema."""
+    serializer = get_serializer(app.type_encoders)
+    if output.suffix in (".yml", ".yaml"):
+        content = dump_yaml(
+            msgspec.to_builtins(app.openapi_schema.to_schema(), enc_hook=serializer),
+            default_flow_style=False,
+            encoding="utf-8",
+        )
+    else:
+        content = msgspec.json.format(
+            encode_json(app.openapi_schema.to_schema(), serializer=serializer),
+            indent=4,
+        )
+
+    try:
+        output.write_bytes(content)
+    except OSError as e:  # pragma: no cover
+        raise LitestarCLIException(f"failed to write schema to path {output}") from e
+
+
 @schema_group.command("openapi")  # type: ignore
 @option(
     "--output",
@@ -41,15 +63,7 @@ def schema_group() -> None:
 )
 def generate_openapi_schema(app: Litestar, output: Path) -> None:
     """Generate an OpenAPI Schema."""
-    if output.suffix in (".yml", ".yaml"):
-        content = dump_yaml(app.openapi_schema.to_schema(), default_flow_style=False)
-    else:
-        content = dumps(app.openapi_schema.to_schema(), indent=4)
-
-    try:
-        output.write_text(content)
-    except OSError as e:  # pragma: no cover
-        raise LitestarCLIException(f"failed to write schema to path {output}") from e
+    _generate_openapi_schema(app, output)
 
 
 @schema_group.command("typescript")  # type: ignore


Test patch:
diff --git a/tests/unit/test_cli/test_schema_commands.py b/tests/unit/test_cli/test_schema_commands.py
--- a/tests/unit/test_cli/test_schema_commands.py
+++ b/tests/unit/test_cli/test_schema_commands.py
@@ -1,14 +1,18 @@
 from __future__ import annotations
 
 from json import dumps as json_dumps
-from typing import TYPE_CHECKING
+from typing import TYPE_CHECKING, Callable
 
 import pytest
 from yaml import dump as dump_yaml
 
+from litestar.cli.commands.schema import _generate_openapi_schema
 from litestar.cli.main import litestar_group as cli_command
 
 if TYPE_CHECKING:
+    from pathlib import Path
+    from types import ModuleType
+
     from click.testing import CliRunner
     from pytest import MonkeyPatch
     from pytest_mock import MockerFixture
@@ -19,7 +23,7 @@ def test_openapi_schema_command(
     runner: CliRunner, mocker: MockerFixture, monkeypatch: MonkeyPatch, filename: str
 ) -> None:
     monkeypatch.setenv("LITESTAR_APP", "test_apps.openapi_test_app.main:app")
-    mock_path_write_text = mocker.patch("pathlib.Path.write_text")
+    mock_path_write_bytes = mocker.patch("pathlib.Path.write_bytes")
     command = "schema openapi"
 
     from test_apps.openapi_test_app.main import app as openapi_test_app
@@ -27,15 +31,35 @@ def test_openapi_schema_command(
     assert openapi_test_app.openapi_schema
     schema = openapi_test_app.openapi_schema.to_schema()
 
-    expected_content = json_dumps(schema, indent=4)
+    expected_content = json_dumps(schema, indent=4).encode()
     if filename:
         command += f" --output {filename}"
         if filename.endswith(("yaml", "yml")):
-            expected_content = dump_yaml(schema, default_flow_style=False)
+            expected_content = dump_yaml(schema, default_flow_style=False, encoding="utf-8")
 
     result = runner.invoke(cli_command, command)
     assert result.exit_code == 0
-    mock_path_write_text.assert_called_once_with(expected_content)
+    mock_path_write_bytes.assert_called_once_with(expected_content)
+
+
+@pytest.mark.parametrize("suffix", ("json", "yaml", "yml"))
+def test_schema_export_with_examples(suffix: str, create_module: Callable[[str], ModuleType], tmp_path: Path) -> None:
+    module = create_module(
+        """
+from datetime import datetime
+from litestar import Litestar, get
+from litestar.openapi import OpenAPIConfig
+
+@get()
+async def something(date: datetime) -> None:
+    return None
+
+app = Litestar([something], openapi_config=OpenAPIConfig('example', '0.0.1', True))
+    """
+    )
+    pth = tmp_path / f"openapi.{suffix}"
+    _generate_openapi_schema(module.app, pth)
+    assert pth.read_text()
 
 
 @pytest.mark.parametrize(