📌

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)
https://usagi.hatenablog.jp/entry/2017/12/01/ac_ue4_2_p5

Discussion