Repository: sanic-org/sanic

Issue:
JSONResponse defaults to None content-type
### Is there an existing issue for this?

- [X] I have searched the existing issues

### Describe the bug

```python
@app.get("/")
async def handler(request: Request):
    return JSONResponse({"message": "Hello World!"})
```
```sh
╰─▶ curl localhost:9999   
HTTP/1.1 200 OK
content-length: 26
connection: keep-alive
alt-svc: 
content-type: None

{"message":"Hello World!"}
```

### Code snippet

_No response_

### Expected Behavior

`content-type: application/json`

### How do you run Sanic?

Sanic CLI

### Operating System

all

### Sanic Version

LTS+

### Additional context

_No response_

Discussion:
codecov[bot]: ## [Codecov](https://codecov.io/gh/sanic-org/sanic/pull/2738?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sanic-org) Report
Patch coverage has no change and project coverage change: **`-0.060`** :warning:
> Comparison is base [(`4ad8168`)](https://codecov.io/gh/sanic-org/sanic/commit/4ad8168bb016cef19213cd3db2b12efb1a4dcb30?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sanic-org) 88.650% compared to head [(`6b9c1cc`)](https://codecov.io/gh/sanic-org/sanic/pull/2738?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sanic-org) 88.591%.

<details><summary>Additional details and impacted files</summary>


```diff
@@              Coverage Diff               @@
##           22.12LTS     #2738       +/-   ##
==============================================
- Coverage    88.650%   88.591%   -0.060%     
==============================================
  Files            82        82               
  Lines          6767      6767               
  Branches       1156      1156               
==============================================
- Hits           5999      5995        -4     
- Misses          531       533        +2     
- Partials        237       239        +2     
```


| [Impacted Files](https://codecov.io/gh/sanic-org/sanic/pull/2738?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sanic-org) | Coverage Δ | |
|---|---|---|
| [sanic/response/types.py](https://codecov.io/gh/sanic-org/sanic/pull/2738?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sanic-org#diff-c2FuaWMvcmVzcG9uc2UvdHlwZXMucHk=) | `84.705% <ø> (ø)` | |

... and [1 file with indirect coverage changes](https://codecov.io/gh/sanic-org/sanic/pull/2738/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sanic-org)

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sanic-org). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sanic-org)

</details>

[:umbrella: View full report in Codecov by Sentry](https://codecov.io/gh/sanic-org/sanic/pull/2738?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sanic-org).   
:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sanic-org).

prryplatypus: Linter errors seem unrelated
ahopkins: Can we get the linter issue fixed?
prryplatypus: > Can we get the linter issue fixed?

Done. Apparently just had to run `black` :)

Patch:
diff --git a/sanic/app.py b/sanic/app.py
index 0d232d6e..f7e0ff7e 100644
--- a/sanic/app.py
+++ b/sanic/app.py
@@ -894,7 +894,6 @@ class Sanic(BaseSanic, StartupMixin, metaclass=TouchUpMeta):
         ] = None
         run_middleware = True
         try:
-
             await self.dispatch(
                 "http.routing.before",
                 inline=True,
@@ -926,7 +925,6 @@ class Sanic(BaseSanic, StartupMixin, metaclass=TouchUpMeta):
                 and request.stream.request_body
                 and not route.extra.ignore_body
             ):
-
                 if hasattr(handler, "is_stream"):
                     # Streaming handler: lift the size limit
                     request.stream.request_max_size = float("inf")
diff --git a/sanic/constants.py b/sanic/constants.py
index ab23c86d..97b41cd6 100644
--- a/sanic/constants.py
+++ b/sanic/constants.py
@@ -4,7 +4,6 @@ from sanic.compat import UpperStrEnum
 
 
 class HTTPMethod(UpperStrEnum):
-
     GET = auto()
     POST = auto()
     PUT = auto()
@@ -15,7 +14,6 @@ class HTTPMethod(UpperStrEnum):
 
 
 class LocalCertCreator(UpperStrEnum):
-
     AUTO = auto()
     TRUSTME = auto()
     MKCERT = auto()
diff --git a/sanic/http/tls/creators.py b/sanic/http/tls/creators.py
index 8ffc8650..ad3b1b4f 100644
--- a/sanic/http/tls/creators.py
+++ b/sanic/http/tls/creators.py
@@ -126,7 +126,6 @@ class CertCreator(ABC):
         local_tls_key,
         local_tls_cert,
     ) -> CertCreator:
