Repository: honojs/hono (2025-07-03T14:03:12Z)

Issue:
TypeError: Body is unusable
### What version of Hono are you using?

4.8.3

### What runtime/platform is your app running on? (with version if possible)

Node

### What steps can reproduce the bug?

Add 2x `zValidators` for path/middleware for json, header and try to access raw body text in route handler.

From my investigations, [this](https://github.com/honojs/hono/blob/main/src/validator/validator.ts#L78) is causing the error. If we clone the json using `await c.req.raw.clone().json()`, it works fine.

Example using create hono app:
```
import { serve } from "@hono/node-server";
import { Hono } from "hono";
import { z } from "zod";
import { zValidator } from "@hono/zod-validator";

const schema = z.object({  mock: z.string() });

const app = new Hono();

app
  .get("/", (c) => {
    return c.text("Hello Hono!");
  })
  .post(
    "/",
    zValidator("header", schema),
    zValidator("json", schema),
    async (c) => {
      // causes error
      console.log(await c.req.raw.clone().text());

      return c.text("Hello Hono!");
    }
  );

serve(
  {
    fetch: app.fetch,
    port: 3000,
  },
  (info) => {
    console.log(`Server is running on http://localhost:${info.port}`);
  }
);
```

### What is the expected behavior?

I'm able to use multiple zValidators - especially for json & header - and still access the raw body.

### What do you see instead?

```
TypeError: unusable
    at Request.clone (node:internal/deps/undici/undici:9942:17)
    at Request.value (file:///Users/pete/Development/poc/hono-bug/node_modules/.pnpm/@hono+node-server@1.14.4_hono@4.8.3/node_modules/@hono/node-server/dist/index.mjs:122:40)
    at <anonymous> (/Users/pete/Development/poc/hono-bug/src/index.ts:21:35)
    at dispatch (file:///Users/pete/Development/poc/hono-bug/node_modules/.pnpm/hono@4.8.3/node_modules/hono/dist/compose.js:22:23)
    at file:///Users/pete/Development/poc/hono-bug/node_modules/.pnpm/hono@4.8.3/node_modules/hono/dist/compose.js:22:46
    at file:///Users/pete/Development/poc/hono-bug/node_modules/.pnpm/hono@4.8.3/node_modules/hono/dist/validator/validator.js:81:11
    at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
    at async dispatch (file:///Users/pete/Development/poc/hono-bug/node_modules/.pnpm/hono@4.8.3/node_modules/hono/dist/compose.js:22:17)
    at async file:///Users/pete/Development/poc/hono-bug/node_modules/.pnpm/hono@4.8.3/node_modules/hono/dist/validator/validator.js:81:5
    at async dispatch (file:///Users/pete/Development/poc/hono-bug/node_modules/.pnpm/hono@4.8.3/node_modules/hono/dist/compose.js:22:17)
```

### Additional information

_No response_


Discussion:
Hi @piotrgrundas 

Can you use `c.req.text()` instead of `c.req.raw.clone().text()`? It uses cached content, and the error is not thrown.

> From my investigations, [this](https://github.com/honojs/hono/blob/main/src/validator/validator.ts#L78) is causing the error. If we clone the json using `await c.req.raw.clone().json()`, it works fine.

If you follow your way, the following code will throw the `Malformed JSON in request body` error.

```ts
import { Hono } from 'hono'
import { validator } from 'hono/validator'

const app = new Hono()

app.post(
  '/',
  async (c, next) => {
    await c.req.text()
    await next()
  },
  validator('json', () => {
    return {}
  }),
  async (c) => {
    return c.json(c.req.valid('json'))
  }
)

export default app
```

It caches the content to avoid that. So, please use `c.req.text()` or `c.req.json()` instead of `c.req.raw.clone().text()` or `c.req.raw.clone().json()`.
@yusukebeI going deeper after your suggestion, I think I found  the root cause. 
Generally, I'm using sth like this to validate JWKS using [jose](https://www.npmjs.com/package/jose) with `flattenedVerify`.

And the problem seems to be only upon applying validation to `json`.

With validator:
![Image](https://github.com/user-attachments/assets/1aec12be-551c-44c7-a0b5-bf02c8af0368)

Without validator:
![Image](https://github.com/user-attachments/assets/fd561f6a-e1ad-4112-9eec-81d860e1c9a9)

Notice how it strips the payload, which causes my problems around veryfication - is there any way around this?
@piotrgrundas 

Hmm. I don't know because it depends on the library. If you provide a minimal repo to reproduce, I may help you. But it should be minimal.
@yusukebe 

Are you asking for a minimal example using jose verification in relation to the validator? Just want to make sure I understand you correctly.

My main question is whether I can get the original payload unchanged when using the validator for JSON.
@piotrgrundas 

Honestly, I can't understand what you meant. You can get the validated value `c.req.valid('json')` if you use a validator. Or, you can get the value of the JSON body `c.req.json()` if you don't use a validator.
@yusukebe 

Please check the screenshots I've provided. If I use the validator for json, the text in `req.text()` is slightly malformed, which is the root of my problem. 
@piotrgrundas But it's a valid JSON.
@yusukebe But it's malformed, and in JOSE (specifically JWS), the payload must exactly match the original bytes that were signed—any deviation breaks the signature.
@piotrgrundas 

Okay. We don't expect the usage, but it can be fixed. I'm working on it.



Patch:
diff --git a/src/request.ts b/src/request.ts
index e93617c8b..5dc034d3f 100644
--- a/src/request.ts
+++ b/src/request.ts
@@ -243,7 +243,7 @@ export class HonoRequest<P extends string = '/', I extends Input['out'] = {}> {
    * ```
    */
   json<T = any>(): Promise<T> {
-    return this.#cachedBody('json')
+    return this.#cachedBody('text').then((text: string) => JSON.parse(text))
   }
 
   /**


Test patch:
diff --git a/src/request.test.ts b/src/request.test.ts
index 5e700059e..a278bead9 100644
--- a/src/request.test.ts
+++ b/src/request.test.ts
@@ -222,6 +222,18 @@ describe('Body methods with caching', () => {
     )
   })
 
+  test('req.json() should keep the content as is', async () => {
+    const text = '{ "foo" : "bar" }'
+    const req = new HonoRequest(
+      new Request('http://localhost', {
+        method: 'POST',
+        body: text,
+      })
+    )
+    expect(await req.json()).toEqual(JSON.parse(text))
+    expect(await req.text()).toEqual(text)
+  })
+
   test('req.arrayBuffer()', async () => {
     const buffer = new TextEncoder().encode('{"foo":"bar"}').buffer
     const req = new HonoRequest(