I am using JSON.net to serialize my scriptable object.
I am not using JSONutility because my scriptable object is nested with references and dictionaries which JsonUtility does not support.
While serializing the SO works well, I am unable to deserialize the json string and load it back into the SO.
Running the code below gives a warning saying SO must be created using CreateInstance instead of new and the data in the SO is not updated.
data = JsonConvert.DeserializeObject<ScriptableObject>(jsonstring, settings);
Running the code below, gives a JSON serialization exception since it is unable to create an instance of a type which is an abstract class in the nested SOs
JsonConvert.PopulateObject(jsonstring, data);
This is the full error:
JsonSerializationException: Could not create an instance of type Raraland.BaseCategory. Type is an interface or abstract class and cannot be instantiated.
I cannot use JsonUtility.FromJsonOverwrite since any lists and nested scriptable objects in my json, don’t get parsed on loading.
How do I deserialize json into my SO using JSON.net? Or is there any other json library that will allow this?
Not sure about serializing scriptable objects in particular, but serializers by default don’t handle polymorphism. You have to tell them explicitly to do that, because it’s more costly, and they may require additional information to keep the cost relatively low.
Typically that involves providing them with a list of known types, or handling conversions more explicitly.
The easiest way in Json.Net (which leads to larger file sizes, and potential security risks) is to tell it to store the full types. Play with the TypeNameHandling setting when serializing the files
.Auto - should provide explicit types only where it’s needed (polymorphic objects)
.All - I think this option provides explicit types even when they aren’t needed
JsonSerializerSettings settings = new JsonSerializerSettings()
{
TypeNameHandling = Newtonsoft.Json.TypeNameHandling.Auto
};
There are other ways to do it in Json.Net as well. You can look them up.
Also, it’s advisable to let unity serialize it’s own weird types. If you’re gonna serialize with JSON, use a plain old C# class.
ScriptableObjects are actually wrappers around native C++ objects. That’s why they’re created via ScriptableObject.Instantiate() and not via new(). JSON.NET can’t now about this and sooner or later something will break.
If you want to have stuff both serialized with Jason and as assets, mark your custom class as [Serializable] and have scriptableObject that holds a reference to it. If you’re on 2019.3+ , you can use [Serialize reference] for the added flexibility.
I actually did use the AutoSettings, forgot to include it in the code. It still however doesn’t seem to work.
There are still some SO types which it warns about handling incorrectly i.e the new operator is being called on them instead of CreateInstance. These SO’s are actually nested deep within the parent SO i’m trying to de/serialize
My current architecture is based on loading initial values from SOs. I then manipulate values in the SOs during gameplay and save then out to file as JSON. The next time the game loads I want to now fill the SO values from the saved json file.
Do you have any suggestions on what I can do instead?
SOs made creating and storing these initial game values a breeze.
I am able to serialize it without any issues. Just unable to deserialize it.
Internally Json.net creates a new instance on deserialization whereas scriptable objects don’t allow creation using the new keyword. Instead they need to use instantiate or createInstance.
Yes I did try it but it doesn’t work either. My SOs are deeply nested and this ends up giving me a warning about deeper nested SOs that they must be created using CreateInstance instead of new and the data in the SO is not updated.
What I did in my serialization tool ( I user Odin serializer instead of JSON.net so I could choose JSON for debugging purposes, and binary for speed in the final product, but I’m going off-topic) is the following:
Create an abstract class that has a GUID, and implements ISerializationCallbackRecevied so the GUID persists. Let’s call that “Saveable”. Mark this class with [Serializable]so you can save it along with any Unity type, be it MonoBehaviours, SOs, etc.
Then you create concrete classes to hold your data, for example
public Vector3 position, scale;
public Quaternion rotation;
}```
Now let's say you want to save this as part of your player data. In your ```Player : Monobehaviour``` class, add a variable of type ```SaveableTransform```
The second part of this is accessing your saved data. What I did was create a global manager using a ```ScriptableObjectSingleton```, the code for which you can easily find on google. This ```SaveableManager : ScriptableObjectSingleton``` has a ```Dictionary<GUID, byte[ ]>``` that holds the GUID and saved data of all the ```Saveable``` objects. The object themselves are self-registering on startup.
This way, you provide a path to your saved data (I chose to have a .zip file with a lot of plain text files inside, each one named as the same as the GUID for easy lookup, but again this is for ease of debugging. For production you probably want a binary blob of data that can be accessed faster).
On application start, you load all the saved data to your dictionary and, when a ```Saveable``` is accessed, you go there to fetch your data and populate the object in a lazy manner. If the data is not found, you can fallback to the default values saved alongside your Monobehaviour/ScriptableObject.
This is a solution I've come along with recently, and is in no way battle-tested. If you need help PM me and I can send you my code.
Good luck!
For anyone reading this, and in case it helps somehow. I must admit I did not read the full thread but if the issue is Unity Deserialize Into Scriptable Object the below link from S.O. might help.
So basically I came across this and didn’t find a answer that I found I liked, so I kinda fucked around.
What I did was “Sister-classes” for the Scriptable objects.
Both the scriptable object and the class itself have a way to funnel data from one to the other.
Kinda like this
This is the class I convert to and from JSON essentially:
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using Utility;
namespace Data.Serialize
{
public class FunctionDataSerialize
{
public FunctionUtility.FunctionNames functionName;
public int totalGamesPlayedWithThisFunction;
public float totalTimePlayedWithThisFunction;
public Vector2Int winLose = Vector2Int.zero;
public float winLostRatioWithFunction;
public int maxScoreWithFunction;
public int minScoreWithFunction;
public float averageScoreWithFunction;
public List<int> totalScoreGathered;
public FunctionDataSerialize()
{
functionName = FunctionUtility.FunctionNames.Abs;
totalGamesPlayedWithThisFunction = 0;
totalTimePlayedWithThisFunction = 0f;
winLose = Vector2Int.zero;
winLostRatioWithFunction = 0f;
maxScoreWithFunction = 0;
minScoreWithFunction = 0;
averageScoreWithFunction = 0f;
totalScoreGathered = new List<int>();
}
public FunctionDataSerialize(TetrisFunctionData data)
{
functionName = data.functionName;
totalGamesPlayedWithThisFunction = data.totalGamesPlayedWithThisFunction;
totalTimePlayedWithThisFunction = data.totalTimePlayedWithThisFunction;
winLose = data.winLose;
winLostRatioWithFunction = data.winLostRatioWithFunction;
maxScoreWithFunction = data.maxScoreWithFunction;
minScoreWithFunction = data.minScoreWithFunction;
averageScoreWithFunction = data.averageScoreWithFunction;
totalScoreGathered = data.totalScoreGathered.ToList();
}
}
}
And this is the main part of the ScriptableObject:
public class TetrisFunctionData : SerializedScriptableObject
{
public FunctionUtility.FunctionNames functionName;
[SerializeField] public int totalGamesPlayedWithThisFunction;
[SerializeField] public float totalTimePlayedWithThisFunction;
[SerializeField] public Vector2Int winLose = Vector2Int.zero;
[SerializeField] public float winLostRatioWithFunction;
[SerializeField] public int maxScoreWithFunction;
[SerializeField] public int minScoreWithFunction;
[SerializeField] public float averageScoreWithFunction;
[SerializeField] public List<int> totalScoreGathered;
public void Setup(FunctionDataSerialize data)
{
functionName = data.functionName;
totalGamesPlayedWithThisFunction = data.totalGamesPlayedWithThisFunction;
totalTimePlayedWithThisFunction = data.totalTimePlayedWithThisFunction;
winLose = data.winLose;
winLostRatioWithFunction = data.winLostRatioWithFunction;
maxScoreWithFunction = data.maxScoreWithFunction;
minScoreWithFunction = data.minScoreWithFunction;
averageScoreWithFunction = data.averageScoreWithFunction;
totalScoreGathered = data.totalScoreGathered.ToList();
}
.....
Then somewhere in the code I do the JSON magic:
public void Serialize() // send to DB
{
var jsonSerializerSettings = new JsonSerializerSettings();
jsonSerializerSettings.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter());
var data = new PlayerSerialize(this);
}
And this:
if (result.Data != null && result.Data.ContainsKey("Data"))
{
var da = JsonConvert.DeserializeObject<PlayerSerialize>(result.Data["Data"].Value);
score = da.score;
playerName = da.playerName;
tetrisGameData.Setup(da.tetrisGameData);
Debug.Log("I am done with player setup");
}
Note that this “da.tetrisGameData” is a ScriptableObject that has other ScriptableObjects in it. in that setup it loops through its list and calls their Setup() methods. And so on.
And of Course this PlayerSerialize is a Sister class here for the total info of the Player Data I hold (Which has SOs within SOs with lists of them…)
I am unsure if this is the best practice, but it is a workaround if you still wish to use the Newtonsoft Json and ScriptableObjects in runtime.
Yup, I think having a “POCO sister object” which contains the same fields is probably the best method for now. If someone made code generation for this I could see that making things easier.
No, not really. Json.NET has several different methods and constructs to deal with such things. First of all the equivalent method to Unity’s FromJsonOverwrite would be PopulateObject as it was already mentioned in the top post. The issue he had was that he seems to have polymorphic classes which isn’t supported by Unity’s JsonUtility and of course creates issues with Json.NET as well. Polymorphic data can only be supported when the serializer stores metadata about the actual instance type.
Json.NET also has support for a CustomCreationConverter. So that converter would take care of creating the scriptable object. You could create a single implementation that could be used for any ScriptableObject.
public class SOConverter<T> : CustomCreationConverter<T> where T : ScriptableObject
{
public override T Create(Type aObjectType)
{
if (typeof(T).IsAssignableFrom(aObjectType))
return (T)ScriptableObject.CreateInstance(aObjectType);
return null;
}
}
With that class you should be able to use DeserializeObject and provide the necessary converters.
YourSOType obj = JsonConvert.DeserializeObject<YourSOType>(json, new SOConverter<YourSOType>(), new SOConverter<SomeOtherSubSOType>());
Here Json.NET should use our converter to actually create the SO instances. Our converter uses the proper CreateInstance call to create the instances. However in theory you could implement any kind of lookup / conversion / replacement with those custom converters. You may want to look into how those converters work. A converter can even customize the generated json and also apply some custom parsing to it. So it would be possible to store some kind of ID for certain SO assets and when deserializing, lookup those IDs in your own database and return the shared asset instead. It’s a bit of work, but the library is quite powerful when utilized correctly.
To be honest, I have barely used the Newtonsoft Json.NET library as I wasn’t in need for any more advanced serializing features. Everyone has to decide for themselfs which approach would fit the usecase best.