ScriptableObject を JSONから読み込む

Unityでデータを量産する場合、ScriptableObject は大変便利です。

```
[CreateAssetMenu(fileName = "SampleDataSO", menuName = "ScriptableObject/SampleDataListSO")]
public class SampleDataSO : ScriptableObject
{
    public SampleData[] samples;
}
```
上記のようなScriptableObjectであれば
```Create> ScriptableObject> SampleDataListSO```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59911/0633b8f3-39ab-20bc-5ec6-db3aa8b710da.png)
でオブジェクトを作成することができ、直接データを作っていくことができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59911/dcbf3815-749c-e632-7c11-8cff2878e3b3.png)

作成したデータをそのまま利用する場合は問題ないのですが、JSON経由でやりとりしたい場合、ScriptableObjectは```CreateInstance<>()```でインスタンスを生成する必要があるため、通常のデータクラスのように
```SampleDataSO data = JsonUtility.FromJson<SampleDataSO>(jsonString);```
とすると
```
Failed to load settings data: Cannot deserialize JSON to new instances of type 'SampleDataSO.'
```
といったエラーが出てしまいます。
そこでScriptableObjectでは
```JsonUtility.FromJsonOverwrite(json, data);```
を使用します。

```SampleDataSO.cs
using JetBrains.Annotations;
using UnityEngine;

[System.Serializable]
public class PersonData
{
    public string name;
    public int age;
}
[System.Serializable]
public class HobbyData
{
    public string hobby;
    public float hours;
}
[System.Serializable]
public class SampleData
{
    public PersonData person;
    public HobbyData[] hobbies;
}

[CreateAssetMenu(fileName = "SampleDataSO", menuName = "ScriptableObject/SampleDataListSO", order = 1)]
public class SampleDataSO : ScriptableObject
{
    public SampleData[] samples;

    // ScriptableObjectをCreateInstance()するにはJsonUtility.FromJsonOverwriteが必要
    public static SampleDataSO CreateScriptableObjectFromJSON(string jsonString)
    {
        SampleDataSO data = CreateInstance<SampleDataSO>();
        JsonUtility.FromJsonOverwrite(jsonString, data);
        return data;
    }
}
```

以下のサンプルは、上記ScriptableObjectをJSON形式でファイルに保存し、再びScriptableObjectに戻します。

```ScriptableObjectJSONSample.cs
using System;
using UnityEngine;

public class ScriptableObjectJSONSample : MonoBehaviour
{
    [SerializeField] string m_dataPath = "SampleData.json";
    [SerializeField] SampleDataSO m_sampleDataSO;

    // Start is called before the first frame update
    void Start()
    {
        SaveToJSON(m_dataPath, m_sampleDataSO);
        SampleDataSO importedDataSO = LoadfromJSONFile(m_dataPath);
        string jsonStr = JsonUtility.ToJson(importedDataSO, true);
        Debug.Log("ImportedData: \n" + jsonStr);
    }

    // Update is called once per frame
    void Update()
    {
        
    }

    public static string GetFullPath(string _dataPath)
    {
#if !UNITY_EDITOR
        string appPath = Application.persistentDataPath;
#else
        string appPath = Application.dataPath;
#endif
        return $"{appPath}/{_dataPath}";
    }

    public static bool SaveToJSON(string _dataPath, SampleDataSO _dataSO)
    {
        bool result = false;
        string fullPath = GetFullPath(_dataPath);
        // ファイルにデータを書き込む
        string jsonStr = JsonUtility.ToJson(_dataSO, true);
        Debug.Log("SaveSettings: " + fullPath);
        try
        {
            System.IO.File.WriteAllText(fullPath, jsonStr);
            result = true;
        }
        catch (Exception e)
        {
            Debug.LogError("Failed to save settings data: " + e.Message);
        }
        return result;
    }

    public static SampleDataSO LoadfromJSONFile(string _dataPath)
    {
        string fullPath = GetFullPath(_dataPath);

        // ファイルからデータを読み込む
        string jsonStr = System.IO.File.ReadAllText(fullPath);

        try
        {
            SampleDataSO data = SampleDataSO.CreateScriptableObjectFromJSON(jsonStr);
            return data;
        }
        catch (Exception e)
        {
            Debug.LogError("Failed to load settings data: " + e.Message);
            return null;
        }
    }
}

```

結果:
```
ImportedData: 
{
    "samples": [
        {
            "person": {
                "name": "アンドリュー",
                "age": 64
            },
            "hobbies": [
                {
                    "hobby": "彫刻",
                    "hours": 6.0
                },
                {
                    "hobby": "自分探しの旅",
                    "hours": 1.0
                }
            ]
        },
        {
            "person": {
                "name": "ポーシャ",
                "age": 32
            },
            "hobbies": [
                {
                    "hobby": "ピアノ演奏",
                    "hours": 2.0
                }
            ]
        }
    ]
}

```