Newtonsoft json deserialize Material

Hey,
I’m trying to save some data as json - data persistency. I’ve tried JsonUtility which fails at inheritance, so I’ve switched to Newtonsoft Json.NET.

I am having problem with deserialization of a Material. I am saving NPC data, which contains a material reference JsonUtility is serializing it by saving InstanceID, but Newtonsoft Json.NET does full material setup (with properties, keywords etc.). On deserialization I get this error:

Error while loading data to file: Newtonsoft.Json.JsonSerializationException: Unable to find a constructor to use for type UnityEngine.Material. A class should either have a default constructor, one constructor with arguments or a constructor marked with the JsonConstructor attribute. Path 'npcs[0].data.material.shader', line 159, position 19.
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateNewObject (Newtonsoft.Json.JsonReader reader, Newtonsoft.Json.Serialization.JsonObjectContract objectContract, Newtonsoft.Json.Serialization.JsonProperty containerMember, Newtonsoft.Json.Serialization.JsonProperty containerProperty, System.String id, System.Boolean& createdFromNonDefaultCreator) [0x000d5] in <761cf2a144514d2291a678c334d49e9b>:0 
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject (Newtonsoft.Json.JsonReader reader, System.Type objectType, Newtonsoft.Json.Serialization.JsonContract contract, Newtonsoft.Json.Serialization.JsonProperty member, Newtonsoft.Json.Serialization.JsonContainerContract containerContract, Newtonsoft.Json.Serialization.JsonProperty containerMember, System.Object existingValue) [0x00148] in <761cf2a144514d2291a678c334d49e9b>:0 
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal (Newtonsoft.Json.JsonReader reader, System.Type objectType, Newtonsoft.Json.Serialization.JsonContract contract, Newtonsoft.Json.Serialization.JsonProperty member, Newtonsoft.Json.Serialization.JsonContainerContract containerContract, Newtonsoft.Json.Serialization.JsonProperty containerMember, System.Object existingValue) [0x0006d] in <761cf2a144514d2291a678c334d49e9b>:0 
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.SetPropertyValue (Newtonsoft.Json.Serialization.JsonProperty property, Newtonsoft.Json.JsonConverter propertyConverter, Newtonsoft.Json.Serialization.JsonContainerContract containerContract, Newtonsoft.Json.Serialization.JsonProperty containerProperty, Newtonsoft.Json.JsonReader reader, System.Object target) [0x00065] in <761cf2a144514d2291a678c334d49e9b>:0 
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject (System.Object newObject, Newtonsoft.Json.JsonReader reader, Newtonsoft.Json.Serialization.JsonObjectContract contract, Newtonsoft.Json.Serialization.JsonProperty member, System.String id) [0x00280] in <761cf2a144514d2291a678c334d49e9b>:0 
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject (Newtonsoft.Json.JsonReader reader, System.Type objectType, Newtonsoft.Json.Serialization.JsonContract contract, Newtonsoft.Json.Serialization.JsonProperty member, Newtonsoft.Json.Serialization.JsonContainerContract containerContract, Newtonsoft.Json.Serialization.JsonProperty containerMember, System.Object existingValue) [0x00161] in <761cf2a144514d2291a678c334d49e9b>:0 
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal (Newtonsoft.Json.JsonReader reader, System.Type objectType, Newtonsoft.Json.Serialization.JsonContract contract, Newtonsoft.Json.Serialization.JsonProperty member, Newtonsoft.Json.Serialization.JsonContainerContract containerContract, Newtonsoft.Json.Serialization.JsonProperty containerMember, System.Object existingValue) [0x0006d] in <761cf2a144514d2291a678c334d49e9b>:0 
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.SetPropertyValue (Newtonsoft.Json.Serialization.JsonProperty property, Newtonsoft.Json.JsonConverter propertyConverter, Newtonsoft.Json.Serialization.JsonContainerContract containerContract, Newtonsoft.Json.Serialization.JsonProperty containerProperty, Newtonsoft.Json.JsonReader reader, System.Object target) [0x00065] in <761cf2a144514d2291a678c334d49e9b>:0 
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject (System.Object newObject, Newtonsoft.Json.JsonReader reader, Newtonsoft.Json.Serialization.JsonObjectContract contract, Newtonsoft.Json.Serialization.JsonProperty member, System.String id) [0x00280] in <761cf2a144514d2291a678c334d49e9b>:0 
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject (Newtonsoft.Json.JsonReader reader, System.Type objectType, Newtonsoft.Json.Serialization.JsonContract contract, Newtonsoft.Json.Serialization.JsonProperty member, Newtonsoft.Json.Serialization.JsonContainerContract containerContract, Newtonsoft.Json.Serialization.JsonProperty containerMember, System.Object existingValue) [0x00161] in <761cf2a144514d2291a678c334d49e9b>:0 
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal (Newtonsoft.Json.JsonReader reader, System.Type objectType, Newtonsoft.Json.Serialization.JsonContract contract, Newtonsoft.Json.Serialization.JsonProperty member, Newtonsoft.Json.Serialization.JsonContainerContract containerContract, Newtonsoft.Json.Serialization.JsonProperty containerMember, System.Object existingValue) [0x0006d] in <761cf2a144514d2291a678c334d49e9b>:0 
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateList (System.Collections.IList list, Newtonsoft.Json.JsonReader reader, Newtonsoft.Json.Serialization.JsonArrayContract contract, Newtonsoft.Json.Serialization.JsonProperty containerProperty, System.String id) [0x00173] in <761cf2a144514d2291a678c334d49e9b>:0 
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateList (Newtonsoft.Json.JsonReader reader, System.Type objectType, Newtonsoft.Json.Serialization.JsonContract contract, Newtonsoft.Json.Serialization.JsonProperty member, System.Object existingValue, System.String id) [0x001c6] in <761cf2a144514d2291a678c334d49e9b>:0 
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal (Newtonsoft.Json.JsonReader reader, System.Type objectType, Newtonsoft.Json.Serialization.JsonContract contract, Newtonsoft.Json.Serialization.JsonProperty member, Newtonsoft.Json.Serialization.JsonContainerContract containerContract, Newtonsoft.Json.Serialization.JsonProperty containerMember, System.Object existingValue) [0x0007f] in <761cf2a144514d2291a678c334d49e9b>:0 
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.SetPropertyValue (Newtonsoft.Json.Serialization.JsonProperty property, Newtonsoft.Json.JsonConverter propertyConverter, Newtonsoft.Json.Serialization.JsonContainerContract containerContract, Newtonsoft.Json.Serialization.JsonProperty containerProperty, Newtonsoft.Json.JsonReader reader, System.Object target) [0x00065] in <761cf2a144514d2291a678c334d49e9b>:0 
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject (System.Object newObject, Newtonsoft.Json.JsonReader reader, Newtonsoft.Json.Serialization.JsonObjectContract contract, Newtonsoft.Json.Serialization.JsonProperty member, System.String id) [0x00280] in <761cf2a144514d2291a678c334d49e9b>:0 
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject (Newtonsoft.Json.JsonReader reader, System.Type objectType, Newtonsoft.Json.Serialization.JsonContract contract, Newtonsoft.Json.Serialization.JsonProperty member, Newtonsoft.Json.Serialization.JsonContainerContract containerContract, Newtonsoft.Json.Serialization.JsonProperty containerMember, System.Object existingValue) [0x00161] in <761cf2a144514d2291a678c334d49e9b>:0 
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal (Newtonsoft.Json.JsonReader reader, System.Type objectType, Newtonsoft.Json.Serialization.JsonContract contract, Newtonsoft.Json.Serialization.JsonProperty member, Newtonsoft.Json.Serialization.JsonContainerContract containerContract, Newtonsoft.Json.Serialization.JsonProperty containerMember, System.Object existingValue) [0x0006d] in <761cf2a144514d2291a678c334d49e9b>:0 
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize (Newtonsoft.Json.JsonReader reader, System.Type objectType, System.Boolean checkAdditionalContent) [0x000db] in <761cf2a144514d2291a678c334d49e9b>:0 
at Newtonsoft.Json.JsonSerializer.DeserializeInternal (Newtonsoft.Json.JsonReader reader, System.Type objectType) [0x00054] in <761cf2a144514d2291a678c334d49e9b>:0 
at Newtonsoft.Json.JsonSerializer.Deserialize (Newtonsoft.Json.JsonReader reader, System.Type objectType) [0x00000] in <761cf2a144514d2291a678c334d49e9b>:0 
at Newtonsoft.Json.JsonConvert.DeserializeObject (System.String value, System.Type type, Newtonsoft.Json.JsonSerializerSettings settings) [0x0002d] in <761cf2a144514d2291a678c334d49e9b>:0 
at Newtonsoft.Json.JsonConvert.DeserializeObject[T] (System.String value, Newtonsoft.Json.JsonSerializerSettings settings) [0x00000] in <761cf2a144514d2291a678c334d49e9b>:0 
at Newtonsoft.Json.JsonConvert.DeserializeObject[T] (System.String value) [0x00000] in <761cf2a144514d2291a678c334d49e9b>:0 
at FileDataHandler.Load () [0x00079] in C:\Data\Projects\TheHiddenWorld\Assets\Scripts\DataPersistence\FileDataHandler.cs:34 
UnityEngine.Debug:LogError (object)
FileDataHandler:Load () (at Assets/Scripts/DataPersistence/FileDataHandler.cs:38)
DataPersistencyManager:LoadGame () (at Assets/Scripts/DataPersistence/DataPersistencyManager.cs:38)
DataPersistencyManager:Start () (at Assets/Scripts/DataPersistence/DataPersistencyManager.cs:16)

