質問をすることでしか得られない、回答やアドバイスがある。

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

ただいまの
回答率

89.63%

UnityでクラスまるごとJSONで保存する

解決済

回答 1

投稿

  • 評価
  • クリップ 0
  • VIEW 217

momiji0210

score 21

Unityでクラスをまるごと保存するサンプルを制作しております。

参考サイトを見ながら、保存まではできたのですが読み込み時にエラーが出てしまいます。
対処方法わかる方おりませんでしょうか。

保存先についてはStreamingAssetsフォルダにて行っており、Jsonの文字列の取得はできておりました。

参考サイト
https://kan-kikuchi.hatenablog.com/entry/Json_SaveData

ArgumentException: Cannot deserialize JSON to new instances of type 'DataManager.'
UnityEngine.JsonUtility.FromJson (System.String json, System.Type type) (at C:/buildslave/unity/build/Modules/JSONSerialize/Public/JsonUtility.bindings.cs:48)
UnityEngine.JsonUtility.FromJson[T] (System.String json) (at C:/buildslave/unity/build/Modules/JSONSerialize/Public/JsonUtility.bindings.cs:33)
DataManager.LoadJson () (at Assets/Scripts/Others/DataManager.cs:323)
DataManager.Awake () (at Assets/Scripts/Others/DataManager.cs:130)
UnityEngine.Object:Instantiate(GameObject)
AddManager:Awake() (at Assets/Scripts/Others/AddManager.cs:25)
using UnityEngine;
using UnityEngine.SceneManagement;
using System;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.Serialization.Formatters.Binary;


// オリジナルクラス
public class MyFunction {

    // 使用例
    /*
    MyFunction myFunc = new MyFunction();
    string filePath = myFunc.GetPathFileNameStreamingAssets("category.json");
    string datastr = myFunc.ReadData(filePath);
    */

    // Androidの場合、StreamingAssetのファイル名から文字列を返す
    public string GetStringAndroidStreamingAssets(string fileName){

        // Androidの場合
        #if UNITY_ANDROID    
        string path = "jar:file://" + Application.dataPath + "!/assets" + "/" + fileName;
        WWW www = new WWW(path);
        while (!www.isDone) {}
        return www.text;

        #else
        Debug.Log("not android");
        return "";
        #endif

    }

    // StreamingAssetsのPathを取得
    public string GetPathStreamingAssets(){

        string path = "";

        #if UNITY_EDITOR
        path = Application.dataPath + "/StreamingAssets/";

        #elif UNITY_IOS
        path = Application.dataPath + "/Raw/";

        #elif UNITY_ANDROID
        path = "jar:file://" + Application.dataPath + "!/assets" + "/";
        WWW www = new WWW(path);
        while (!www.isDone) {}

        #else
        path = Application.dataPath + "/StreamingAssets/";
        #endif

        return path;
    }

    // StreamingAssetsのPathを取得してFileNameを付加する
    public string GetPathFileNameStreamingAssets(string fileName){

        return (GetPathStreamingAssets() + fileName);

    }

    public string ReadData(string filePath) {
        string str = "";
        StreamReader reader;
        reader = new StreamReader (filePath);
        str = reader.ReadToEnd ();
        reader.Close ();
        //Debug.Log(str);
        return str;
    }

}

/* ====================================================================== 
* 全シーンで使える変数。
* [DataManager.Instance.変数名]で各種処理を呼び出すことが可能。
*====================================================================== */
[Serializable]
public class DataManager : MonoBehaviour {

    private static DataManager instance = null;

    public bool bgmFlag;
    public int playerMax = 3;
    public int playerLow = 1;
    public int playerHigh = 2;

       //SaveDataをJsonに変換したテキスト(リロード時に何度も読み込まなくていいように保持)
      [SerializeField]
      private static string _jsonText = "";

    void Start() {
    }

    // 初期化
    public static DataManager Instance{
        get{
            if( null == instance ){
                instance = (DataManager)FindObjectOfType(typeof(DataManager));                
                if( null == instance ){
                    Debug.Log(" DataManager Instance Error ");                
                }
            }
            return instance;
        }    

    }

    void Awake(){

        GameObject[] obj = GameObject.FindGameObjectsWithTag("DataManager");

        if( 1 < obj.Length ){
            // 既に存在しているなら削除
            Destroy( gameObject );
        }else{
            // シーン遷移では破棄させない
            DontDestroyOnLoad( gameObject );
            LoadJson();
            //Load();
        }

    }