-
         creator: Optional[CertCreator] = None
 
         cert_creator_options: Tuple[
diff --git a/sanic/mixins/startup.py b/sanic/mixins/startup.py
index a6126433..1e463bd7 100644
--- a/sanic/mixins/startup.py
+++ b/sanic/mixins/startup.py
@@ -1109,7 +1109,6 @@ class StartupMixin(metaclass=SanicMeta):
         app: StartupMixin,
         server_info: ApplicationServerInfo,
     ) -> None:  # no cov
-
         try:
             # We should never get to this point without a server
             # This is primarily to keep mypy happy
diff --git a/sanic/request.py b/sanic/request.py
index 592869e5..bd7b5663 100644
--- a/sanic/request.py
+++ b/sanic/request.py
@@ -146,7 +146,6 @@ class Request:
         head: bytes = b"",
         stream_id: int = 0,
     ):
-
         self.raw_url = url_bytes
         try:
             self._parsed_url = parse_url(url_bytes)
diff --git a/sanic/response/types.py b/sanic/response/types.py
index fe3c941e..9ebdb0d6 100644
--- a/sanic/response/types.py
+++ b/sanic/response/types.py
@@ -232,7 +232,7 @@ class JSONResponse(HTTPResponse):
         body: Optional[Any] = None,
         status: int = 200,
         headers: Optional[Union[Header, Dict[str, str]]] = None,
-        content_type: Optional[str] = None,
+        content_type: str = "application/json",
         dumps: Optional[Callable[..., str]] = None,
         **kwargs: Any,
     ):
diff --git a/sanic/server/legacy.py b/sanic/server/legacy.py
index 824287e5..a8018ce2 100644
--- a/sanic/server/legacy.py
+++ b/sanic/server/legacy.py
@@ -94,7 +94,6 @@ def watchdog(sleep_interval, reload_dirs):
 
     try:
         while True:
-
             changed = set()
             for filename in itertools.chain(
                 _iter_module_files(),
diff --git a/sanic/server/websockets/frame.py b/sanic/server/websockets/frame.py
index b31e93c1..130dc5fa 100644
--- a/sanic/server/websockets/frame.py
+++ b/sanic/server/websockets/frame.py
@@ -52,7 +52,6 @@ class WebsocketFrameAssembler:
         paused: bool
 
     def __init__(self, protocol) -> None:
-
         self.protocol = protocol
 
         self.read_mutex = asyncio.Lock()
diff --git a/sanic/server/websockets/impl.py b/sanic/server/websockets/impl.py
index 5e258e97..2125faa7 100644
--- a/sanic/server/websockets/impl.py
+++ b/sanic/server/websockets/impl.py
@@ -686,7 +686,6 @@ class WebsocketImplProtocol:
         :raises TypeError: for unsupported inputs
         """
         async with self.conn_mutex:
-
             if self.ws_proto.state in (CLOSED, CLOSING):
                 raise WebsocketClosed(
                     "Cannot write to websocket interface after it is closed."
diff --git a/sanic/touchup/meta.py b/sanic/touchup/meta.py
index 682600d6..011570da 100644
--- a/sanic/touchup/meta.py
+++ b/sanic/touchup/meta.py
@@ -11,7 +11,6 @@ class TouchUpMeta(SanicMeta):
         methods = attrs.get("__touchup__")
         attrs["__touched__"] = False
         if methods:
-
             for method in methods:
                 if method not in attrs:
                     raise SanicException(
diff --git a/sanic/utils.py b/sanic/utils.py
index 51d94d08..76bf003c 100644
--- a/sanic/utils.py
+++ b/sanic/utils.py
@@ -75,7 +75,6 @@ def load_module_from_file_location(
         location = location.decode(encoding)
 
     if isinstance(location, Path) or "/" in location or "$" in location:
-
         if not isinstance(location, Path):
             # A) Check if location contains any environment variables
             #    in format ${some_env_var}.


Test patch:
diff --git a/tests/test_response_json.py b/tests/test_response_json.py
index c89dba42..aa1c61dd 100644
--- a/tests/test_response_json.py
+++ b/tests/test_response_json.py
@@ -213,3 +213,12 @@ def test_pop_list(json_app: Sanic):
 
     _, resp = json_app.test_client.get("/json-pop")
     assert resp.body == json_dumps(["b"]).encode()
+
+
+def test_json_response_class_sets_proper_content_type(json_app: Sanic):
+    @json_app.get("/json-class")
+    async def handler(request: Request):
+        return JSONResponse(JSON_BODY)
+
+    _, resp = json_app.test_client.get("/json-class")
+    assert resp.headers["content-type"] == "application/json"