LoginSignup
1
2

【Unity】JSONデータを送信するときの処理メモ

Last updated at Posted at 2023-07-29

詰まったこと

Unityクライアントアプリから、Laravelで開発しているサーバサイドへjson文字列を送る処理で
WWWFormにJsonUtilityでJSON文字にしたデータを送ろうとしたとき、サーバで受け取る時には抜け落ちていた。
原因と対応策についての備忘録。

#事象発生時の実装

スキルの効果量を計算するため、パーティメンバの現在のステータス、敵のステータス、補正を掛けるときに使う数値等々をクラスで持っており、JsonUtilityで文字列を生成する。


ownerJson = JsonUtility.ToJson(owner);
scoreJson = JJsonUtility.ToJson(new ScoreInfo());
partyJson = "[";
enemyJson = "[";

int pIndex = 0;
party.ForEach(p =>
{
    if (pIndex > 0) partyJson += ",";
    partyJson += JsonConvert.SerializeObject(p);
    pIndex++;
});
partyJson += "]";

int eIndex = 0;
enemyParty.ForEach(e =>
{
    if (eIndex > 0) enemyJson += ",";
    enemyJson += JsonConvert.SerializeObject(e);
    eIndex++;
});
enemyJson += "]";

skillJson = JsonConvert.SerializeObject(skill);

POST通信でJSON文字列を送る時の処理。WWWFormを作成し、UploadHandlerRawでbodyにセット

 public async Task<List<string>> getBattleEffectVal(
        string ownerJson,
        string partyJson, 
        string enemyJson, 
        string scoreJson,
        string skillJson,
        int skillCount,
        string dataTable)
    {
        string url = apiPath + API_BATTLE_EFFECT_CALC;

        WWWForm form = new WWWForm();
        form.AddField("owner", ownerJson);
        form.AddField("party", partyJson);
        form.AddField("enemy", enemyJson);
        form.AddField("dataTable", dataTable);
        form.AddField("score", scoreJson);
        form.AddField("skill", skillJson);
        form.AddField("skillCount", skillCount);

        return await sendApi<string>(url, false, form);
    }

    /// <summary>
    /// APIリクエストを投げる
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="url">APIリクエストURL</param>
    /// <param name="isGet">GET通信か</param>
    /// <param name="form">Form情報</param>
    /// <param name="bodyBytes">Bodyに設定するByte情報</param>
    /// <returns></returns>
    private async Task<List<T>> sendApi<T>(string url, bool isGet, WWWForm form)
    {
        AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);

        List<T> responses = new List<T>();
        UnityWebRequest request = isGet ? UnityWebRequest.Get(url) : UnityWebRequest.Post(url, form);

        // 識別番号を渡してアセットバンドル名を取得
        request.SetRequestHeader("Content-Type", "application/json");
        request.SetRequestHeader("Accept", "application/json");
        request.SetRequestHeader("Authorization", "Bearer " + User.ApiToken);

        if(form != null)
        {
            request.uploadHandler = new UploadHandlerRaw(form.data);
        }

        await request.SendWebRequest();

        if (request.result == UnityWebRequest.Result.ConnectionError)
        {
            Debug.Log($"通信エラー:{request.method}  {url} {request.responseCode}");
            Debug.Log(request.result);
            Debug.Log(request.error);
            Debug.Log(request.downloadHandler.text.ToString());
            Debug.Log(form.data);

            return null;
        }
//後続処理

この状態で送信したときのbody情報

Larabel側に出ていたデバッグログ

~~ヘッダ情報等など~~
"x-unity-version":["2022.1.20f1"],
"content-length":["10932"]},
"body":[]} ※空!

生成したJSON文字をPostmanでBodyにセットした場合はしっかり出ている。

"content-length":["7055"]},
"body":{"owner":"{\"status\":{\"characterId\":0,\"level\":1,\"discription\":\"\",\"health\":{\"maxHealth\":{\"value\":100,\"valueDouble\":0.0},\"health\":{\"value\":10,\"valueDouble\":0.0}},\"power\":{\"value\":0,\"valueDouble\":0.0,\"powerEffect\":0},\"mental\":{\"value\":0,\"valueDouble\":0.0,\"mentalEffect\":0},\"critical\":{\"value\":0,\"valueDouble\":0.0,\"criticalEffect\":0.0},\"rebornCount\":{\"maxRebornCount\":0,\"rebornCount\":0}},\"nomalSkill\":{\"skillName\":\"\",\"discription\":\"\",\"count\":0,\"battleType\":1,\"useType\":1,\"actions\":[{\"instanceID\":31862}]},\"actionSkill\":{\"skillName\":\"\",\"discription\":\"\",\"count\":0,\"battleType\":1,\"useType\":2,\"actions\":[]},\"partyIndex\":0,\"race\":1,\"isPlayer\":true,\"characterClass\":1}","party":"[{\"status\":{\"characterId\":0,\"level\":1,\"discription\":\"\",\"health\":{\"maxHealth\":{\"value\":100,\"valueDouble\":0.0},\"health\":{\"value\":10,\"valueDouble\":0.0}},\"power\":{\"value\":0,\"valueDouble\":0.0,\"powerEffect\":0},\"mental\":{\"value\":0,\"valueDouble\":0.0,\"mentalEffect\":0},\"critical\":{\"value\":0,\"valueDouble\":0.0,\"criticalEffect\":0.0},\"rebornCount\":{\"maxRebornCount\":0,\"rebornCount\":0}},\"nomalSkill\":{\"skillName\":\"\",\"discription\":\"\",\"count\":0,\"battleType\":1,\"useType\":1,\"actions\":[{\"instanceID\":31862}]},\"actionSkill\":{\"skillName\":\"\",\"discription\":\"\",\"count\":0,\"battleType\":1,\"useType\":2,\"actions\":[]},\"partyIndex\":0,\"race\":1,\"isPlayer\":true,\"characterClass\":1},{\"status\":{\"characterId\":0,\"level\":2,\"discription\":\"\",\"health\":{\"maxHealth\":{\"value\":100,\"valueDouble\":0.0},\"health\":{\"value\":10,\"valueDouble\":0.0}},\"power\":{\"value\":0,\"valueDouble\":0.0,\"powerEffect\":0},\"mental\":{\"value\":0,\"valueDouble\":0.0,\"mentalEffect\":0},\"critical\":{\"value\":0,\"valueDouble\":0.0,\"criticalEffect\":0.0},\"rebornCount\":{\"maxRebornCount\":0,\"rebornCount\":0}},\"nomalSkill\":{\"skillName\":\"\",\"discription\":\"\",\"count\":0,\"battleType\":5,\"useType\":1,\"actions\":[]},\"actionSkill\":{\"skillName\":\"\",\"discription\":\"\",\"count\":0,\"battleType\":5,\"useType\":1,\"actions\":[]},\"partyIndex\":1,\"race\":3,\"isPlayer\":false,\"characterClass\":52},{\"status\":{\"characterId\":0,\"level\":2,\"discription\":\"\",\"health\":{\"maxHealth\":{\"value\":100,\"valueDouble\":0.0},\"health\":{\"value\":10,\"valueDouble\":0.0}},\"power\":{\"value\":0,\"valueDouble\":0.0,\"powerEffect\":0},\"mental\":{\"value\":0,\"valueDouble\":0.0,\"mentalEffect\":0},\"critical\":{\"value\":0,\"valueDouble\":0.0,\"criticalEffect\":0.0},\"rebornCount\":{\"maxRebornCount\":0,\"rebornCount\":0}},\"nomalSkill\":{\"skillName\":\"\",\"discription\":\"\",\"count\":0,\"battleType\":5,\"useType\":1,\"actions\":[]},\"actionSkill\":{\"skillName\":\"\",\"discription\":\"\",\"count\":0,\"battleType\":5,\"useType\":1,\"actions\":[]},\"partyIndex\":2,\"race\":2,\"isPlayer\":false,\"characterClass\":11},{\"status\":{\"characterId\":0,\"level\":2,\"discription\":\"\",\"health\":{\"maxHealth\":{\"value\":100,\"valueDouble\":0.0},\"health\":{\"value\":10,\"valueDouble\":0.0}},\"power\":{\"value\":0,\"valueDouble\":0.0,\"powerEffect\":0},\"mental\":{\"value\":0,\"valueDouble\":0.0,\"mentalEffect\":0},\"critical\":{\"value\":0,\"valueDouble\":0.0,\"criticalEffect\":0.0},\"rebornCount\":{\"maxRebornCount\":0,\"rebornCount\":0}},\"nomalSkill\":{\"skillName\":\"\",\"discription\":\"\",\"count\":0,\"battleType\":5,\"useType\":1,\"actions\":[]},\"actionSkill\":{\"skillName\":\"\",\"discription\":\"\",\"count\":0,\"battleType\":5,\"useType\":1,\"actions\":[]},\"partyIndex\":3,\"race\":2,\"isPlayer\":false,\"characterClass\":50}]","enemy":"[{\"status\":{\"characterId\":0,\"level\":1,\"discription\":\"\",\"health\":{\"maxHealth\":{\"value\":100,\"valueDouble\":0.0},\"health\":{\"value\":10,\"valueDouble\":0.0}},\"power\":{\"value\":0,\"valueDouble\":0.0,\"powerEffect\":0},\"mental\":{\"value\":0,\"valueDouble\":0.0,\"mentalEffect\":0},\"critical\":{\"value\":0,\"valueDouble\":0.0,\"criticalEffect\":0.0},\"rebornCount\":{\"maxRebornCount\":0,\"rebornCount\":0}},\"nomalSkill\":{\"skillName\":\"\",\"discription\":\"\",\"count\":0,\"battleType\":5,\"useType\":1,\"actions\":[]},\"actionSkill\":{\"skillName\":\"\",\"discription\":\"\",\"count\":0,\"battleType\":5,\"useType\":1,\"actions\":[]},\"partyIndex\":0,\"race\":2,\"isPlayer\":false,\"characterClass\":0},{\"status\":{\"characterId\":0,\"level\":2,\"discription\":\"\",\"health\":{\"maxHealth\":{\"value\":100,\"valueDouble\":0.0},\"health\":{\"value\":10,\"valueDouble\":0.0}},\"power\":{\"value\":0,\"valueDouble\":0.0,\"powerEffect\":0},\"mental\":{\"value\":0,\"valueDouble\":0.0,\"mentalEffect\":0},\"critical\":{\"value\":0,\"valueDouble\":0.0,\"criticalEffect\":0.0},\"rebornCount\":{\"maxRebornCount\":0,\"rebornCount\":0}},\"nomalSkill\":{\"skillName\":\"\",\"discription\":\"\",\"count\":0,\"battleType\":5,\"useType\":1,\"actions\":[]},\"actionSkill\":{\"skillName\":\"\",\"discription\":\"\",\"count\":0,\"battleType\":5,\"useType\":1,\"actions\":[]},\"partyIndex\":1,\"race\":2,\"isPlayer\":false,\"characterClass\":0},{\"status\":{\"characterId\":0,\"level\":3,\"discription\":\"\",\"health\":{\"maxHealth\":{\"value\":100,\"valueDouble\":0.0},\"health\":{\"value\":10,\"valueDouble\":0.0}},\"power\":{\"value\":0,\"valueDouble\":0.0,\"powerEffect\":0},\"mental\":{\"value\":0,\"valueDouble\":0.0,\"mentalEffect\":0},\"critical\":{\"value\":0,\"valueDouble\":0.0,\"criticalEffect\":0.0},\"rebornCount\":{\"maxRebornCount\":0,\"rebornCount\":0}},\"nomalSkill\":{\"skillName\":\"\",\"discription\":\"\",\"count\":0,\"battleType\":5,\"useType\":1,\"actions\":[]},\"actionSkill\":{\"skillName\":\"\",\"discription\":\"\",\"count\":0,\"battleType\":5,\"useType\":1,\"actions\":[]},\"partyIndex\":2,\"race\":2,\"isPlayer\":false,\"characterClass\":0},{\"status\":{\"characterId\":0,\"level\":5,\"discription\":\"\",\"health\":{\"maxHealth\":{\"value\":100,\"valueDouble\":0.0},\"health\":{\"value\":10,\"valueDouble\":0.0}},\"power\":{\"value\":0,\"valueDouble\":0.0,\"powerEffect\":0},\"mental\":{\"value\":0,\"valueDouble\":0.0,\"mentalEffect\":0},\"critical\":{\"value\":0,\"valueDouble\":0.0,\"criticalEffect\":0.0},\"rebornCount\":{\"maxRebornCount\":0,\"rebornCount\":0}},\"nomalSkill\":{\"skillName\":\"\",\"discription\":\"\",\"count\":0,\"battleType\":5,\"useType\":1,\"actions\":[]},\"actionSkill\":{\"skillName\":\"\",\"discription\":\"\",\"count\":0,\"battleType\":5,\"useType\":1,\"actions\":[]},\"partyIndex\":3,\"race\":2,\"isPlayer\":false,\"characterClass\":0}]","score":"{\"judge\":[{\"judgeName\":\"BAD\",\"score\":0,\"lernScore\":[0,0,0,0]},{\"judgeName\":\"GOOD\",\"score\":0,\"lernScore\":[0,0,0,0]},{\"judgeName\":\"GREAT\",\"score\":0,\"lernScore\":[0,0,0,0]},{\"judgeName\":\"PARF\",\"score\":0,\"lernScore\":[0,0,0,0]},{\"judgeName\":\"MISS\",\"score\":0,\"lernScore\":[0,0,0,0]}],\"combo\":{\"combo\":0,\"maxCombo\":0,\"lernCombo\":[0,0,0,0],\"lernMaxCombo\":[0,0,0,0]},\"score\":0.0}","skill":"{\"actionName\":\"単体攻撃\",\"discription\":\"敵一体に3倍攻撃\",\"skillType\":1,\"side\":false,\"args\":[3.0],\"results\":[],\"dataTable\":\"{owner.pow}*{skill.args.0}\"}","skillCount":"10","dataTable":"{owner.pow}*{skill.args.0}"}} 

[2023-07-23 22:05:47] local.INFO: Response: {"status_code":200,"headers":{"cache-control":["no-cache, private"],
"date":["Sun, 23 Jul 2023 13:05:47 GMT"],
"content-type":["text/html; charset=UTF-8"],"x-ratelimit-limit":[60],"x-ratelimit-remaining":[59]},"body":"0+0*3"} 

原因

WWWFormに文字列をセットするサンプル(ChatGPTに出してもらった)で直接文字列を宣言し、
それをFormデータにセットするとbodyから抜け落ちることなくサーバ側に渡されていた。

違いとしては、「""」で囲んだ状態(エスケープされている状態)になっているかという点。

//JsonUtilityで出力した文字列
dataTable:{owner.pow}*{skill.args.0} 
※実際ははDebug.Log($"dataTable:{dataTable}")で出している
→サーバログ:body":[]}
 
//直宣言した文字列
string test = "{\"dataTable\":\"{owner.pow}*{skill.args.0}\"}";
{"dataTable":"{owner.pow}*{skill.args.0}"}
→サーバログ:"body":{"dataTable":"{owner.pow}*{skill.args.0}"}

JsonUtilityは値を「""」で囲めていない。
だからといって以下のようにしてもbodyにはセットされず…

WWWForm form = new WWWForm();
form.AddField("owner", ownerJson);
form.AddField("party", partyJson);
form.AddField("enemy", enemyJson);
form.AddField("dataTable", "\"{owner.pow}*{skill.args.0}\"");
form.AddField("score", scoreJson);
form.AddField("skill", skillJson);
form.AddField("skillCount", skillCount);

