Bug: Deserializing a Scriptable Object that contains a sprite reference throws error
HunterAhlquist opened this issue ยท 1 comments
Expected behavior
Serialized ScriptableObject with Sprite reference gets initialized through its converter.
Actual behavior
Throws a NullReferenceException:
Call Stack
NullReferenceException
UnityEngine.Sprite.get_bounds () (at :0)
(wrapper dynamic-method) System.Object.lambda_method(System.Runtime.CompilerServices.Closure,object)
Newtonsoft.Json.Serialization.ExpressionValueProvider.GetValue (System.Object target) (at :0)
Rethrow as JsonSerializationException: Error getting value from 'bounds' on 'UnityEngine.Sprite'.
Newtonsoft.Json.Serialization.ExpressionValueProvider.GetValue (System.Object target) (at :0)
Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CalculatePropertyDetails (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, System.Boolean& useExistingValue, System.Object& currentValue, Newtonsoft.Json.Serialization.JsonContract& propertyContract, System.Boolean& gottenCurrentValue, System.Boolean& ignoredValue) (at :0)
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) (at :0)
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) (at :0)
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) (at :0)
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) (at :0)
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) (at :0)
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) (at :0)
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) (at :0)
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) (at :0)
Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize (Newtonsoft.Json.JsonReader reader, System.Type objectType, System.Boolean checkAdditionalContent) (at :0)
Newtonsoft.Json.JsonSerializer.DeserializeInternal (Newtonsoft.Json.JsonReader reader, System.Type objectType) (at :0)
Newtonsoft.Json.JsonSerializer.Deserialize (Newtonsoft.Json.JsonReader reader, System.Type objectType) (at :0)
Newtonsoft.Json.JsonConvert.DeserializeObject (System.String value, System.Type type, Newtonsoft.Json.JsonSerializerSettings settings) (at :0)
Newtonsoft.Json.JsonConvert.DeserializeObject[T] (System.String value, Newtonsoft.Json.JsonSerializerSettings settings) (at :0)
Newtonsoft.Json.JsonConvert.DeserializeObject[T] (System.String value) (at :0)
SOTester.Start () (at Assets/SOTester.cs:15)
Steps to reproduce
- New project
- Import
jillejr.newtonsoft.json-for-unity
via UPM - Import
jillejr.newtonsoft.json-for-unity.converters
via UPM - Add following script to an object in a scene:
public class SOTester : MonoBehaviour {
public TestSO obj;
void Start() {
string serialized = JsonConvert.SerializeObject(obj);
obj = null;
obj = JsonConvert.DeserializeObject<TestSO>(serialized);
}
}
- Create the following ScriptableObject type:
[CreateAssetMenu(fileName = "New SO", menuName = "Test Scriptable Object")]
public class TestSO : ScriptableObject {
public string name;
public Sprite icon;
}
- Create an instance of TestSO and populate with valid data
- Play the scene.
- Object will serialize, but will not deserialize due to the Sprite reference in the type.
Details
Host machine OS running Unity Editor ๐ Windows
Unity build target ๐ Windows, Mac, Linux
Newtonsoft.Json-for-Unity package version ๐ 10.0.302
Newtonsoft.Json-for-Unity.Converters package version ๐ 1.4.0
I was using Unity version ๐ 2020.3.26f1
Checklist
- Shutdown Unity, deleted the /Library folder, opened project again in Unity, and problem still remains.
- Checked to be using latest version of the package.
Hello @HunterAhlquist, thanks for reporting this!
Sad to say that there's some technical issues with this, which is why this package don't support Sprites at the moment.
Some of the fields are no biggie, such as:
Sprite.bounds
Sprite.rect
Sprite.border
Sprite.pixelsPerUnit
Sprite.spriteAtlasTextureScale
Sprite.pivot
Sprite.packed
- etc
However, some other fields are less straight forward. Such as:
Sprite.texture
Sprite.associatedAlphaSplitTexture
Sprite.m_CachedPtr
(inherited fromUnityEngine.Object
)Sprite.m_InstanceID
(inherited fromUnityEngine.Object
)
Especially the latter two makes it impossible to simply just create new objects on the fly when deserializing. Instead, I have to call Sprite.Create
or something similar, which allocates a lot, does not reference your internal assets, and will duplicate the sprites if they are referenced multiple times in the project.
Depending on your use case, I suggest to either:
-
Use AssetReference from the Addressables package, which is supported since v1.4.0 of Newtonsoft.Json-for-Unity.Converters.
This would allow you to make sure to reuse the same exact asset without excessive allocations and texture duplications. The JSON would then only include the GUID of the asset.
-
Use a custom type for Sprite, that includes all the fields you want to transfer via JSON. Ex:
public class SpriteData { public Bounds bounds; public Rect rect; public Vector4 border; public float pixelsPerUnit; public float spriteAtlasTextureScale; public Vector2 pivot; public bool packed; // add other fields you care about }
Use this if your use case orients around the metadata for a sprite. What you'd do is to
new SpriteData()
and then assign all fields based on a UnitySprite
asset. If you want to apply the state back to a UnitySprite
asset then you do the reverse and assign the fields from theSpriteData
on to the UnitySprite
assets.Then to get your ScriptableObject to not error because of the Sprite reference, you can add the
[JsonIgnore]
attribute to that field. -
Write a custom JSON converter yourself. If you don't care about the asset reference loss, sprite duplications overhead, or other things like that, then you can create a SpriteConverter that suits your use case precisely.