Invoke-RestMethod で日本語が文字化けする。送る側を UTF-8 にしても直らない

PowerShell 5.1 で FastAPI の日本語 JSON が文字化けする原因と直し方

Windows PowerShell 5.1 の Invoke-RestMethod で、日本語の JSON が「ã」で始まる文字列に化ける。原因はレスポンスの Content-Type に charset がないこと。FastAPI 側での直し方と、サーバーを触れないときの読み方。

公開
書き手
開発記録をもとに AI が書いた(本人の確認はまだ)
裏取り
公開前に AI が別に確かめた:PowerShell 5.1 と 7.6 で再現をやり直し、前回と同じ結果になった。Starlette 1.6.0 のソースで charset が text/ にだけ付くこと、FastAPI 0.141.1 のソースで既定のエラー応答が charset のない JSONResponse を返すことを確かめた。ISO-8859-1 で読むという公式の説明は見つけていない。(2026.10.14)
根拠
6 件

先に結論

  • Windows PowerShell 5.1 の Invoke-RestMethod は、Content-Type に charset がない JSON を UTF-8 として読まない。
  • FastAPI(Starlette)の JSON 応答は、既定では application/json だけで charset が付かない。
  • サーバー側で application/json; charset=utf-8 を返すようにすれば直る。エラー応答の分も忘れずに。
  • サーバーを触れないなら、生のバイト列を UTF-8 として読み直すか、PowerShell 7 を使う。

症状:GET でも化ける

自作のローカル API(FastAPI)を Windows PowerShell 5.1 から叩いたら、日本語が ã で始まる文字列になった。

最初は送る側を疑い、リクエストの本文を UTF-8 のバイト列にして送った。それでも直らなかった。決め手は、本文を持たない GET でも同じように化けたこと。送っていないものは化けようがないので、受け取る側の読み方の問題だと分かる。

原因:Content-Type に charset がない

FastAPI が返す JSON の本文は、ちゃんと UTF-8 だった。問題はヘッダーのほうで、Content-Type が application/json だけだった。

FastAPI の JSON 応答は Starlette の JSONResponse で、Starlette は text/ で始まる型にしか charset を足さない。application/json はそのまま出ていく。

PowerShell 5.1 は、charset がないと本文を UTF-8 として読まない。同じ本文を charset あり・なしで返して比べると、こうなった。

返したヘッダー PowerShell 5.1 PowerShell 7.6
application/json ã で始まる文字列に化けた 読めた
application/json; charset=utf-8 読めた 読めた

化けた文字を一文字ずつ見ると、「こ」の UTF-8 のバイト E3 81 93 が、そのまま U+00E3 U+0081 U+0093 の三文字になっていた。一バイトを一文字として読む ISO-8859-1(Latin-1)で読んだときと同じ形になる。

切り分けの順番

開発記録では、次の順に疑っていた。

  1. リクエスト本文のエンコード → GET でも化けるので違う
  2. データベースに保存した文字が壊れている → 保存されていたのは UTF-8 のままで、壊れていなかった
  3. レスポンスの Content-Type に charset がない → これだった

データベースは壊れていなかったので、書き換えずに済んだ。同じ症状のときは、ヘッダーを先に見ると早い。

直し方:FastAPI 側で charset を付ける

charset 付きの JSON 応答クラスを一つ作り、アプリ全体の既定にする。ルートごとに直すと漏れるので、まとめて変える。

from fastapi import FastAPI, Request
from fastapi.encoders import jsonable_encoder
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
from starlette.exceptions import HTTPException as StarletteHTTPException


class UTF8JSONResponse(JSONResponse):
    media_type = "application/json; charset=utf-8"


app = FastAPI(default_response_class=UTF8JSONResponse)


@app.exception_handler(StarletteHTTPException)
async def http_exception_handler(request: Request, exc: StarletteHTTPException):
    return UTF8JSONResponse(
        status_code=exc.status_code,
        content={"detail": exc.detail},
        headers=getattr(exc, "headers", None),
    )


@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
    return UTF8JSONResponse(status_code=422, content={"detail": jsonable_encoder(exc.errors())})

エラー応答も忘れない

default_response_class を変えただけでは、エラー応答は変わらない。FastAPI 0.141.1 で確かめると、普通の応答は charset=utf-8 付きになったが、HTTPException の 404 と、入力の検査で落ちた 422 は application/json のままだった。

エラーの detail に日本語を入れていると、そこだけ化ける。上のコードの二つの例外ハンドラーはそのためのもの。このコードで 200、401、404、422 のすべてが charset=utf-8 付きになることを確かめた。

サーバーを触れないとき

他人の API など、ヘッダーを変えられないときは、受け取る側で UTF-8 として読み直す。

$res = Invoke-WebRequest $url -UseBasicParsing
$json = [Text.Encoding]::UTF8.GetString($res.RawContentStream.ToArray()) | ConvertFrom-Json

PowerShell 5.1 でも、これで正しく読めた。PowerShell 7(7.6 で確認)なら、charset がなくてもそのまま読める。

よく見かける [Console]::OutputEncoding = [Text.Encoding]::UTF8 は、この症状には効かなかった。画面に出すときの設定で、応答の読み方は変わらないからだ。

確かめ方

応答のヘッダーを見る。

(Invoke-WebRequest $url -UseBasicParsing).Headers['Content-Type']

application/json だけなら、PowerShell 5.1 では化ける。application/json; charset=utf-8 になっていれば大丈夫。

似た罠:スクリプトファイルそのもの

この記事の再現用に、日本語のコメントを入れた .ps1 を BOM なしの UTF-8 で保存したら、PowerShell 5.1 では途中でエラーになった。PowerShell 7 では動いた。BOM 付きで保存し直すと、5.1 でも動いた。

応答の文字化けとは別の話だが、5.1 で日本語を扱うなら、スクリプトは BOM 付きで保存しておくと迷わない。

確かめた環境

項目 版
OS Windows 11(build 26200、ANSI コードページ 932)
Windows PowerShell 5.1.26100.9444
PowerShell 7.6.6
FastAPI / Starlette 0.141.1 / 1.6.0

再現に使ったサーバーは、Python 標準ライブラリの http.server で、同じ UTF-8 の本文を charset あり・なしの二通りで返すだけのもの。