【PowerShell】でDBにPOSTした際に文字化けする問題の解決方法

# はじめに
個人開発をしている大学生やまいです。現在は読書家向けSNSを開発しており、本記事もそれの一環です。今後も本開発で学んだことを発信していくため、似たようなものを作りたいという方は追ってください。
この世には`curl`と`Invoke-RestMethod`で`POST`した際に日本語が文字化けするという問題があります。本記事ではその解決法を紹介します。

# 環境
<dl>
  <dt>OS</dt>
  <dd>Windows11</dd>
  <dt>Go</dt>
  <dd>1.23.2 windows/amd64</dd>
  <dt>PostgreSQL</dt>
  <dd>1.5.11</dd>
  <dt>PowerShell</dt>
  <dd>7.2.24</dd>
</dl>

# 発生した問題
一番最初に実行していたコマンドはこちらです。これでは`POST`したBookTitleとContentが???になってしまいます。
```shell
Invoke-RestMethod -Uri "http://localhost:8080/reviews" -Method Post -Headers @{"Content-Type"="application/json"} -Body (@{ BookTitle="吾輩は猫である"; Content="おもろかった"} | ConvertTo-Json -Depth 10 -Compress)
```

インサートした際の出力
```shell
ID BookTitle Content CreatedAt
-- --------- ------- ---------
 8 ???????   ??????  2025/03/06 13:52:32
```


今回のシステムでは`PowerShell`からリクエストを送信→`Go`のプログラムがそれを取得し`DB`に登録という形でインサートしています。

# 行った対策
## 1.SQLから直接INSERT
まずはどこで文字化けが起きているのか特定するために`PowerShell`から直接`DB`にインサートします。
```shell
docker exec -it book_review_db psql -U user -d book_review -c "INSERT INTO reviews (BookTitle, Content) VALUES ('吾輩は猫である', 'おもろかった');"
```
すると、文字化けせずにインサートすることができました。

## 2.Go側で受け取ったJSONデータを確認
前節でPostgreSQL側の設定が正しく、`PowerShell`から`Go`にリクエストを送信している部分か`Go`のプログラムが`DB`に登録している部分のどちらかで文字化けが起こっていることが確認できました。
`POST`メソッドを受け取る`Go`のプログラムに受け取ったJSONデータを確認するプログラムを追加します。
```Go:controllers/reviews.go
package controllers

import (
	"bytes"
	"dokusyo/backend/database"
	"dokusyo/backend/models"
	"fmt"
	"io"
	"net/http"

	"github.com/gin-gonic/gin"
)

// 感想一覧を取得
func GetReviews(c *gin.Context) {
    var reviews []models.Review
    database.DB.Find(&reviews)
    c.JSON(http.StatusOK, reviews)
}

// 新しい感想を投稿
func CreateReview(c *gin.Context) {
    var review models.Review

    // クライアントから送信された JSON を確認
    jsonData, _ := c.GetRawData()
    fmt.Println("Received JSON:", string(jsonData))

    // 読み取ったデータを元にリクエストのボディを再セット
    c.Request.Body = io.NopCloser(bytes.NewBuffer(jsonData))

    if err := c.ShouldBindJSON(&review); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    // Go 内部で受け取ったデータを確認
    fmt.Println("Parsed Review:", review.BookTitle, review.Content)

    database.DB.Create(&review)
    c.JSON(http.StatusCreated, review)
}
```
このプログラムににより、`Go`側のRecieved JSON:で????が表示、つまり`PowerShell`から送信した時点でJSONのエンコーディングが壊れていることが分かった。

## 3.ConvertTo-Jsonを使用
コマンドに`ConvertTo-Json`オプションを追加し実行。
```shell
Invoke-RestMethod -Uri "http://localhost:8080/reviews" -Method Post -Headers @{"Content-Type"="application/json; charset=utf-8"} -Body ($(@{ BookTitle="吾輩は猫である"; Content="おもろかった"} | ConvertTo-Json -Depth 10 -Compress))
```
結果変わらず、エンコーディングは崩れたまま?????が保存された

## 4.UTF-8にエンコードして送信（解決）
最初に`body`部分を定義、その後エンコードして送信する。
```shell
$body = '{ "BookTitle": "吾輩は猫である", "Content": "おもろかった" }';
$utf8Body = [System.Text.Encoding]::UTF8.GetBytes($body);
Invoke-RestMethod -Uri "http://localhost:8080/reviews" -Method Post -Headers @{"Content-Type"="application/json; charset=utf-8"} -Body $utf8Body
```
これによってエンコーディングは崩れずインサートすることができた。

# わかったこと
解決法として、先に`UTF-8`にエンコードしてから送信すると文字崩れが起きないことが分かった。
Microsoftの`Invoke-RestMethod`ページを参照したところ、`PowerShell7.4`以降では`UTF-8`に設定されていると書かれている。そこで`PowerShell`のバージョンを確認してみると...
```shell
$PSVersionTable.PSVersion
```
```shell
Major  Minor  Patch  PreReleaseLabel BuildLabel
-----  -----  -----  --------------- ----------
7      2      24
```
ｺﾚｶﾞｹﾞﾝｲﾝﾃﾞｽﾈｽﾐﾏｾﾝ

なお、`curl`コマンドを実行しても文字化けせずにインサートすることができる。
-X POSTを使うパターン
```shell
curl.exe --data '"{\"BookTitle\": \"吾輩は猫である\", \"Content\": \"おもろかった\"}"' --verbose --request POST --url "http://localhost:8080/reviews"
```
--jsonを使うパターン
```shell
curl --json '"{\"BookTitle\": \"吾輩は猫である\", \"Content\": \"おもろかった\"}"' http://localhost:8080/reviews
```

## 参考文献

https://learn.microsoft.com/ja-jp/powershell/module/microsoft.powershell.utility/invoke-restmethod?view=powershell-7.4#description

https://learn.microsoft.com/ja-jp/powershell/module/microsoft.powershell.utility/convertto-json?view=powershell-7.5

https://everything.curl.dev/http/post/json.html