Any idea how to solve this? I am aware of it is probably due to compiler stripping, but how to force Unity not to skip Material constructors? I’ve tried Unity - Manual: Managed code stripping, but did not have luck.

I can live with hacky custom referencing (as I dont need to create the material from the scatch), but would prefer correct solution.

Do not (event attempt to) serialize entire UnityEngine.Object instances!

Well, you asked for the “correct solution”. :wink:

Why do you need to serialize this, could you explain? It may be far more trivial than you think, unless you are creating a runtime material editor and even then you’d probably get away with something way easier than serializing “foreign” objects that contain far more data than the data that you actually need to serialize (aka serialize only the differences).

Let’s say the user is allowed to change the player material’s color. If so, you only serialize the color. Upon deserialization, you take the base material that the object has already assigned to it, and override its color from the serialized data. That’s it!

You can extend this to almost every writable property of a UnityEngine.Object.

3 Likes

No, you can’t just Serialize/Deserialize automatically a complex object like a Material using any serialization library except of Unity’s one (which knows how to do it). What you should do is to save in the JSON file any relevant information you need to retrieve the Material, not recreate it. Most of the time, the material name (and color) would be enough.

2 Likes

Thanks guys, I thought so.

I am using Scriptable object (SO) as template for a NPC, then in when spawning, I copy the SO data into NPCData (just to get rid of SO) and add/modify additional variables. Material is part of the NPC as it is 2D game and the NPC is rendered on a quad. Upon quit, I am saving all spawned NPC with all their data.
It is prototype, so I’ve started wih serializing of all data which for JsonUtility was not problem due to serialization only instanceIDs, but as soon as I found out it does not work with Inheritance I’ve switched to Newtonsoft Json.NET.

The basic idea of the prototype was to save all data (meaning no manual picking specific info to save), but that is a bad practice anyway, so I will save only data I need and recreate NPCData on data loading.

InstanceID’s aren’t persistent between build game sessions anyway (nor editor sessions), so they aren’t a stable way to have save games. Direct references to Unity object can’t be serialised/deserialised out, just like we can’t serialise/deserialise entire Unity object.

A general approach is to try and represent your game-state (or portions of it) as pure C# data, so it can be written to disk without any conversion. Otherwise you can also crunch down the Unity runtime into a serialisable form and write that to disk. One requires more up-front work, the other more work after the fact.

2 Likes