    // リストを保存
    public static void SaveList<T>(string key , List<T> value){
        string serizlizedList = Serialize<List<T>> (value);
        PlayerPrefs.SetString (key, serizlizedList);
    }

    // リストを読み込み
    public static List<T> LoadList<T> (string key){
        //keyがある時だけ読み込む
        if (PlayerPrefs.HasKey (key)) {
            string serizlizedList = PlayerPrefs.GetString (key);
            return Deserialize<List<T>> (serizlizedList);
        }

        return new List<T> ();
    }

    //引数のオブジェクトをシリアライズして返す
    private static string Serialize<T> (T obj){
        BinaryFormatter binaryFormatter = new BinaryFormatter ();
        MemoryStream    memoryStream    = new MemoryStream ();
        binaryFormatter.Serialize (memoryStream , obj);
        return Convert.ToBase64String (memoryStream   .GetBuffer ());
    }

    //引数のテキストを指定されたクラスにデシリアライズして返す
    private static T Deserialize<T> (string str){
        BinaryFormatter binaryFormatter = new BinaryFormatter ();
        MemoryStream    memoryStream    = new MemoryStream (Convert.FromBase64String (str));
        return (T)binaryFormatter.Deserialize (memoryStream);
    }

    //保存しているJsonを取得する
    private static string GetJson(){
        //既にJsonを取得している場合はそれを返す。
        if(!string.IsNullOrEmpty(_jsonText)){
            return _jsonText;
        }

        //Jsonを保存している場所のパスを取得。
        string filePath = "";

        MyFunction myFunc = new MyFunction();
        filePath = myFunc.GetPathFileNameStreamingAssets("DataManager.json");

        //Jsonが存在するか調べてから取得し変換する。存在しなければ新たなクラスを作成し、それをJsonに変換する。
        if(File.Exists(filePath)){
            _jsonText = File.ReadAllText (filePath);
        }
        else{
            _jsonText = JsonUtility.ToJson(new DataManager ());
        }

        Debug.Log(_jsonText);

        return _jsonText;
    }

    //データを読み込む。
    private static void LoadJson(){
        instance = JsonUtility.FromJson<DataManager>(GetJson ());
    }

    public void ReloadJson(){
        JsonUtility.FromJsonOverwrite (GetJson(), this);
      }

    public void SaveJson(){
        _jsonText = JsonUtility.ToJson(this, true);
        File.WriteAllText (GetSaveFilePath(), _jsonText);
    }

    // 削除
    public void DeleteJson(){
        _jsonText = JsonUtility.ToJson(new DataManager ());
        ReloadJson ();
    }

    //保存する場所のパスを取得。
    private static string GetSaveFilePath(){

        string filePath = "";

        MyFunction myFunc = new MyFunction();
        filePath = myFunc.GetPathFileNameStreamingAssets("DataManager.json");
        Debug.Log(filePath);
    /*
        //確認しやすいようにエディタではAssetsと同じ階層に保存し、それ以外ではApplication.persistentDataPath以下に保存するように。
        #if UNITY_EDITOR
        filePath += ".json";
        #else
        filePath = Application.persistentDataPath + "/" + filePath;
        #endif
*/

        return filePath;
    }
}
  • 気になる質問をクリップする

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • SurferOnWww

    2020/02/12 17:50

    コードを丸投げしてデバッグしてくれと言ってるみたいですね。少しは自分で切り分けしてから質問するようにした方が解決が早いと思いますよ。エラーメッセージは「JSON 文字列を DataManager 型にデシリアライズできない」と言っているのですから、その部分だけ切り出して検証するとか。

    キャンセル

  • momiji0210

    2020/02/12 20:31

    >>nanami12様
    ご回答ありがとうございます。
    Awake内のLoadJson();部分で、エラーが出ています。
    エラーについては、一番上のコードの内容です。

    >>izmktr様
    StartなどでSaveJsonすると自動生成されます。
    下記のような普通のJsonが出力されていました。

    {
    "bgmFlag": false,
    "playerMax": 3,
    "playerLow": 1,
    "playerHigh": 2
    }

    >>SurferOnWww様
    ご指摘のような形になって申し訳ございません。
    参考サイトを読み進めながら、試しているのですがこのエラーだけどうしてもとれず・・・。

    エラー内容については何となくわかるのですが、なぜ書き出したものが不具合なのかわからず。
    別のサンプルを試してJsonUtilityで読み込めることは確認しているため、このエラーの解消方法がわかりませんでした。

    キャンセル

  • SurferOnWww

    2020/02/13 10:00

    上の私のコメントで、

    > エラーメッセージは「JSON 文字列を DataManager 型にデシリアライズできない」と言っているのですから、その部分だけ切り出して検証するとか。

    ・・・と書きましたけど、それをやってみませんか? それで原因が分かれば自己解決できるかもしれません。できない事情はないはずですが。

    キャンセル

