Howto use JSON in 5.3 [Guide]

Hi,
One of the new additions of Unity 5.3 is the introduction of the JSONUtility.
You can find the relative documentation here : Unity - Manual: JSON Serialization
In this post, I will show a simple implementation.
First, we will create a Scriptable Object called TestData to store the data from our class.

using UnityEngine;
using System.Collections;

#if UNITY_EDITOR
using UnityEditor;
#endif

[System.Serializable]
public class TestData : ScriptableObject
{ 
    public string curDataStorage;
}

Next, I 'll create a simple class called Test. As long as you are using fields, your own custom classes should work too.

using UnityEngine;
using System.Collections;

public class Test : MonoBehaviour
{
    public TestData curData = null;

    public float nr0 = 0f;
    public float nr1 = 1f;
    public float nr2 = 2f;
}

Now, create a folder called Editor and place the next script called TestEditor inside.

using UnityEngine;
using UnityEditor;
using System.Collections;

[CustomEditor(typeof(Test))]
//
public class TestEditor : Editor 
{
    Test targetTest;
    string path = "Assets/DataAsset.asset";
   
    void OnEnable () 
    {
        targetTest = (Test)target;
        if(targetTest!=null)
        {
            targetTest.curData = LoadData();
        }
    }

    void OnDestroy()
    {
        SaveData();
    }
   
    public override void OnInspectorGUI () 
    {
        DrawDefaultInspector();

        EditorApplication.playmodeStateChanged = () =>
        {
            if( EditorApplication.isPlayingOrWillChangePlaymode && !EditorApplication.isPlaying )
            {
                SaveData();
            }
        };

        EditorUtility.SetDirty(targetTest);
    }

    void SaveData()
    {
        TestData curData =  (TestData)AssetDatabase.LoadAssetAtPath(path, typeof(TestData));
        if(curData==null)
        {
            curData = CreateDatabase();   
        }
        curData.curDataStorage = JsonUtility.ToJson(targetTest);
        AssetDatabase.Refresh();
        AssetDatabase.SaveAssets();   
    }
   
    TestData LoadData()
    {
        TestData curData =  (TestData)AssetDatabase.LoadAssetAtPath(path, typeof(TestData));
       
        if(curData!=null)
        {
            JsonUtility.FromJsonOverwrite( curData.curDataStorage, targetTest );
            return curData;
        }
        else 
        {
            return CreateDatabase();   
        }
    }
   
    TestData CreateDatabase()
    {
        TestData curData = (TestData)ScriptableObject.CreateInstance(typeof(TestData));
        if(curData!=null)
        {
            AssetDatabase.CreateAsset(curData, path);
            AssetDatabase.Refresh();
            AssetDatabase.SaveAssets();
            return curData;
        }
        else
        {
            return null;
        }
    }
}

Every change of the class values after you hit Play, persists in Edit mode.
Kind regards,
-Ippokratis

2 Likes

This is so cool and helpful thanks Ippokratis!

Thanks Bill,
Here is another example, for using it with an Editor Window this time.

Create a folder called Editor and place the following script named ExEditorWindow inside :

using UnityEngine;
using UnityEditor;
using System.Collections;

namespace ExNamespace
{

[System.Serializable]
public class ExEditorWindow : EditorWindow
{
    [MenuItem("Window/Example Editor Window", false, 10)]
    public static void LaunchExEditorWindow()
    {
        var exEditorWindow = EditorWindow.GetWindow<ExEditorWindow>("Example",true, typeof(EditorWindow));
    }
   
    void OnEnable()
    {
        EditorApplication.playmodeStateChanged -= OnPlayModeChanged;
        EditorApplication.playmodeStateChanged += OnPlayModeChanged;
        curData = LoadData();
    }

    void OnDisable()
    {
        EditorApplication.playmodeStateChanged -= OnPlayModeChanged;
        SaveData();
    }
   
    void OnPlayModeChanged()
    {
        if (EditorApplication.isPlayingOrWillChangePlaymode)
        {
            SaveData();
        }
    }

    void OnDestroy()
    {
        EditorApplication.playmodeStateChanged -= OnPlayModeChanged;
        SaveData();
    }
   
    public ExData curData;
    string path = "Assets/ExDataAsset.asset";
    public Color exCol = Color.white;
   
    void OnGUI()
    {
        EditorGUILayout.BeginHorizontal();
        GUILayout.FlexibleSpace();
        GUILayout.Label("Example Editor Window",  GUILayout.Height(40), GUILayout.ExpandWidth(true));
        GUILayout.FlexibleSpace();
        EditorGUILayout.EndHorizontal();
        exCol = EditorGUILayout.ColorField("Example color", exCol);
        //Update the UI every frame
        Repaint();    
    }
   
    void SaveData()
    {
        curData =  (ExData)AssetDatabase.LoadAssetAtPath(path, typeof(ExData));
        if(curData==null)
        {
            curData = CreateDatabase();   
        }
        curData.curDataStorage = JsonUtility.ToJson(this);
        AssetDatabase.Refresh();
        AssetDatabase.SaveAssets();   
    }
   
    ExData LoadData()
    {
        curData =  (ExData)AssetDatabase.LoadAssetAtPath(path, typeof(ExData));
       
        if(curData!=null)
        {
            JsonUtility.FromJsonOverwrite( curData.curDataStorage, this );
            return curData;
        }
        else
        {
            return CreateDatabase();   
        }
    }
   
    ExData CreateDatabase()
    {
        ExData curData = (ExData)ScriptableObject.CreateInstance(typeof(ExData));
        if(curData!=null)
        {
            AssetDatabase.CreateAsset(curData, path);
            AssetDatabase.Refresh();
            AssetDatabase.SaveAssets();
            return curData;
        }
        else
        {
            return null;
        }
    }
}

}//end namespace ExNamespace

Now create another script called ExData.

using UnityEngine;
using System.Collections;

#if UNITY_EDITOR
using UnityEditor;
#endif

namespace ExNamespace
{

[System.Serializable]
public class ExData : ScriptableObject
{   
    public string curDataStorage;
}

}//end namespace ExNamespace

This can serve as a base for your Editor Windows.

First of all, thanks for the guide. I’ve taken your original example and tweaked it in a test project to fit my project’s use case. I’m seeing the data persist across scenes in the editor, but not until I click the object in the hierarchy.
I’m fairly new to Editor scripting, but I think I’m making the connection on why this behavior is happening.

However, I’m looking for a solution that updates the persisted data at run-time. Is this possible using what you’ve laid out here?

Thanks in advance.

When you are in editor mode, you can apply changes only after you select the gameObject. This is expected, I do not know how else you may approach it.

If you need to make changes in play mode that persist, you need to move the save and load data methods from the TestEditor script to the Test script and call it OnEnable, OnDisable, OnDestroy - plus whatever else you need to do for your particular use case.

Share your findings here, if you wish.

Hi,
Great guide got it working fine with simple objects and even vectors. The only thing I’m running into is trouble getting an Array/List to parse through the built in Unity Json. I encapsulated it in a struct (Like the Unity page said.) and it still didn’t work. Is it just not implemented in Unity yet?

example pseudo code.
struct arrayHolder
{
object[ ] arrayOfObjects;
}
void Save()
{
arrayHoler temp;
//fill up the array with objects;
string fileFiller = JsonUtility.ToJson(temp);
WriteFile(path, fileFiller);
}

Also, there is a problem with System.Guid. You cannot serialize\deserialize this type. But there you can also create wrapper class:

[Serializable]
public struct SerializableGuid
{
    public string Value;
    private SerializableGuid(string value)
    {
        Value = value;
    }
    public static implicit operator SerializableGuid(Guid guid)
    {
        return new SerializableGuid(guid.ToString());
    }
    public static implicit operator Guid(SerializableGuid serializableGuid)
    {
        return new Guid(serializableGuid.Value);
    }
}

Advanced version of this wrapper

1 Like

This is a great start Thank You.

The only thing I’d like to know how to do on top of this is to be able to nest the exposed fields.

So in your OP example Nr 0 would be a number and have a bunch of other data nested to that number.

So if I have example 5 objects that share the same info but variations, those variations would then be stored under
(Nr 0 - Nr 4)

Nr 1
which weapon is he carrying
is guy wearing a hat
is guy wearing a red or blue shirt

Nr 2
which weapon is he carrying
is guy wearing a hat
is guy wearing a red or blue shirt :wink:

You can try to modify the above code to your class specs and see how it works.
It is easier to comment on code :slight_smile:

I’m trying to convert a Stack to json, your api takes it without error message but returns {}…
Any hint?
(I tried converting the stack to a list, which is serialized by unity and I still get {})

    [System.Serializable]
    public class SavedData{
        public string entityID;
        public string prefabID;
        public Vector3 position;
        public Quaternion rotation;
        public Panda.BTSnapshot snapshot = null;
    }

    void Save()
    {
        Stack<SavedData> save = new Stack<SavedData> ();
        foreach (KeyValuePair<GameObject, GameObject> g in Pool.poolInstantiated)
        {
            SavedData sd = new SavedData ();
            sd.entityID = g.Key.name;
            sd.prefabID = g.Value.name;
            sd.position = g.Key.transform.position;
            sd.rotation = g.Key.transform.rotation;
            PandaBehaviour pb = g.Key.GetComponent<PandaBehaviour> ();
            if (pb) sd.snapshot = pb.snapshot;
            save.Push (sd);
        }
        string savedString = JsonUtility.ToJson (save);
        PlayerPrefs.SetString (SAVEGAMENAME, savedString);
        hasSaveGame = true;
    }

here is what debug has to say

Did you try creating a wrapper class for the Stack object? Something like this usually works for me:

[System.Serializable]
    public class SavedData{
        public string entityID;
        public string prefabID;
        public Vector3 position;
        public Quaternion rotation;
        public Panda.BTSnapshot snapshot = null;
    }

    [System.Serializable]
    public class SavedDataStack{
        public Stack<SavedData> dataStack;
    }

    void Save()
    {

        SaveDataStack save = new SavedDataStack();

        foreach (KeyValuePair<GameObject, GameObject> g in Pool.poolInstantiated)
        {
            SavedData sd = new SavedData ();
            sd.entityID = g.Key.name;
            sd.prefabID = g.Value.name;
            sd.position = g.Key.transform.position;
            sd.rotation = g.Key.transform.rotation;
            PandaBehaviour pb = g.Key.GetComponent<PandaBehaviour> ();
            if (pb) sd.snapshot = pb.snapshot;
            save.dataStack.Push (sd);
        }
        string savedString = JsonUtility.ToJson (save);
        PlayerPrefs.SetString (SAVEGAMENAME, savedString);
        hasSaveGame = true;
    }

JsonUtility.ToJson still returns “{}” because stack isn’t serialized by unity. I replaced by list and pop() by [0] and removeat(0) and it’s now working.

how can i get url data and show in unity as a grid any one can help me