Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug: Deserializing a Scriptable Object that contains a sprite reference throws error #69

Open
2 tasks done
HunterAhlquist opened this issue Feb 13, 2022 · 1 comment
Open
2 tasks done
Labels
bug Something isn't working

Comments

@HunterAhlquist
Copy link

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.
@HunterAhlquist HunterAhlquist added the bug Something isn't working label Feb 13, 2022
@applejag
Copy link
Owner

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 from UnityEngine.Object)
  • Sprite.m_InstanceID (inherited from UnityEngine.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:

  1. 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.

  2. 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 Unity Sprite asset. If you want to apply the state back to a Unity Sprite asset then you do the reverse and assign the fields from the SpriteData on to the Unity Sprite assets.

    Then to get your ScriptableObject to not error because of the Sprite reference, you can add the [JsonIgnore] attribute to that field.

  3. 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants