Repository: honojs/hono (TypeScript)

Issue:
Validator doesn't parse body when request uses content-type application/json; charset=utf-8
### What version of Hono are you using?

4.5.1

### What runtime/platform is your app running on?

Cloudflare workers

### What steps can reproduce the bug?

There is a bit of a discussion on stack overflow about application/json content-type - if it's valid for it to indicate a charset or not (https://stackoverflow.com/questions/9254891/what-does-content-type-application-json-charset-utf-8-really-mean).

I noticed that `context.json()` returns a response with content-type with a charset included (https://github.com/honojs/hono/blob/main/src/context.ts#L733), but when we send a request with the exact content-type to the app which uses a validator, it fails. It seems to be caused by https://github.com/honojs/hono/blob/main/src/validator/validator.ts#L74 and the fact that `jsonRegex` doesn't include optional matching for a charset.

I prepared a code that demonstrates the issue and a test case.

File app.ts:
```
import { createRoute, OpenAPIHono, z } from '@hono/zod-openapi';

const app = new OpenAPIHono();

const RequestBodySchema = z
  .object({
    name: z.string(),
  })
  .openapi('RequestBody');

const ResponseBodySchema = z
  .object({
    status: z.string(),
  })
  .openapi('ResponseBody');

const route = createRoute({
  method: 'post',
  path: '/test',
  request: {
    body: {
      content: {
        'application/json': {
          schema: RequestBodySchema,
        },
      },
      required: true,
    },
  },
  responses: {
    200: {
      content: {
        'application/json': {
          schema: ResponseBodySchema,
        },
      },
      description: 'Successful response',
    },
  },
});

app.openapi(route, async (c) => {
  const { name } = c.req.valid('json');
  return c.json({
    status: 'ok',
  } satisfies z.infer<typeof ResponseBodySchema>);
});

export default app;

```

file app.test.ts:
```
import { describe, expect, test } from 'vitest';
import app from './app';

test('should return valid response for content-type application/json with a charset', async () => {
  const res = await app.request('/test', {
    method: 'post',
    body: JSON.stringify({
      name: 'John Doe',
    }),
    headers: {
      'content-type': 'application/json; charset=utf-8',
    },
  });
  console.log(await res.text());
  expect(res.status).toBe(200);
});
```



### What is the expected behavior?

I would expect the test to pass. Validator should accept both 'application/json' as well as 'application/json; charset=utf-8'

### What do you see instead?

Error - zod parsing fails, because it's called on empty object and the requests fails with status 400.

### Additional information

_No response_


Discussion:
Hi @bartekbp 

It is a bug that we can reproduce with this code:

```ts

import { Hono } from 'hono'
import { validator } from 'hono/validator'

const app = new Hono()

app.post(
  '/',
  validator('json', (v) => v),
  async (c) => {
    const { name } = c.req.valid('json')
    return c.json({
      name,
    })
  }
)

describe('Validator', () => {
  test('should return valid response for content-type application/json with a charset', async () => {
    const res = await app.request('/', {
      method: 'post',
      body: JSON.stringify({
        name: 'foo',
      }),
      headers: {
        'content-type': 'application/json; charset=utf-8',
      },
    })
    expect(res.status).toBe(200)
    expect(await res.json()).toEqual({ name: 'foo' })
  })
})
```

I'll fix it soon. Thank you very much!



Patch:
diff --git a/src/validator/validator.ts b/src/validator/validator.ts
index 076fccb4d..b9c346a35 100644
--- a/src/validator/validator.ts
+++ b/src/validator/validator.ts
@@ -23,8 +23,8 @@ export type ValidationFunction<
 // eslint-disable-next-line @typescript-eslint/no-explicit-any
 type ExcludeResponseType<T> = T extends Response & TypedResponse<any> ? never : T
 
-const jsonRegex = /^application\/([a-z-\.]+\+)?json$/
-const multipartRegex = /^multipart\/form-data(; boundary=[A-Za-z0-9'"()+_,\-./:=?]+)?$/
+const jsonRegex = /^application\/([a-z-\.]+\+)?json(;\s*[a-zA-Z0-9\-]+\=([^;]+))*$/
+const multipartRegex = /^multipart\/form-data(; boundary=[a-zA-Z0-9'"()+_,\-./:=?]+)?$/
 const urlencodedRegex = /^application\/x-www-form-urlencoded$/
 
 export const validator = <


Test patch:
diff --git a/src/validator/validator.test.ts b/src/validator/validator.test.ts
index de4ac8834..64788fdc9 100644
--- a/src/validator/validator.test.ts
+++ b/src/validator/validator.test.ts
@@ -136,6 +136,18 @@ describe('JSON', () => {
     expect(res.status).toBe(200)
   })
 
+  it('Should validate if Content-Type is a application/json with a charset', async () => {
+    const res = await app.request('http://localhost/post', {
+      method: 'POST',
+      headers: {
+        'Content-Type': 'application/json; charset=utf8',
+      },
+      body: JSON.stringify({ foo: 'bar' }),
+    })
+    expect(res.status).toBe(200)
+    expect(await res.json()).toEqual({ foo: 'bar' })
+  })
+
   it('Should validate if Content-Type is a subtype like application/merge-patch+json', async () => {
     const res = await app.request('http://localhost/post', {
       method: 'POST',