Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.

[Solved] Saving ScriptableObject into Json

Discussion in 'Scripting' started by joshcamas, Mar 10, 2018.

  1. joshcamas

    joshcamas

    Joined:
    Jun 16, 2017
    Posts:
    1,250
    Hello friends :) I am going a little crazy...

    I have been using this and this as references.

    My problem is with saving a ScriptableObject onto a JSON. More specifically, saving a class / ScriptableObject with another ScriptableObject inside.

    Here is code that works:
    Code (csharp):
    1.  
    2. public class PlayerData : ScriptableObject
    3. {
    4.     public int age;
    5.     public string gender;
    6. }
    7. ...
    8.  
    9. PlayerData playerBoi = ScriptableObject.CreateInstance<PlayerData>();
    10. playerBoi.gender = "d";
    11. playerBoi.age = 12;
    12. string json = JsonUtility.ToJson(playerBoi ,true);
    13.  
    14.  
    This results in:

    Code (csharp):
    1.  
    2. {
    3.     "gender": "d",
    4.     "age": 12
    5. }
    6.  
    Beautiful!! But what if I want some more complex data? My world save data consists of many layers of scriptable objects. This results in some sad behavior:

    Code (csharp):
    1.  
    2. public class SaveWrapper {
    3.     public PlayerData playerData;
    4. }
    5.  
    6. SaveWrapper wrapper = new SaveWrapper();
    7. wrapper.playerData = playerBoi;
    8.  
    9. string json = JsonUtility.ToJson(wrapper ,true);
    10.  
    11.  
    This results in:

    Code (csharp):
    1.  
    2. {
    3.     "playerData": {
    4.         "instanceID": 655202
    5.     }
    6. }
    7.  
    In other words, no matter what I do, the only thing that comes out of a scriptableobject within a class / scriptableobject is the instance id. Nothing else :(

    It's almost as if JSON doesn't know what type it is, so it just casts to "scriptableObject" or even "Object", so it loses all that nice juicy data?

    :( Is there any fix to this?

    Josh
     
  2. Lurking-Ninja

    Lurking-Ninja

    Joined:
    Jan 20, 2015
    Posts:
    8,816
    Is there a good reason why you don't want to use the built-in serialization?

    If there is, serialization works on different way. The instanceId is a reference to the other scriptable object. When you serialize something, you can store the instanceID with it. When you read back the JSON, you read the instanceID and when you read back the referenced object, you will know where to put those.

    The reason why you only the reference instead of the data is that is a separate object (even on the Unity UI) so it gets serialized separately.
     
    MaskedMouse and joshcamas like this.
  3. joshcamas

    joshcamas

    Joined:
    Jun 16, 2017
    Posts:
    1,250
    I ended up using Odin Inspector to serialize this stuff. Unity Engine does not support serializing classes objects within scriptable objects, since on deserialization it would have no idea what class to serialize it as. Odin solves this by adding special keys to the json objects (such as "$type") that it uses specifically to overdo this.

    Also, yus! Sometimes I want to save the scriptableobject, and as you say sometimes I want to save a reference. Forum post here
     
    MaskedMouse likes this.
  4. MaskedMouse

    MaskedMouse

    Joined:
    Jul 8, 2014
    Posts:
    988
    I am running into the same problem with my inventory.
    When I add an item to my inventory, I Instantiate a clone of the item and change its data (such as current stack count).
    Then when I want to save the inventory I serialize it as a json. But then I get InstanceID's as well because the Item class extends ScriptableObject.

    I already noticed whenever I add an Item to the inventory that way I was getting a type mismatch. Seems like I have to split the data that is changed runtime into a pure C# class.
    Or indeed use Odin the serializer on steroids, but I haven't done a lot with Odin yet.

    Example of the Split Data where Items is List<InventoryItem> and InventoryItem contains Item and StackCount.
    That I can serialize and deserialize.

    Code (CSharp):
    1. {
    2.     "Items": [{
    3.             "Item": {
    4.                 "instanceID": 0
    5.             },
    6.             "StackCount": 0
    7.         }, {
    8.             "Item": {
    9.                 "instanceID": 0
    10.             },
    11.             "StackCount": 0
    12.         }, {
    13.             "Item": {
    14.                 "instanceID": 13552
    15.             },
    16.             "StackCount": 5
    17.         }, {
    18.             "Item": {
    19.                 "instanceID": 0
    20.             },
    21.             "StackCount": 0
    22.         }, {
    23.             "Item": {
    24.                 "instanceID": -9810
    25.             },
    26.             "StackCount": 10
    27.         }
    28.     ]
    29. }
     
    eses likes this.
  5. MaskedMouse

    MaskedMouse

    Joined:
    Jul 8, 2014
    Posts:
    988
    @joshcamas how did you solve it with Odin?

    cause even with workarounds there are still quite a lot of times I want to just serialize and deserialize some data of scriptable objects

    With this class structure I wouldn't be able to serialize the player inventory state. Which is quite a pity because I'd like to store the current stack of the item I am keeping in the inventory.
    Which I worked around by a pure C# class containing an Item as reference and an int as stack size.

    But what If I got a weapon, which is upgraded its stats, its an instance then, not the reference stats. Which is why I want to be able to serialize and deserialize these things.

    Code (CSharp):
    1. public class Player : MonoBehaviour
    2. {
    3.         public Inventory Inventory;
    4. }
    5.  
    6. public class Inventory : ScriptableObject
    7. {
    8.         [SerializeField]
    9.         private List<Item> items;
    10. }
    11.  
    12. public class Item : ScriptableObject
    13. {
    14.         public int ItemID;
    15. }
    16.  
    17. public class StackableItem : Item
    18. {
    19.         public int CurrentStack;
    20.         public int MaxStack;
    21. }
     
    Last edited: Aug 1, 2018
    joshcamas likes this.
  6. joshcamas

    joshcamas

    Joined:
    Jun 16, 2017
    Posts:
    1,250
    Odin allows me to serialize anything I need, including unity classes, by running this:

    Code (CSharp):
    1. byte[] serializedData = SerializationUtility.SerializeValue<DataSaveType>(dataToSave, DataFormat.Binary);
    Basically this allows me to save the data to a file, and then load. This is one way to serialize EVERYTHING, however this isn't the best - if you have an item with 5 different fields, and 100 items, that's 500 fields. That adds a lot of bulk.

    Here's a possible solution for you!

    First, I made it so a reference to a scriptable object is serializable. This is done by storing scriptable objects in a lookup table, each with its own GUID. So to save the reference, just save the GUID. To load it, just search the table for the GUID.

    But what if you have upgraded stats?

    How about having a class or struct called "ScriptableObjectReference" that holds an "guid" field (like above), and a dictionary field. (Dictionaries are serializable with Odin)

    So on saving/loading, have your item save/load whatever data you want, including weapon damage or whatever. All you need to do now is save that ScriptableObjectReference!

    Extra fun thing: check if the damage value is equal to the damage value of the original scriptable object. If it is, no need to save it!

    I personally made a much more complex system, it's a bit crazy. Basically its a scriptable object that can have a "parent", and each field can be disabled and enabled. Whenever you "get" one of those fields, it checks if it's enabled. If not, then it goes to the parent, and checks if its enabled, and so on. If enabled, then it returns it.

    The same goes for runtime - when I create an instance of this scriptable object, it creates a "child", with all of its fields disabled. If I ever set a value, then it gets triggered as enabled. Then upon saving, it only grabs the reference to the parent, as well as any modified fields.



    The issues with this is that each item instance or whatever is a scriptable object, which is pretty heavy. If I was to reimplement this, I wouldn't make them scriptable objects. Instead, I would make these objects just a simple class, and then make a separate class that is simply a scriptable object that holds one of these classes inside of it.

    Hope this makes some sort of sense. I would personally go for my first idea, instead of going into the pit of crazyness of my system. I wish you the best, happy to help if you have other questions
     
  7. MaskedMouse

    MaskedMouse

    Joined:
    Jul 8, 2014
    Posts:
    988
    Hmm I was thinking about a scriptable object reference to the item. But have a boolean in there IsModified.
    if it is modified, save the item's override stats to a json in a string. Serialize that and upon reloading deserialize the items first and then do a post action that will deserialize the modified items overwriting their properties again.

    Still kind of annoying to work with workarounds just because the Unity serialization isn't able to serialize nested scriptable objects. Cause if I were to call ToJson on every item in the inventory it would work. Because serializing a scriptable object is possible, just not a nested one.
    It makes things more complicated than I want it to be.
     
  8. joshcamas

    joshcamas

    Joined:
    Jun 16, 2017
    Posts:
    1,250
    That line of code allows nested scriptable objects :))
     
    MaskedMouse likes this.
  9. MaskedMouse

    MaskedMouse

    Joined:
    Jul 8, 2014
    Posts:
    988
    true but it is a binary serialization, won't that have a lot of overhead deserializing?
    Also GC allocation
     
  10. joshcamas

    joshcamas

    Joined:
    Jun 16, 2017
    Posts:
    1,250
    No idea honestly. I only use it for runtime stuff, only when I save the game, and since saving already has overhead, I've never had an issue.

    Are you using this to serialize data in the editor / as assets?
     
    Last edited: Aug 2, 2018
  11. MaskedMouse

    MaskedMouse

    Joined:
    Jul 8, 2014
    Posts:
    988
    Any item would be created in the editor, saved as an .asset file with the CreateAssetMenu.
    [concept]
    If I have a sword, I want to go by the blacksmith give him some money and some items to change the properties of my sword. Let's say the damage went up by 5 and the attack speed went up by 0.1 (it modified the Scriptable Object Item its properties).
    When I want to save my game, I want to save the player's inventory which is a scriptable object containing scriptable object items (Items can be anything, Consumable, Equipable, Quest Item, Junk).

    The Sword is a Weapon and a Weapon is an Equipabble and an Equipabble is an Item. Heavily polymorphing objects there using a class structure to define its own properties. Weapons have damage, Consumables don't that kind of data splitting.

    My Inventory class contains a List<Item> however I cannot serialize this into a json format with Unity's JsonUtility.ToJson
    It will just serialize it as a list of InstanceID's. If I were to deserialize this again (loading a save game) then the runtime edited values (by the black smith) would be gone. It would be a regular sword again.

    If I don't serialize the Inventory but cycle through the inventory items and call ToJson seperately, it does work funnily enough. but then I have seperate jsons for each Item in my inventory with also no way to deserialize it.

    I've tried serializing and deserializing the Inventory with Odin to a binary format but it warns me about that Unity should serialize its own weird objects. I had no success deserializing it either.
    I am kind of wondering if what I am doing is even possible.

    Having scriptable objects just makes it easy to drag and drop things into fields. Just the idea that I could give the player a tutorial inventory with tutorial items and then when the tutorial ends have it reference its own empty starter player inventory.
    if loading from a save game, deserialize the save game and have the inventory loaded with all its items and runtime changed properties again.

    I got like 4 ~ 5 years experience with Unity, but it is the first time I'm actually trying to create this kind of Scriptable Object Inventory / Item structure and trying to serialize it to a save file.
     
    Last edited: Aug 2, 2018
  12. joshcamas

    joshcamas

    Joined:
    Jun 16, 2017
    Posts:
    1,250
    Yeah I used to get a warning but something I did stopped it. Not sure why. I've never had issues with using it.
    This is my code for serializing/deserializing:
    Code (CSharp):
    1. //Serialize - note you can use json serialization if you wanted
    2. byte[] serializedData = SerializationUtility.SerializeValue<WorldSaveData>(worldData, DataFormat.Binary);
    3.  
    4. newWorldData = SerializationUtility.DeserializeValue<WorldSaveData>(serializedData, DataFormat.Binary);
    If you serialize to json, what does the data look like? That can be a useful way to check out what's going on.

    Personally I think this might be a dead end if you don't want to use Odin. Having serialization with no polymorphism is fine most of the time, since scriptable object assets support it... but you can't serialize scriptable objects inside of other scriptable objects, which makes it pretty tough. I tried for quite a while, and almost quit before I ended up using Odin.

    I guess one possibility is to string those jsons together inside of a larger json structure. Involves string manipulation which isn't fun though.
     
  13. ItzChris92

    ItzChris92

    Joined:
    Apr 26, 2017
    Posts:
    371
    @joshcamas I am facing similar problems, so have looked towards Odin. What I would like to know is how you pass in your serialized data in this line of code when deserializing? I could store it as a variable when I serialize, but it would be lost when play stopped.

    Code (CSharp):
    1. newWorldData = SerializationUtility.DeserializeValue<WorldSaveData>(serializedData, DataFormat.Binary);
     
  14. joshcamas

    joshcamas

    Joined:
    Jun 16, 2017
    Posts:
    1,250
    Since it's just a byte array, I just save it to a file! Then I load it the same way ^_^
     
  15. ItzChris92

    ItzChris92

    Joined:
    Apr 26, 2017
    Posts:
    371
    Silly me... just got confused looking at your dummy code lol.

    Also, whenever I serialize a list of scriptable objects, it only saves the instance IDs. I lose all of my data when deserializing because of this. Did you come across this issue?
     
  16. joshcamas

    joshcamas

    Joined:
    Jun 16, 2017
    Posts:
    1,250
    This is strange, are you using Odin? Cause this is what happens when one tries to serialize a scriptable object inside of a scriptable object, using Unity's Serialization...
     
  17. SaturnusK1

    SaturnusK1

    Joined:
    Jan 23, 2018
    Posts:
    7
    Maybe You should've just used [System.Serializable] before your wrapper class.
     
  18. joshcamas

    joshcamas

    Joined:
    Jun 16, 2017
    Posts:
    1,250
    Ah I figured out this issue a long time ago, turns out JSON utility does not serialize scriptable objects inside of other scriptable objects. I had to use another serializer instead. (Odin)
     
  19. shawnblais

    shawnblais

    Joined:
    Oct 11, 2012
    Posts:
    323
    I find it easier to just sidestep the issue, but not trying to force ScriptableObject to be serialized.

    So, instead of:

    Code (CSharp):
    1. class ItemDataObject : ScriptableObject {
    2.     public string itemName;
    3. }
    I'll do this instead:

    Code (CSharp):
    1. class ItemDataObject : ScriptableObject {
    2.     public ItemData data;
    3. }
    4. [System.Serializable]
    5. class ItemData {
    6.     public string itemName;
    7. }
    8.  
    Then I can use the ScriptableObject within unity as normal, but when it comes time to serialize, we can just save out the data itself using standard JSON, no issues.
     
  20. joshcamas

    joshcamas

    Joined:
    Jun 16, 2017
    Posts:
    1,250
    If it works for ya, then awesome! ^_^ I personally use a data system that uses a lot of scriptableobjects that need saving, so sadly I cannot go that way :(
     
  21. FrostFT

    FrostFT

    Joined:
    Jul 2, 2015
    Posts:
    4
    Show please example of using serialize/deserialize.
     
  22. joshcamas

    joshcamas

    Joined:
    Jun 16, 2017
    Posts:
    1,250
    Dpo you mean how I personally do it, using Odin serializer?
     
  23. FrostFT

    FrostFT

    Joined:
    Jul 2, 2015
    Posts:
    4
    yes. I wanna know how to deserialize. Show more completable example.
     
  24. joshcamas

    joshcamas

    Joined:
    Jun 16, 2017
    Posts:
    1,250
    Well, I'll just repost what I posted earlier.

    Code (CSharp):
    1.    
    2. //Serialize
    3. byte[] serializedData = SerializationUtility.SerializeValue<WorldSaveData>(worldData, DataFormat.Binary);
    4.  
    5. //Deserialize
    6. newWorldData = SerializationUtility.DeserializeValue<WorldSaveData>(serializedData, DataFormat.Binary);
    7.  
    In this case, I have a class called WorldSaveData. worldData is an instance of this class, and I want to serialize it. Using the first line, I serialize it using Binary serialization (Note the "DataFormat.Binary"), and it saves it to a byte array. I can then do whatever I want with this, such as saving it to a file. Then, if I want to deserialize it, I use the second line - converting the byte array back into the WorldSaveData type.
     
  25. Tosatsu

    Tosatsu

    Joined:
    Sep 4, 2014
    Posts:
    14
    This went down the rabbit hole, no joke. I don't really like the usage of ScriptableObjects for setting up items that will be instances in the Game. There might be usages I am missing, but it seems more straightfoward to use components, and leave ScriptableObjects as pure data containers.
    You might say, yes, this is what occurs here, but it's not true. Say you implement a drop table of items, that can be a scriptable object, as there is no reason to repeatedly copy paste the droptable component. But for items themselves, which can have very unique properties, it looks like it gets in the way a lot.
    Say you instantiate 10 swords they will need to Instantiate their own SO because if they reference the original SO asset, you end up with 10 upgraded swords if you upgrade one. So yeah. I am more curious about everyone elses oppinion on this.
    I see their use for generic items, who share their data, but this serialization issue seems like a huge pain.\
    I am currently fighting to find an optimal solution too :D
     
    BorisOkunskiy and toomasio like this.
  26. Woj_Gabel_FertileSky

    Woj_Gabel_FertileSky

    Joined:
    Jun 18, 2012
    Posts:
    61
    Hello.
    I was also trying to implement some easy solutions of a dynamic inventory system. I really wanted to use Scriptable Objects.
    I was having problems with maintaining polymorphism, minimal code smell and easy of use.
    The best I did is this:

    Code (CSharp):
    1. public abstract class ItemModel : ScriptableObject
    2.     {
    3.         [SerializeField]
    4.         protected int id;
    5.         public int Id { get { return this.id; } }
    6.  
    7.         public string Serialize<T> ( T itemModel ) where T : ItemModel
    8.         {
    9.             string s = "";
    10.             s = JsonUtility.ToJson ( itemModel );
    11.             return s;
    12.         }
    13.  
    14.         public T DeSerialize<T> ( string s, T itemToOverwrite ) where T : ItemModel
    15.         {
    16.             JsonUtility.FromJsonOverwrite ( s, itemToOverwrite );
    17.             return itemToOverwrite;
    18.         }
    19.     }
    And now with the base class. I think this could be in the ItemModel class, but it's ok for this example.

    Code (CSharp):
    1. [CreateAssetMenu ( fileName = "newItem", menuName = "Inventory4.0/items/Basic Item" )]
    2.     public class ItemBasicModel : ItemModel
    3.     {
    4.         public string itemName;
    5.     }
    Code (CSharp):
    1. [CreateAssetMenu ( fileName = "newItem", menuName = "Inventory4.0/items/living" )]
    2.     public class ItemLivingModel : ItemSpaceEntityModel
    3.     {
    4.         public int hitPoints;
    5.     }
    And to test it:
    Code (CSharp):
    1.     public class ItemSystemTest : MonoBehaviour
    2.     {
    3.         public List<ItemModel> inventory;
    4.  
    5.         T GetItem<T> ( int id ) where T : ItemModel
    6.         {
    7.             return inventory.Find ( myItem => myItem.Id == id ) as T;
    8.         }
    9.  
    10.         [ContextMenu ( "Test" )]
    11.         void Test ()
    12.         {
    13.             ItemModel item2 = GetItem<ItemModel> ( 3 );
    14.             string s = item2.Serialize<ItemModel> ( item2 );
    15.             Debug.Log ( s );
    16.  
    17.             ItemModel newItem = item2.DeSerialize<ItemModel> ( s, item2 );
    18.             Debug.Log ( item2.Serialize<ItemModel> ( item2 ) );
    19.  
    20.         }
    21.     }
    Both of them produce the same result:
    Code (CSharp):
    1. {"id":3,"itemName":"Basic Living cube but Bigger","prefab":{"instanceID":-3319188},"initialScale":2.0,"hitPoints":50}
    Now, if you want to have some a container, that is also derived from the ItemModel, with the list of ItemModels, then you need to figure out how to change the instanceID's to actual serialized strings(In case of referencing other SCriptableObjects in the serialized ScriptableObject). I did it with editing the string in a recursive function in the serializing method of the container class. But I think a better approach would be to use the Serialization callbacks.

    Now there is a problem with creating and maintaining seperate instances of those Items.
    My approach to that problem:
    Code (CSharp):
    1. public abstract class WorldEntity : MonoBehaviour
    2.     {
    3.         private ItemModel itemModelCopy;
    4.         //
    5.         //Summary:
    6.         //
    7.         //Returns the copy if the item this entity was spawned from.
    8.         //It is safe to work on, because it is a seperate instance of the itemModel.
    9.         //
    10.         public T GetItemModel<T> () where T : ItemModel
    11.         {
    12.             return itemModelCopy as T;
    13.         }
    14.  
    15.         public void SetItemModelCopy<T> ( T item ) where T : ItemModel
    16.         {
    17.             itemModelCopy = item;
    18.         }
    19. }
    Code (CSharp):
    1. [CreateAssetMenu ( fileName = "newItem", menuName = "Inventory4.0/items/item with Entity" )]
    2.     public class ItemSpaceEntityModel : ItemBasicModel
    3.     {
    4.         [SerializeField]
    5.         protected Transform prefab;
    6.         public Transform Prefab { get { return this.prefab; } }
    7.  
    8.         public float initialScale = 1;
    9.  
    10.         public OUTPUT Spawn<INPUT, OUTPUT> ( INPUT initialData )
    11.         where OUTPUT : WorldEntity
    12.         where INPUT : AData
    13.         {
    14.             Transform t = UnityEngine.Object.Instantiate ( prefab );
    15.             OUTPUT o = t.GetComponent<OUTPUT> ();
    16.             o.SetItemModelCopy<ItemSpaceEntityModel> ( UnityEngine.Object.Instantiate ( this));
    17.             return o.InitialiseWithData<INPUT, ItemSpaceEntityModel, OUTPUT> ( this, initialData );
    18.         }
    19.     }
    20.  
    21.     public abstract class AData
    22.     {
    23.  
    24.     }
    25.  
    26.     public class SpawnData : AData, ITest
    27.     {
    28.         public string inputString;
    29.  
    30.         public virtual string TestMyself ( AData data = null )
    31.         {
    32.             return "inputString: " + inputString;
    33.         }
    34.     }
    And the Entity that is holding the instance:


    Code (CSharp):
    1. public abstract class WorldEntity : MonoBehaviour
    2.     {
    3.         private ItemModel itemModelCopy;
    4.         //
    5.         //Summary:
    6.         //
    7.         //Returns the copy if the item this entity was spawned from.
    8.         //It is safe to work on, because it is a seperate instance of the itemModel.
    9.         //
    10.         public T GetItemModel<T> () where T : ItemModel
    11.         {
    12.             return itemModelCopy as T;
    13.         }
    14.  
    15.         public void SetItemModelCopy<T> ( T item ) where T : ItemModel
    16.         {
    17.             itemModelCopy = item;
    18.         }
    19.  
    20.         public abstract OUTPUT InitialiseWithData<SPAWNDATA, MODELDATA, OUTPUT> ( MODELDATA itemInventoryModel, SPAWNDATA initialData )
    21.         where SPAWNDATA : AData
    22.         where OUTPUT : WorldEntity
    23.         where MODELDATA : ItemModel;
    24.     }
    Sorry for any errors in the code, I was copy pasting without much cleanup.
    I hope it helps.
     
    Last edited: Nov 15, 2018
  27. AubreyH

    AubreyH

    Joined:
    May 17, 2018
    Posts:
    18
    I found that if I was using JsonUtility.FromJsonOverwrite on a scriptable object, and that scriptable object stored references to other scriptable objects, then it would not find the scriptable object being referred to, if, and only if, that scriptable object had not yet been loaded into the scene. i.e. if your code hasn't "touched" that data or read from it in any particular way, it won't have been loaded, and thus the id will come back null during deserialisation. So it's a kinda race condition thing.

    My work around was to make sure I loaded all my saved game data after I loaded all the game data. I had previously been using JsonUtility.FromJsonOverwrite in "Awake". At that point it was able to link the ID up to the actual scriptable object. This may not be ideal as it means loading everything you *might* need before you deserialize your data into your scriptable object.

    I can't be sure this is 100% why this happens, but loading my save data much later worked for me.
     
  28. fmoo

    fmoo

    Joined:
    May 31, 2015
    Posts:
    15
    I know this is a pretty old post, but I ran into this issue as well, and since it comes up first on Google, I wanted to share my approach.

    I went down the road that @joshcamas suggested about using serializable references, and documented it and have uploaded the code here: https://github.com/fmoo/Orange/tree/master/Assets/Scripts/Serialization. I also included an `SORefType` attribute decorator for the Editor to restrict selection to ScriptableObjects of the proper type.
     
  29. Dextozz

    Dextozz

    Joined:
    Apr 8, 2018
    Posts:
    488
    TenenetGameStudio and aweha like this.
  30. Kabinet13

    Kabinet13

    Joined:
    Jun 13, 2019
    Posts:
    8
    Utter Newbie here, when deserializing the data, would you have to instantiate a load of serializable objects into a list and loop through them applying the deserialized data as you go?
     
  31. bloodthirst69

    bloodthirst69

    Joined:
    Oct 1, 2017
    Posts:
    24
    i stumbled on this problem myself and i found a solution to use JSON serialization (Newtonsoft) while keeping the references to assets and prefabs , here's a video i made explaining how it works in case anyone is interrested.
     
  32. usernameHed

    usernameHed

    Joined:
    Apr 5, 2016
    Posts:
    90
    Hello Blood! I will post here and not on youtube, sorry for the duplicate:

    Hello! First of all, really really awesome video! I've listen to it twice and I'm implementing it on my project. 2 issue so far:
    First, what's the:
    Code (CSharp):
    1. private static UnityObjectContractResolver UnityObjectContractResolver = new UnityObjectContractResolver();
    I see that you use it for the ContractResolver of the GetJsonSerializerSettings(), but I don't see what is it.

    - Second little thing: as I don't know your function TypeUtils.IsSubClassOf, I assumed that I can do that:
    Code (CSharp):
    1. if (!objectType.IsSubclassOf(typeof(UnityEngine.Object))) return (false);

    - Third and most hot part for me:

    I'm using a main ScriptableObject that hold reference to other scriptableObject
    (and child's scriptableObject can also have other scriptableObject Reference, as well as materials, prefabs...)
    (PS: I'm using a special Drawer to be able to display them like the [InlineEditor] of Odin)

    Do you think that your method can work if all these scriptableObject derive from a
    Code (CSharp):
    1. BaseScriptableObject : ScriptableObject, ISerializationCallbackReceiver
    with your implementation ?

    I was thinking about just change all link by Type + Names of the files, and in the serialization Process, do a Resource.Load(), Since I strictly setup the names of my files, and they are all in a Resources folder. But with what you've said at the end that we cannot do it during serialization, it's sucks :(

    Thanks!
     
  33. bloodthirst69

    bloodthirst69

    Joined:
    Oct 1, 2017
    Posts:
    24
    Hey ! glad my vid helped someone (although it is kinda rushed an assumes the user is familiar with this stuff) , well first or all lemme give the solutions to the first 2 issue u mentioned :

    -1 the UnityObjectContractResolver is this :
    Code (CSharp):
    1.     public class UnityObjectContractResolver : DefaultContractResolver
    2.     {
    3.         protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
    4.         {
    5.  
    6.             IList<JsonProperty> properties = base.CreateProperties(type, memberSerialization);
    7.             if (type.IsSubclassOf(typeof(UnityEngine.Object)))
    8.             {
    9.                 foreach (JsonProperty property in properties)
    10.                 {
    11.                     if (property.PropertyName.Equals("name") || property.PropertyName.Equals("hideFlags"))
    12.                         property.Ignored = true;
    13.  
    14.                 }
    15.             }
    16.             return properties;
    17.         }
    18.     }
    so why do we need this ? well unity doesn't want to have these fields ("name" and 'hideFlags") to be messed with during the serialization process , so we use this as a sort of filter , and what it does is it passes you a type as a parameter and expects from you to return the list of properties to be written in the json , and since we don't wanna write those fields then we just tell the serializer to ignore em and unity will be happy.

    -2 TypeUtils.IsSubClassOf is basically what u mentioned , i just don't like the way it's named originally
    here's the actual method

    Code (CSharp):
    1.  
    2.         /// <summary>
    3.         /// Is <paramref name="child"/>  a subtype (or same type) as <paramref name="parent"/> ?
    4.         /// </summary>
    5.         /// <param name="child"></param>
    6.         /// <param name="parent"></param>
    7.         /// <returns></returns>
    8.         public static bool IsSubTypeOf(Type child, Type parent)
    9.         {
    10.             if (child == parent)
    11.                 return true;
    12.  
    13.             return parent.IsAssignableFrom(child);
    14.         }
    now to the other question , can it work with the case u mentioned ? yes , it should , BUT!
    The way i made this is kinda hacky but justified , in my case , i know that :
    -"TreeNodeData" will always be the root object
    -"TreeNodeData" will never have child objects (or child-child objects , or child-child-child objects , etc) or type "TreeNodeData"
    that's why i can afford to write that dirty check like "if(obj is UnityEngine.Object && obj is not NodeTreeData).

    i actually solved this problem too , and made it work "correctly" for most cases , but to explain it i will need another video hah ! hope this helps u get through tho
     
  34. ArikaHoshino

    ArikaHoshino

    Joined:
    Sep 14, 2020
    Posts:
    13
    Hi, did you finally get it to work? I am also stuck when serializing ScriptableObject with fields of scriptableObjects and other Unity Objects
     
  35. bloodthirst69

    bloodthirst69

    Joined:
    Oct 1, 2017
    Posts:
    24
    Hey again and sorry for the delay, i made another update that will probably solve the problem definitively of JSON serialization for the folks here , feel free to check it out and ask any question
     
    IggyZuk likes this.
  36. DSivtsov

    DSivtsov

    Joined:
    Feb 20, 2019
    Posts:
    148
    Last edited: Nov 7, 2022
    miriamcg likes this.