📕
UnityでPythonサーバ(FastAPI)にJSON形式でPOST送信時の422エラー解決方法
前書き
Unity初心者の私が非常に悩まされたことをまとめて、今後の迷える子羊への参考になればいいと思い書きました。
至らぬ点やもっといい解決策があれば教えていただきたいです。
環境
- Unity:2022.3.8f1
 - Python:3.10.13
- FastAPI:0.103.0
 
 
解決できること
unity(c#)からJSON形式でPython(FastAPI)に送信
Python エラーと解決
FastAPIでPOSTリクエストで受け取るときには
エラー内容
POST送信された際422エラーが出力された
422エラーが出た時に使っていたコード
api.py
from fastapi import FastAPI
import subprocess
import os
@app.post("/text")
def testPost(text:str):
    return {"text":text}
    
if __name__ == "__main__":
    name, ex = os.path.splitext(os.path.basename(__file__))
    subprocess.run(f"uvicorn {name}:app --reload")
解決
- リクエストボディの定義
 - 422エラーのデバック
 
全体のコード
api.py
from pydantic import BaseModel
from fastapi import FastAPI, Request, status
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
import subprocess
import os
@app.exception_handler(RequestValidationError)
async def handler(request:Request, exc:RequestValidationError):
    print(exc)
    return JSONResponse(content={}, status_code=status.HTTP_422_UNPROCESSABLE_ENTITY)
class Item(BaseModel):
    text:str
@app.post("/text")
def testPost(data:Item):
    return {"text":data.text}
    
if __name__ == "__main__":
    name, ex = os.path.splitext(os.path.basename(__file__))
    subprocess.run(f"uvicorn {name}:app --reload")
1.リクエストボディの定義
POSTリクエストはデータを送信するためにリクエストボディ使用します。
リクエストボディを定義するにはpydanticライブラリを使います。
pydanticからBaseModelをインポートして継承したクラスを作成します。
pipでpydanticライブラリのインストール
pip install pydantic
api.py
from pydantic import BaseModel
from fastapi import FastAPI
import subprocess
import os
# リクエストボディ
class Item(BaseModel):
    text:str
@app.post("/text")
def testPost(data:Item):
    return {"text":data.text}
    
if __name__ == "__main__":
    name, ex = os.path.splitext(os.path.basename(__file__))
    subprocess.run(f"uvicorn {name}:app --reload")
ItemクラスにBaseModelを継承して使います。
2.422エラーのデバック
以下の記事を参考にコードを追記する
from pydantic import BaseModel
from fastapi import FastAPI, Request, status
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
app = FastAPI()
@app.exception_handler(RequestValidationError)
async def handler(request:Request, exc:RequestValidationError):
    print(exc)
    return JSONResponse(content={}, status_code=status.HTTP_422_UNPROCESSABLE_ENTITY)
# 以下コード
追記して422エラーになると以下ものが出ます.
msgには「値は有効な辞書ではない」と出ている
422エラー
[{'loc': ('body',), 'msg': 'value is not a valid dict', 'type': 'type_error.dict'}]
Unity エラーと解決
エラー内容
- APIに送信するデータが辞書型ではない
 - 辞書型で送信するのに
form.AddFieldを使うのを間違っていた 
TestPost.cs
using UnityEngine;
using UnityEngine.Networking;
using System.Collections;
using System.Collections.Generic;
using UnityEngine.UI;
public class TestPost : MonoBehaviour
{
    private string serverURL = "http://127.0.0.1:8000/"; // サーバーのURLに置き換える
    private void Start()
    {
        StartCoroutine("Test2voice");
    }
    IEnumerator Test2voice()
    {
        WWWForm form = new WWWForm();
	
	// ▼ここから▼
        form.AddField("text", "こんにちは、これはテストです");
        UnityWebRequest request = UnityWebRequest.Post(serverURL + "text", form);
        
        yield return request.SendWebRequest();
	// ▲ここまでが原因▲
        if (request.result == UnityWebRequest.Result.Success)
        {
            Debug.Log("フォームデータが正常に送信されました。");
            Debug.Log(request.downloadHandler.text);
            // 必要に応じてレスポンスを処理します。
        }
        else
        {
            Debug.LogError("フォームデータの送信中にエラーが発生しました: " + request.error);
        }
    }
}
解決
- JSONデータのクラスを作成
 - 
JsonUtility.ToJsonを使いstring型のJSON形式で返す - ASCIIコードで送信しないようにする
 
TestPost.cs
using UnityEngine;
using UnityEngine.Networking;
using System.Collections;
using System.Collections.Generic;
using UnityEngine.UI;
public class TestPost : MonoBehaviour
{
    private string serverURL = "http://127.0.0.1:8000/"; // サーバーのURLに置き換える
    // JSONデータのクラス
    [System.Serializable]
    private class RequestData
    {
        public string text;
    }
    private void Start()
    {
        StartCoroutine("Test2voice");
    }
    IEnumerator Test2voice()
    {
        RequestData requestData = new RequestData
        {
            text = "こんにちは、これはテストです",
        };
        // JSONデータを文字列に変換
        string json = JsonUtility.ToJson(requestData);
        // Debug.Log(json);
        WWWForm form = new WWWForm();
	// string型をbyte型配列に変換して送信
        byte[] postData = System.Text.Encoding.UTF8.GetBytes(json);
        var request = new UnityWebRequest(serverURL + "text", "POST");
        request.uploadHandler = (UploadHandler)new UploadHandlerRaw(postData);
        request.downloadHandler = (DownloadHandler)new DownloadHandlerBuffer();
        request.SetRequestHeader("Content-Type", "application/json");
        yield return request.Send();
        if (request.result == UnityWebRequest.Result.Success)
        {
            Debug.Log("フォームデータが正常に送信されました。");
            Debug.Log(request.downloadHandler.text);
            // 必要に応じてレスポンスを処理します。
        }
        else
        {
            Debug.LogError("フォームデータの送信中にエラーが発生しました: " + request.error);
        }
    }
}
new UnityWebRequest(serverURL + "text", "POST");
上記のコードのserverURL + "text"はFastAPIのhttp://127.0.0.1:8000/textに送信する
最後に
こういったエラーはbing aiやChatGPTに聞くのも、解決の糸口になるのでガンガン使っていきましょう。
Discussion