UEでJSONの匿名型の配列をシリアライズ、デシリアライズしてみた
修羅場(デッドライン)を超えてきました...
Unreal Engine (UE) Advent Calendar 2025 2日目の記事です。
UEでたまたまJSONのキーの無い配列、無名配列とも匿名型の配列と呼ばれるデータを扱う機会があったので、UE標準のJSONモジュールを使ったシリアライズ、デシリアライズのやり方をメモとして残します。
前提条件
- Unreal C++オンリーです。ある程度c++が分かる人向けの記事です。
- UE5.7で検証を行いました。
- UEのエンジンバージョンアップにより、APIが変更される可能性があります。
開発環境
- Windows11
- Unreal Engine 5.7
- JetBrains Rider 2025.3.0.3
UEでJSONを取り扱うための下準備
◯◯.Build.csのPublicDependencyModuleNames、もしくはPrivateDependencyModuleNamesのどちらかに以下のモジュールを追加してください。
- Json
- JsonUtilities
コード例
PublicDependencyModuleNames.AddRange(
new[]
{
"Core",
"Engine",
"Json",
"JsonUtilities"
});
PrivateDependencyModuleNames.AddRange(
new[]
{
"Json",
"JsonUtilities"
});
JSONのデータ処理をcppファイル等が入っているprivateフォルダしか使用しない場合は、PrivateDependencyModuleNamesに追加すると依存関係が弱くなるのでオススメです。
匿名型の配列をデシリアライズする
キーが無い配列データをデシリアライズする方法です。
例としてvoicevox coreでAccentPhraseを匿名型の配列として取得するAPIがあるので、受け取ったJSONをUEで使えるようにデシリアライズしてみます。
例
[ {
"moras" : [ {
"text" : "エ",
"consonant" : null,
"consonant_length" : null,
"vowel" : "e",
"vowel_length" : 0.1429999,
"pitch" : 5.641225
}, {
"text" : "ン",
"consonant" : null,
"consonant_length" : null,
"vowel" : "N",
"vowel_length" : 0.09110167,
"pitch" : 5.670825
} ],
"accent" : 1,
"pause_mora" : {
"text" : "、",
"consonant" : null,
"consonant_length" : null,
"vowel" : "pau",
"vowel_length" : 0.4350787,
"pitch" : 0.0
},
"is_interrogative" : false
}, {
"moras" : [ {
"text" : "ダ",
"consonant" : "d",
"consonant_length" : 0.04958991,
"vowel" : "a",
"vowel_length" : 0.21048196,
"pitch" : 5.923339
} ],
"accent" : 2,
"pause_mora" : null,
"is_interrogative" : false
} ]
上記のJSONをUEで容易に使用できるように、対応したstructでデシリアライズします。
APIから取得するJSONのオブジェクトのデータ形式が固定の場合は、FJsonObjectConverterを用いて変換が可能です。
// 例としてFString型のJsonStringという変数を定義し、受け取ったJSONを格納したと想定
// 『FVoicevoxAccentPhrase』という名前のstructを定義
FVoicevoxAccentPhrase AccentPhrase;
FJsonObjectConverter::JsonArrayStringToUStruct(UTF8_TO_TCHAR(JsonString), &AccentPhrasesList, 0, 0);
また、以下のようにFJsonSerializer::Deserializeを利用しても変換できます。
// 例としてFString型のJsonStringという変数を定義し、受け取ったJSONを格納したと想定
// 『FVoicevoxAccentPhrase』という名前のstructを定義
TArray<FVoicevoxAccentPhrase> AccentPhrases;
const TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(JsonString);
if (TSharedPtr<FJsonValue> JsonValue; FJsonSerializer::Deserialize(Reader, JsonValue) && JsonValue.IsValid())
{
if (const TArray<TSharedPtr<FJsonValue>>* JsonArray = nullptr; JsonValue->TryGetArray(JsonArray))
{
for (const TSharedPtr<FJsonValue>& Element : *JsonArray)
{
FVoicevoxAccentPhrase AccentPhrase;
if (FJsonObjectConverter::JsonObjectToUStruct(Element->AsObject().ToSharedRef(), FVoicevoxAccentPhrase::StaticStruct(), &AccentPhrase))
{
AccentPhrases.Add(AccentPhrase);
}
}
}
}
匿名型の配列にシリアライズする
TArrayを匿名型の配列のJSONにシリアライズする場合、FJsonObjectConverterでは対応するAPIがないため実装する必要があります。
大体の場合がsturctの配列をJSONに変換して渡す、になるので大まかな実装は以下の通りになります。
- FJsonValueの配列を作成
- JsonObjectConverter::UStructToJsonObjectを実行し、FJsonValueの配列に追加
- FJsonValueの配列にFJsonSerializer::Serializeを実行
サンプルコード
// 『FVoicevoxAccentPhrase』という名前のstructを定義
// JSONに変換したいTArray<FVoicevoxAccentPhrase>を『AccentPhrases』という名前で定義
// JSON値配列を作成
TArray<TSharedPtr<FJsonValue>> JsonArray;
for (const auto& Accent : AccentPhrases)
{
TSharedPtr<FJsonObject> JsonObject = MakeShared<FJsonObject>();
FJsonObjectConverter::UStructToJsonObject(FVoicevoxAccentPhrase::StaticStruct(), &Accent, JsonObject.ToSharedRef(), 0, 0);
JsonArray.Add(MakeShared<FJsonValueObject>(JsonObject));
}
FString OutputString;
const auto Writer = TJsonWriterFactory<>::Create(&OutputString);
FJsonSerializer::Serialize(JsonArray, Writer);
おまけとしてAPIのやり取りを決める中にはデータがない場合はキーを含めないでほしい、もしくは数値の「0」を利用するので、データが無い場合はnullを設定してほしいという要望があります。
その場合、FJsonObjectのSet◯◯Fieldでキーと値を設定します。
// 『FVoicevoxAccentPhrase』という名前のstructを定義
// JSONに変換したいTArray<FVoicevoxAccentPhrase>を『AccentPhrases』という名前で定義
// JSON値配列を作成
TArray<TSharedPtr<FJsonValue>> JsonArray;
for (const auto& [Pause_mora, Is_interrogative] : AccentPhrases)
{
TSharedPtr<FJsonObject> Obj = MakeShared<FJsonObject>();
// Textに格納した文字列が空の場合、受け取る側の欲しい値が[null]
if (Pause_mora.Text.IsEmpty())
{
Obj->SetObjectField(TEXT("pause_mora"), nullptr);
}
else
{
// 特定の数値型のキーにnullを固定で入れてほしい、また0の場合はキーに含めないでほしい、という仕様
TSharedPtr<FJsonObject> StructObject = MakeShared<FJsonObject>();
StructObject->SetStringField(TEXT("text"), Pause_mora.Text);
// pitchの値が0より上の場合はキーに含める
if (Pause_mora.pitch > 0)
{
StructObject->SetNumberField(TEXT("pitch"), Pause_mora.pitch);
}
// consonantの型はfloatだが、受け取る側の欲しい値が[null]という想定
StructObject->SetObjectField(TEXT("consonant"), nullptr);
Obj->SetObjectField(TEXT("pause_mora"), StructObject);
}
Obj->SetBoolField(TEXT("is_interrogative"), Is_interrogative);
JsonArray.Add(MakeShared<FJsonValueObject>(Obj));
}
// 配列を書き出し
FString OutputString;
const auto Writer = TJsonWriterFactory<>::Create(&OutputString);
FJsonSerializer::Serialize(JsonArray, Writer);
出力されるJSONの例
[ {
"pause_mora" : {
"text" : "ア",
"consonant" : null,
"pitch" : 1.5
},
"is_interrogative" : false
}, {
"pause_mora" : null,
"is_interrogative" : false
}, {
"pause_mora" : {
"text" : "イ",
"consonant" : null,
},
"is_interrogative" : false
} ]
まとめ
- 匿名型の配列をデシリアライズする場合、データのキーが固定であればFJsonObjectConverterを用いて変換が可能
- シリアライズする場合はFJsonObjectConverterに対応するAPIがないので、TArray<TSharedPtr<FJsonValue>>に必要なデータを格納後、FJsonSerializer::Serializeを実行する必要がある
以上です。UEで少し特殊なJSONを扱う場合の参考になれば幸いです。
参考資料
Real Unreal Engine C++ 2017-12 (part-5/5)
Discussion