3

I want to save multiple, unrelated ScriptableObjects objects to one save file. That way, I can save / load data for anything I might want to keep track of. IE. Doors Unlocked, Inventory, High Scores, Treasure Chests found, etc.. This way I can still use ScriptableObjects, and save and/or reload to a point anything that is under control of the DataManager. I wanted the DataManager to do nothing but save and reload the ScriptableObjects with their saved data.

enter image description here

Saving the different ScriptableObjects to one file seems, in my opinion, to work fine. If I open the data_editor.sav file, I can see that the data has been serialized and put in the file.

{"serializedObjects":["{\"GUID\":\"e27e7e59111210141bb70cbdff58f4f6\",\"Prefab\":{\"instanceID\":3648}}","{\"GUID\":\"52abd33f0dd972246b5eaa87a938331e\",\"Prefab\":{\"instanceID\":3190}}","{\"GUID\":\"78d783846b8abb3488ee1e274773aec6\",\"Prefab\":{\"instanceID\":3080}}"]}

However, reading back the data, and trying to repopulate my DataManager's managedData property is what keeps breaking. I cannot for the life of me figure out how to deserialize the JSON strings back to ScriptableObjects.

I get the error:

ArgumentException: Cannot deserialize JSON to new instances of type 'ScriptableObject.'

I have looked at several SO questions, including this one, but I can't seem to find anything that deals with managing multiple different ScriptableObjects into one.

Any help?

I have tried variations on JsonUtility.FromJsonOverwrite and JsonUtility.FromJson<ScriptableObject> but I couldn't get it to work.

DataStore This is the class that is written to disk.

[Serializable]
    public class DataStore : ISerializationCallbackReceiver
    {  
        public List<string> serializedObjects;

        public DataStore()
        {
            serializedObjects = new List<string>();
        }

        #region ISerializationCallbackReceiver_Methods
        /// <summary>
        /// Serialization implementation from ISerializationCallbackReceiver
        /// </summary>
        public void OnBeforeSerialize() {}

        /// <summary>
        /// Deserialization implementation from ISerializationCallbackReceiver
        /// </summary>
        public void OnAfterDeserialize()
        {
            Debug.Log("OnAfterDeserialize Loaded Count: " + serializedObjects.Count);
            foreach (string so in serializedObjects)
            {
                Debug.Log(so);
            }
        }

        #endregion

        public void AddSerializedObjects(List<ScriptableObject> scriptableObjects)
        {
            foreach(ScriptableObject so in scriptableObjects)
            {
                serializedObjects.Add(JsonUtility.ToJson(so));
            }

        }


        public List<ScriptableObject> GetStoredSerializedObjects()
        {

            //return JsonUtility.FromJsonOverwrite(serializedObjects.ToString(), new List<ScriptableObject>());

            List<ScriptableObject> temp = new List<ScriptableObject>();

            foreach (string so in serializedObjects)
            {

                //ScriptableObject file = ScriptableObject.CreateInstance(JsonUtility.FromJson<ScriptableObject>(so));
                var json = JsonUtility.FromJson<ScriptableObject>(so);
                //JsonUtility.FromJsonOverwrite(so, stemp);

                ScriptableObject file = ScriptableObject.CreateInstance((ScriptableObject)json);

                temp.Add(file);
            }
            return temp;
        }
    }

After the data is read back from the save file, I can output to see the SO's data.

enter image description here

Here is how the SO look

enter image description here enter image description here

DataManager

public class DataManager : MonoBehaviour
{

        public List<ScriptableObject> managedData = new List<ScriptableObject>();

        private DataStore m_Data;

        [NonSerialized]
        private IDataSaver m_Saver;

        private void Awake()
        {       
            m_Saver = new JsonSaver();
            m_Data = new DataStore();
            LoadData();
        }

        public void SaveData()
        {
            m_Data.AddSerializedObjects(managedData);
            m_Saver.Save(m_Data);
        }

        public void LoadData()
        {
            Debug.Log("LoadData()");
            m_Saver.Load(m_Data);
//Here is where I am trying to put the scriptable objects back
            //managedData.Clear();
            //managedData.AddRange(m_Data.GetStoredSerializedObjects()));
            //managedData =  m_Data.GetStoredSerializedObjects();
        }
}

IDataSaver

public interface IDataSaver {
        void Save(DataStore data);
        bool Load(DataStore data);
}

JsonSaver

public class JsonSaver : IDataSaver {

        private static readonly string s_Filename = "data_editor.sav";
    public static string GetSaveFilename()
    {
        return string.Format("{0}/{1}", Application.persistentDataPath, s_Filename);
    }

    protected virtual StreamWriter GetWriteStream() {
        return new StreamWriter(new FileStream(GetSaveFilename(), FileMode.Create));
    }

    protected virtual StreamReader GetReadStream() {
        return new StreamReader(new FileStream(GetSaveFilename(), FileMode.Open));
    }

    public void Save(DataStore data) {
        string json = JsonUtility.ToJson(data);
        using (StreamWriter writer = GetWriteStream()) {
            writer.Write(json);
        }
    }

    public bool Load(DataStore data) {
        string loadFilename = GetSaveFilename();
        if (File.Exists(loadFilename)) {
            using (StreamReader reader = GetReadStream()) {
                JsonUtility.FromJsonOverwrite(reader.ReadToEnd(), data);
            }

            return true;
        }

        return false;
    }
}
user-44651
  • 3,924
  • 6
  • 41
  • 87
  • 1
    I won't write this as an answer because I am not 100% sure and I don't have the time right now to look into it but here's what I remember from past attempts at something similar: SOs are literally just assets like an image file in your project. They exist even after you have closed the game down. When you serialize their data out, that's all you're doing; Serializing their *data* out, but the assets remain. Upon deserialization, rather than trying to instantiate SOs, you should just *repopulate* the existing ones with the data you've deserialized. Hope that helps. – Darren Ruane Jul 02 '19 at 20:50

1 Answers1

0

Before talk about the question, I suggest if you decide use json to do (de)serialize works, you'd better use custom class with SerializableAttribte, ScriptableObject contains otiose data as you can see like GUID.

I have tried variations on JsonUtility.FromJsonOverwrite and JsonUtility.FromJson but I couldn't get it to work.

Both of them can work, the problem is you can't deserialize the text (the json string) back to a ScriptableObject object, because the json serialize process doesn't include type information, you need record it by yourself.

Example write

foreach(ScriptableObject so in scriptableObjects)
{
    string name = so.GetType().Name;
    serializedObjects.Add(name); // Record the type name
    serializedObjects.Add(JsonUtility.ToJson(so));
}

When read

string name = serializedObjects[0]; // The recorded type name
string json = serializedObjects[1];
ScriptableObject so = ScriptableObject.CreateInstance(name);
JsonUtility.FromJsonOverwrite(json, so);
shingo
  • 18,436
  • 5
  • 23
  • 42