回答 1

checkベストアンサー

+1

・原因
MonoBehaviorを継承しているクラスでJsonUtility.FromJsonを行うとクラスの生成に失敗します。

・解決法
MonoBehaviorを継承しない作りに戻す。
MonoBehaviorを継承しない受け皿となるクラスを別に作ってそれを用いる。
JsonUtility.FromJsonOverwriteを使う。

上記どれかで良いです。

詳しくは下記サイトを見て下さい。
https://creive.me/archives/14311/

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/02/14 10:29

    コメントありがとうございます。
    そうだったのですね・・・。こちら知らなかったため、非常に助かりました。

    ha_wn様の言う通り、継承しない作り試したところ、うまく動作しました。
    こちらありがとうございました。
    後ほど、ベストアンサーとして設定させてくださいませ。

    別件になってしまい恐縮なのですが、独自で作ったクラスに
    直接Loadさせる方法はないでしょうか。
    現在だと、下記のようなイメージでLoadさせています。

    hoge.Instance.Item = hoge.Instance.Item.LoadJson(); // 現在のイメージ
    hoge.Instance.Item.LoadJson(); で読めるようにしたい

    この辺、おそらくstaticなどを使って実装するのかもしれないのですが、よくわかっておらず。。。

    [Serializable]
    public class ITEM {

    public int id;
    public string name;
    public string description;
    public List<int> arrayInt;
    public List<string> array;

    private string _jsonText = "";
    private string pathJson = "Item.json";

    // ログ
    public void Log(){
    Debug.Log("id => " + id);
    Debug.Log("name => " + name);
    Debug.Log("description => " + description);
    Debug.Log("arrayInt => " + string.Join(", ", arrayInt));
    Debug.Log("array => " + string.Join(", ", array));
    }

    public void LogJson(){
    this.ReloadJson();
    Debug.Log("json => " + _jsonText);
    }

    //保存する場所のパスを取得。
    private string GetSaveFilePath(){
    string filePath = "";
    MyFunction myFunc = new MyFunction();
    filePath = myFunc.GetPathFileNameStreamingAssets(pathJson);
    Debug.Log(filePath);
    return filePath;
    }

    //保存しているJsonを取得する
    private string GetJson(){

    //既にJsonを取得している場合はそれを返す。
    if(!string.IsNullOrEmpty(_jsonText)){
    return _jsonText;
    }
    //
    //Jsonを保存している場所のパスを取得。
    string filePath = "";

    MyFunction myFunc = new MyFunction();
    filePath = myFunc.GetPathFileNameStreamingAssets(pathJson);

    //Jsonが存在するか調べてから取得し変換する。存在しなければ新たなクラスを作成し、それをJsonに変換する。
    if(File.Exists(filePath)){
    _jsonText = File.ReadAllText (filePath);
    }
    else{
    _jsonText = JsonUtility.ToJson(new ITEM ());
    }

    Debug.Log(_jsonText);

    return _jsonText;
    }

    //データを読み込む。
    public ITEM LoadJson(){
    return JsonUtility.FromJson<ITEM>(GetJson ());
    }

    //データを読み込む。 これをしたい
    public void LoadJson1(){
    //this = JsonUtility.FromJson<ITEM>(GetJson ());
    }

    // 再読み込み
    public void ReloadJson(){
    JsonUtility.FromJsonOverwrite (GetJson(), this);
    }

    // 削除
    public void DeleteJson(){
    _jsonText = JsonUtility.ToJson(new ITEM ());
    this.ReloadJson ();
    }

    // 保存
    public void SaveJson(){
    _jsonText = JsonUtility.ToJson(this, true);
    File.WriteAllText (GetSaveFilePath(), _jsonText);
    }
    }

    キャンセル

  • 2020/02/14 11:19 編集

    参考サイトの_instanceに代入する作りに戻せば良いと思います。
    何か弊害があるのでしょうか?

    キャンセル

  • 2020/02/14 11:26

    別件は別件として新たに質問を立て、この質問自体はBAを選択してクローズすべきと思います。

    キャンセル

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

  • ただいまの回答率 89.63%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる