UE4でJsonを使ってみよう

本記事は [Unreal Engine 4 アドベントカレンダーその1](https://qiita.com/advent-calendar/2020/ue4) 12日目の記事です。

今年も遅刻して大変申し訳ございませんでした・・・
仕事が炎上していて時間が取れず、精神が病んでるとはいえ本当に申し訳ありませんでした・・・

UE4で今までJsonを使ったことがなかったのでちょっと調べてみたのですが結構あっさりと使えたので今回はその辺について書いていけたらと思ております。

試してみた環境

* UE4のバージョン：4.25.4
* VisualStudio2017


## 準備
まずJsonを扱うにあたり、既存のモジュールを利用するため参照の追加が必要です。
ProjectのBuild.csにJsonを追加します。

```C#:Build.cs
PublicDependencyModuleNames.AddRange(new string[] 
{
    "Core",
    "CoreUObject",
    "Engine",
    "InputCore",
    "HeadMountedDisplay",
    "Json",//追加
    "JsonUtilities",//追加
});
```

実際にJsonの読み書きの処理を書いていきます。
すでにUE4にはJson周りの処理で便利なクラスがありますのでそれを使っていきます。
とりあえずヘッダーに必要な関数を定義していきます。

```C++:Json.h
#pragma once

#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "Dom/JsonObject.h"
#include "MyJsonLib.generated.h"

typedef TSharedPtr<FJsonObject> JsonObjectPtr;

UCLASS()
class JSON2_API UJson : public UBlueprintFunctionLibrary
{
	GENERATED_BODY()

	UFUNCTION(BlueprintCallable, Category = "Json")
	static bool WriteJsonData(FString CharName, TArray<AActor*> Items, TArray<int> Count);
	
	UFUNCTION(BlueprintCallable, Category = "Json")
	static bool ReadJsonData(FDateTime& SaveedDate, FString& CharName, TArray<FString>& Items, TArray<int>& Count);
}
```

続いてCppファイルに必要な処理を書いていきます。
ここでも必要なヘッダーをインクルードします。

```C++:Json.cpp
#include "MyJsonLib.h"
#include "JsonUtilities/Public/JsonUtilities.h"
#include "Runtime/Json/Public/Serialization/JsonReader.h"
#include "Misc/DateTime.h"//これは別になくてもいいです(今回日付関連で必要だったため)
```
続いてJsonファイルの入出力先を記載します。

```C++:Json.cpp
static const FString RootName("InventoryObject");
static const FString WritePath(FPaths::ProjectSavedDir() / "Json");
static const FString FileName("JsonData.json");
static const FString FilePathFull(WritePath / FileName);
```

##Jsonファイル出力
まずはJsonファイルの出力です。

```C++:Json.cpp
bool UJsonL::WriteJsonData(FString CharName, TArray<AActor*> Items, TArray<int> Count)
{
	JsonObjectPtr JsonRootObject = MakeShareable(new FJsonObject);

	//保存した日時
	const FString SaveDate = FDateTime::UtcNow().ToString();
	JsonRootObject->SetStringField("SaveDate", SaveDate);

	//キャラ名
	JsonRootObject->SetStringField("Character", CharName);
	
	//レベル上のオブジェクトの名前
	TArray<TSharedPtr<FJsonValue>> ItemObjects;

	for (AActor* obj : Items)
	{
		ItemObjects.Add(MakeShareable(new FJsonValueString(obj->GetFullName())));
	}
	JsonRootObject->SetArrayField("Items", ItemObjects);

	//数値の配列
	TArray<TSharedPtr<FJsonValue>> ItemCountArray;
	for (int i : Count)
	{
		ItemCountArray.Add(MakeShareable(new FJsonValueNumber(i)));
	}
	JsonRootObject->SetArrayField("ItemCount", ItemCountArray);

	// FStringにJsonを書き込むためのWriterを作成
	FString OutPutString;
	TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&OutPutString);

	// JsonをFStringに書き込み
	FJsonSerializer::Serialize(JsonRootObject.ToSharedRef(), Writer);

	return FFileHelper::SaveStringToFile(OutPutString, *FilePathFull);
}
```

関数の引数に出力したい型を記述してください。
使用時に引数として値を渡してあげます。
基本的にはJsonRootObject->SetStringField("Character", CharName);みたいに
書き出したい値の型に応じたSet〇〇Field関数を使用し、引数にKeyとValueを渡します。

後は
TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&OutPutString);
上記のようにJsonをFStringに書き込むためのWriterを作成し、
FJsonSerializer::Serialize(JsonRootObject.ToSharedRef(), Writer);
シリアライザーを使用してJsonをFStringに書き込みます。

そして下記のように指定した出力先へファイルを出力します。
return FFileHelper::SaveStringToFile(OutPutString, *FilePathFull);

後は作成したノードをブループリントで使用して出力先を確認すれば指定した出力先にJsonファイルが書き出されていると思います。
![JsonOutPut.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252878/500a53af-c595-3314-1d1c-eb259fb0cf86.png)

##Jsonファイル読み込み
つづいてJsonファイルの読み込みです。

```C++:Json.cpp
bool UJson::ReadJsonData(FDateTime& SavedDate, FString & CharName, TArray<FString>& Items, TArray<int>& Count)
{
	FString RawData;

	bool bLoadedFile = FFileHelper::LoadFileToString(RawData, *FilePathFull);

	if (bLoadedFile)
	{
		// FJsonObject(Jsonデータの入れ物)を作成
		JsonObjectPtr JsonRootObject = MakeShareable(new FJsonObject());

		// FStringからJsonを読み込むためのReaderを作成
		TSharedRef<TJsonReader<>> JsonReader = TJsonReaderFactory<>::Create(RawData);

		// Json文字列からJsonオブジェクトに読み込み
		if (FJsonSerializer::Deserialize(JsonReader, JsonRootObject))
        {
		    //保存日時の取得
		    FDateTime::Parse(JsonRootObject->GetStringField("SaveDate"), SavedDate);

		    //キャラネームの取得
		    CharName = JsonRootObject->GetStringField("Character");

		    //レベル上のオブジェクトの取得
		    for (TSharedPtr<FJsonValue> V : JsonRootObject->GetArrayField("Items"))
		    {
			Items.Add(V->AsString());
		    }

		    //アイテムカウントの取得
		    for (TSharedPtr<FJsonValue> V : JsonRootObject->GetArrayField("ItemCount"))
		    {
			Count.Add(V->AsNumber());
		    }
		    return true;
	   }
	   return false;
}
```

流れはほとんど出力の時と同じです。
読み込みたい値の型を関数の引数として記述してください。
JsonObjectPtr JsonRootObject = MakeShareable(new FJsonObject());
これでFJsonObject(Jsonデータの入れ物)を作成し
TSharedRef<TJsonReader<>> JsonReader = TJsonReaderFactory<>::Create(RawData);
上記の処理でFStringからJsonを読み込むためのReaderを作成します。

後は読み込みたい値の型に対応したGet〇〇Field("Keyを指定")で値を取得していきます。
文字列ならGetStringFieldとなります。

こちらもブループリントで呼び出し、PrintString等出力してみると下記のようにJsonファイルが読み込めていることが確認できると思います。
![JsonInput.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252878/f532c5ef-f1e7-ba77-d004-bf89aced15ed.png)

##おまけ
Jsonファイルを出力する際に成型して出力するかしないかを選択できるようにすることもできます。
Jsonファイル書き出しの関数にbCondensedみたいな感じで成形するかしないかのフラグを引数として追加してください。
コードにすると下記のような感じになります

```C++:Json.cpp
bool UJsonL::WriteJsonData(FString CharName, TArray<AActor*> Items, TArray<int> Count, bool bCondensed)
{
    //一部省略
    
    // FStringにJsonを書き込むためのWriterを作成
	FString OutPutString;

    if (bCondensed)
	{
		TSharedRef<TJsonWriter<TCHAR, TCondensedJsonPrintPolicy<TCHAR>>> Writer = TJsonWriterFactory<TCHAR, TCondensedJsonPrintPolicy<TCHAR>>::Create(&OutPutString);
		// JsonをFStringに書き込み
		FJsonSerializer::Serialize(Object.ToSharedRef(), Writer);
	}
	else
	{
		TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&OutPutString);
		// JsonをFStringに書き込み
		FJsonSerializer::Serialize(Object.ToSharedRef(), Writer);
	}

	return FFileHelper::SaveStringToFile(OutPutString, *FilePathFull);
}
```

Writer作成時にTCondensedJsonPrintPolicyを渡してあげることでJsonの出力時の形式を指定できます。
出力すると下記の画像のように出力されます。
![JsonCondensed.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252878/e4e8cea2-d8e1-9da0-514b-f67a66e2f4c3.png)

##終わりに
UE4でのJsonの扱いは
・TJsonReader
・TJsonWriter
を使用することで結構簡単に使えたりします。
読み込み速度などはC++で使われているrapidJsonやpicoJsonと比べていないのでそこはまた時間があるときに見てみたいですね。
Json周りはゲーム開発でも使う機会は多いのでUE4での入出力の方法が勉強できたのは自分にとって大きな収穫でした。
この記事が少しでも皆さんのUE4ライフの助けになったのでしたら幸いです。

参考にさせていただいた記事
[PaperSloth’s diary](https://papersloth.hatenablog.com/entry/2019/05/07/223031)