diff --git a/src/fetch.ts b/src/fetch.ts index 086875a..281cbbb 100644 --- a/src/fetch.ts +++ b/src/fetch.ts @@ -106,7 +106,13 @@ export function createFetch (globalOptions: CreateFetchOptions): $Fetch { } if (ctx.options.body && isPayloadMethod(ctx.options.method)) { if (isJSONSerializable(ctx.options.body)) { - ctx.options.body = JSON.stringify(ctx.options.body) + // Automatically JSON stringify request bodies, when not already a string. + ctx.options.body = typeof ctx.options.body === 'string' + ? ctx.options.body + : JSON.stringify(ctx.options.body) + + // Set Content-Type and Accept headers to application/json by default + // for JSON serializable request bodies. ctx.options.headers = new Headers(ctx.options.headers) if (!ctx.options.headers.has('content-type')) { ctx.options.headers.set('content-type', 'application/json') diff --git a/test/index.test.ts b/test/index.test.ts index 1a6b7fe..d5c5fbb 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -1,6 +1,6 @@ import { listen } from 'listhen' import { getQuery, joinURL } from 'ufo' -import { createApp, useBody } from 'h3' +import { createApp, useBody, useRawBody } from 'h3' import { Blob } from 'fetch-blob' import { FormData } from 'formdata-polyfill/esm.min.js' import { describe, beforeEach, afterEach, it, expect } from 'vitest' @@ -20,6 +20,7 @@ describe('ohmyfetch', () => { res.setHeader('Content-Type', 'application/octet-stream') return new Blob(['binary']) }) + .use('/echo', async req => ({ body: await useRawBody(req) })) listener = await listen(app) }) @@ -73,6 +74,12 @@ describe('ohmyfetch', () => { } }) + it('does not stringify body when content type != application/json', async () => { + const msg = '"Hallo von Pascal"' + const { body } = await $fetch(getURL('echo'), { method: 'POST', body: msg, headers: { 'Content-Type': 'text/plain' } }) + expect(body).to.deep.eq(msg) + }) + it('Bypass FormData body', async () => { const data = new FormData() data.append('foo', 'bar')