見出し画像

【備忘録】UnityWebRequestとJsonUtilityを一緒に使ったらつまずいた件

 自作ゲームのデバッグ用にUnityでソフトを拵えていた時のこと。外部Jsonファイルをデシリアライズする機能を実装しようとUnityWebRequestとJsonUtilityを使ってコードを書いていました。しかし、デバッグをしている最中、原因不明のエラーがでて詰まってしまいました。
 今回は原因から解決方法までを備忘録としてまとめておきます。


問題のコード

一部コードは省略してます。

var handle = UnityWebRequest.Get(path);
await handle.SendWebRequest();
if (handle.result == UnityWebRequest.Result.ConnectionError)
{
	Debug.LogWarning($"{handle.error}");
	return;
}
var jsonText = handle.downloadHandler.text;
JsonUtility.FromJson<TMP>(jsonText);

 エラーが吐かれたのは最後の行。UnityWebRequestで読み込んだJsonファイルをデシリアライズするところです。

Argument Exception: JSON parse error: Invalid value.

 JsonUtilityのFromJson関数からスローされた例外です。要約すると、渡されたJsonファイルをデシリアライズできなかったよ。ということです。

原因

 では、なぜデシリアライズできなかったのでしょうか。こういう場合大抵はJsonファイルの指定し間違いでしょう。しかし、私の場合はちゃんとJson形式になっていました。
 夜中に悪戦苦闘する中ようやくたどり着いた原因。それは読み込もうとしたJsonファイルが、UTF-8 (BOM付き)で記述されていたのが問題でした。

※ここではUTF-8 BOMの詳しい説明は省きますが、下記の記事で説明されているので、良ければどうぞ。

 ではなぜBOM付きで記述されているだけで上記のようにエラーが出てしまうのでしょうか。それは、テキストファイルの先頭に3バイトのデータが付いているからです。Jsonファイルに余分な3バイトのデータがくっついているから正しくデシリアライズできなかったのです。
 しかも厄介なことに、エンコードによっては文字に起こせないので、実質見えません。私がハマったのはこれが一番の原因です。

検証

 Unityに備わっているTextAssetクラスを用いて取り出したJsonファイルの内容と、UnityWebRequestで読み込んだJsonファイルの内容を比べてみました。

TextAsset
TextAssetで読み込んだテキストデータ
UnityWebRequestで読み込んだテキストデータ

 一見すると両者とも同じ様に見えます。しかし、後者をJsonUtilityでデシリアライズしようとすると、上記のエラーが出てしまいます。
 次は文字列をバイト配列に変換して出力してみます。

前者がTextAsset、後者がUnityWebRequest

 前者と後者で先頭のバイトが違うのはすぐわかると思います。先ほども言った通りBOM付きは先頭に3バイトのデータが付与されています。バイト配列にしてようやくエラーの原因を目にみえる形にできました。3バイト以降は両者とも同じデータになっています。

解決方法

 今回は2つの方法で上記の問題を回避できます。1つはUnityWebRequestを使わない方法。もう1つはBOMを削除する方法です。

UnityWebRequestを使わない(PC内のデータを取得したい人向け)

 ご自身のPC内にあるテキストデータが欲しいだけなら、UnityWebRequestは使わない方が良いでしょう。理由は簡単です。この3バイトを取り除くのがめんどくさいからです。わざわざUnityWebRequestを使ってBOM付きテキストデータを取得するより、Fileクラスを使ってテキストデータを読み込んだほうが簡単だからです。
サンプルはこんな感じ。(一部コードは省略)

var jsonText = await File.ReadAllTextAsync(path, Encoding.UTF8);
JsonUtility.FromJson<TMP>(jsonText)

 問題のコードと比べても、こっちのほうが短い上にBOMが付いてないテキストデータが返ってきます。私の場合非同期でテキストデータを読み込みたかったのでReadAllTextAsync関数を使ってますが、同期しても良いという方ならこちらの書き方でもよいでしょう。

var jsonText = File.ReadAllText(path, Encoding.UTF8);
JsonUtility.FromJson<TMP>(jsonText);

UnityWebRequestを使う(外部データを取得したい人向け)

 どうしても外部から読み込むのにUnityWebRequestを使いたい方はいるでしょう。そういった方は下記の記事を参考にBOMを取り除くのが良いと思います。

 記事を参考に問題があったコードを修正してみました。(一部(以下略))

var handle = UnityWebRequest.Get(path);
await handle.SendWebRequest();
if (handle.result == UnityWebRequest.Result.ConnectionError)
{
	Debug.LogWarning($"{handle.error}");
	return;
}
var jsonText = handle.downloadHandler.text;
var encoding = Encoding.UTF8;
var bomCharArray = encoding.GetString(encoding.Preamble).ToCharArray();
var jsonTextWithoutBOM = jsonText.TrimStart(bomCharArray);
JsonUtility.FromJson<TMP>(jsonTextWithoutBom);

デバッグ用にバイト配列を出力してみた結果はこちら。

修正後の結果

ちゃんとBOMを削除できていることが分かります。

この記事が気に入ったらサポートをしてみませんか?