解決策

WWWFormは使わず、文字列配列を作成して全部合体させたうえでバイトデータをbodyにセットする。

        List<string> formDatas = new List<string>()
        {
            $"\"owner\":{ownerJson}",
            $"\"party\":{partyJson}",
            $"\"enemy\":{enemyJson}",
            $"\"score\":{scoreJson}",
            $"\"skill\":{skillJson}", //dataTableはskillJsonに含まれる
            $"\"skillCount\":{skillCount}"
        };

        string jsonArray = "{" + string.Join(",", formDatas) + "}";
        byte[] jsonBytes = System.Text.Encoding.UTF8.GetBytes(jsonArray);
private async Task<List<T>> sendApi<T>(string url, bool isGet, WWWForm form, byte[] body=null)
    {
        AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);

        List<T> responses = new List<T>();
        UnityWebRequest request = isGet ? UnityWebRequest.Get(url) : UnityWebRequest.Post(url, form);

        // 識別番号を渡してアセットバンドル名を取得
        request.SetRequestHeader("Content-Type", "application/json");
        request.SetRequestHeader("Accept", "application/json");
        request.SetRequestHeader("Authorization", "Bearer " + User.ApiToken);
        request.downloadHandler = new DownloadHandlerBuffer();

        if (form != null)
        {
            request.uploadHandler = new UploadHandlerRaw(form.data);
        }
        if(body != null)
        {
            //jsonBytesを引数bodyで渡してセット
            request.uploadHandler = new UploadHandlerRaw(body);
        }

無事に計算結果を取得
image.png

WWWFormでうまく渡す方法も探します…。

補足

JsonUtilityの代わりにJsonConvert.SerializeObjectでエスケープ付の文字列生成できるようです。
エラーが出て確認できなかったので、機会あれば追記します。

# 追記(2023/8/13)
修正後の例ではjsonをそのまま渡していますが、key:valueのvalueが単純な文字列の場合
「"」をエスケープして囲む必要があります。忘れないようにメモ。

List<string> formDatas = new List<string>()
        {
            $"\"id\":\"{userId}\"",
            $"\"passwprd\":\"{password}\""
        };
1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2