🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

Q&A

解決済

1回答

2521閲覧

JsonUtility.ToJson() でシリアライズしたクラス配列に初期値が入る問題を回避したい

honjoriki

総合スコア81

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

0グッド

0クリップ

投稿2020/12/13 04:31

編集2020/12/14 09:16

解決したい問題

JsonUtility.ToJson()でJSONに変換したクラス配列データの要素にnullの要素があった時、クラスのフィールド変数の型の初期値が入るようです。JSONに変換するだけで配列に値が入ってしまうようで、nullチェックなどが通用しなくなって困っています。配列に要素を入れないようにする方法はありますでしょうか?

該当のソースコード

データ用クラス ItemData

C#

1using System; 2using UnityEngine; 3 4[Serializable] 5public class ItemData 6{ 7 [SerializeField] int id; 8 [SerializeField] string name; 9 10 public int ID { get { return id; } } 11 public string NAME { get { return name; } } 12 13 public ItemData(int id, string name) 14 { 15 this.id = id; 16 this.name = name; 17 } 18}

データ管理用クラス ItemManager

C#

1using System; 2using UnityEngine; 3 4[Serializable] 5public class ItemManager 6{ 7 // 簡易的なシングルトン 8 public static ItemManager Instance { get { return instance; } } 9 10 private static ItemManager instance = new ItemManager(); 11 12 private ItemManager() 13 { 14 Debug.Log("Created Instance."); 15 } 16 17 // JSONに変換したいクラス配列 18 [SerializeField] private ItemData[] itemDataArray = new ItemData[5]; 19 20 // アイテムをnullのインデックスに格納 21 public void CreateItemData() 22 { 23 for(int i = 0; i < itemDataArray.Length; i++) { 24 if(itemDataArray[i] == null) { 25 itemDataArray[i] = new ItemData(i, "Test" + i.ToString()); 26 break; 27 } 28 } 29 } 30 31 // 配列をJSONに変更(外部にデータ保存のため) 32 public void ConvertToJson() 33 { 34 string json = JsonUtility.ToJson(ItemManager.Instance); 35 Debug.Log(json); 36 } 37 38 // Debug.Log()にアイテムの名前一覧を表示 39 public void PrintData() 40 { 41 for(int i = 0; i < itemDataArray.Length; i++) { 42 if(itemDataArray[i] != null) { 43 Debug.Log(itemDataArray[i].NAME); 44 } 45 } 46 } 47}

実際にデータクラスを呼ぶクラス

using UnityEngine; public class Test : MonoBehaviour { private void Start() { // 適当に3つくらいデータを格納する ItemManager.Instance.CreateItemData(); ItemManager.Instance.CreateItemData(); ItemManager.Instance.CreateItemData(); // まずはそのまま表示 ItemManager.Instance.PrintData(); // Jsonに変換 ItemManager.Instance.ConvertToJson(); // Jsonに変換してからまた表示すると、なぜか値が入っている ItemManager.Instance.PrintData(); } }

出力結果

Jsonにシリアライズしてから表示すると、nullチェックが効いていない
イメージ説明

開発環境

  • Unity 2019.4.15f

現状の回避方法

// 配列をJSONに変更(外部にデータ保存のため) public void ConvertToJson() { string json = JsonUtility.ToJson(ItemManager.Instance); Debug.Log(json); // 現状の回避方法として、配列要素が初期値になっていたら // nullにするという方法をとっていますが、そのためにこの処理を追加するのもつらいです for(int i = 0; i < itemDataArray.Length; i++) { if(itemDataArray[i].NAME == null) { itemDataArray[i] = null; } } }

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

デバッガで実行過程を見てみると、おっしゃる通りToJsonを通過するとたちまち要素3番と4番が出現していました。

図1

ToJsonの説明を見てみてもこの現象に関する言及はなさそうですし、ToJsonの実装を見てみても肝心の処理内容はネイティブコードになっていて追跡困難で、これはもうこういうものとしてあきらめて、何とか対処するしかないのかもしれません。

あくまでも想像に過ぎませんが、ToJsonでシリアライズする際に、内部的にはItemManager自身は実はJSON表現から(あるいはもしかするとその中間表現から)デシリアライズされる過程があるのかもしれませんね。
ご質問者さんがお試しの通り、nullだったインデックスにはデフォルト値の要素が入ってきてしまうのなら、逆にそれを利用して要素が元々はnullだったのかどうか識別するというのは、不本意かもしれませんが妥当な手段なような気がします。

あるいはItemDataを下記のようにして、ニセnull判定を行うとかでしょうかね?
個人的にはnullじゃないのにnullのふりをするというのは余計な混乱を招きそうで、不健全な香りがしますが...

C#

1using System; 2using UnityEngine; 3 4[Serializable] 5public class ItemData 6{ 7 [SerializeField] private bool isNotNull; 8 [SerializeField] int id; 9 [SerializeField] string name; 10 11 public int ID { get { return this.id; } } 12 public string NAME { get { return this.name; } } 13 14 public ItemData(int id, string name) 15 { 16 this.isNotNull = true; 17 this.id = id; 18 this.name = name; 19 } 20 21 public static bool operator ==(ItemData x, ItemData y) => CompareEquality(x, y); 22 23 public static bool operator !=(ItemData x, ItemData y) => !CompareEquality(x, y); 24 25 public override bool Equals(object obj) 26 { 27 return CompareEquality(this, obj as ItemData); 28 } 29 30 public override int GetHashCode() 31 { 32 return base.GetHashCode(); 33 } 34 35 private static bool CompareEquality(ItemData lhs, ItemData rhs) 36 { 37 var lhsIsActualNull = ReferenceEquals(lhs, null); 38 var rhsIsActualNull = ReferenceEquals(rhs, null); 39 40 if (rhsIsActualNull && lhsIsActualNull) 41 { 42 return true; 43 } 44 45 if (rhsIsActualNull) 46 { 47 return !lhs.isNotNull; 48 } 49 50 return lhsIsActualNull ? !rhs.isNotNull : ReferenceEquals(lhs, rhs); 51 } 52}

図2

投稿2020/12/14 13:25

Bongo

総合スコア10811

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

honjoriki

2020/12/15 00:07

Bongo様。ご回答ありがとうございます。 JsonUtility.ToJson()の実装まで確認していただきありがとうございます。 やはり現状、こちらのほうで何らかの対処をしなければいけないようですね...。 ToJsonの仕様上、こうなってしまうことが明らかになりましたので、Bongo様のご回答のとおり、 「あらかじめbool変数を用意しておき、その変数で判断する」のがセオリーになるのではないかと思いました。 ありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問