JsonUtility.FromJson is calling default constructor although doc says it isn't

Hello dear community,

I found a weird bug in my code and tried to trace it down. It turned out that the FromJson function is calling the default contructor although the documentation says “the constructor for the object is not executed during deserialization”. (Unity - Scripting API: JsonUtility.FromJson)

I have a DataManager for saving/loading data with this function:

    public static InternalDataContainer LoadInternalData()
    {
        InternalDataContainer internalDataContainer = gameStateFileDataHandler.Load<InternalDataContainer>(internalDataFileName);

        internalDataContainer ??= new InternalDataContainer();
        return internalDataContainer;
    }

The load function is simply doing the following:

    public override T Load<T>(string key)
    {
        string data = readFile(key);

        if (data == "")
            return default(T);

        T loadedObj = JsonUtility.FromJson<T>(data);
        return loadedObj;
    }

And the InternalDataContainer class is defined as follows:

[Serializable]
public class InternalDataContainer
{
    /// Lots of fields...

    [field:SerializeField]
    public SerializableDateTime TimeStamp { get; set; }

    public InternalDataContainer()
    {
        // Assignment of default values for all those fields
        TimeStamp = new SerializableDateTime(DateTime.Now);
    }
}

Now, it all worked, except loading the TimeStamp from the .json file. Although the time was correct in the file, after loading the .json file, the TimeStamp was always DateTime.Now.
I found the Bug, it was inside the class SerializableDateTime:

[Serializable]
public class SerializableDateTime
{
    [SerializeField]
    private long ticks;

    private bool initialized;
    private DateTime dateTime;

    public DateTime DateTime
    {
        get
        {
            if (!initialized)
            {
                dateTime = new DateTime(ticks);
                initialized = true;
            }
            return dateTime;
        }
    }

    public SerializableDateTime(DateTime dateTime)
    {
        ticks = dateTime.Ticks;
// Here is the Bug: I had to remove those two lines (or at least the last one)
        //this.dateTime = dateTime;
        //initialized = true;
    }
}

The reason was: When having the default assignment (with DateTime.Now) in the class constructor, the SerializableDateTime constructor was called before JsonUtility.FromJson overrides the members. (i.e. before OnAfterDeserialize is called)
=> Then the datetime is assigned a wrong value and it will never be updated to the afterwards correctly deserialized ticks, because initialized is already set to true.

It took me a couple of hours to trace it down and I am just very confused why the default constructor is executed when calling FromJson because the documentation explicitly states that is is not called.
Am I doing a mistake here or is this info in the docs simply wrong?

Greetings :slight_smile:

Did you put a Debug.Log in it to confirm it was executing? I don’t see how Unity could invoke a constructor with parameters when it has no concept of what to supply it, unless its supplies a default value. A constructor with parameters is not a default constructor in any case.

What if you give it a parameterless constructor?

1 Like

I think you misunderstood, the public InternalDataContainer() constructor is called (and inside there is the call to initialize the time). When I changed it to a require a parameter (eg an arbitrary number) then it was not called anymore but that should not be the way to go :smile:

Right I see what you mean.

The docs are definitely wrong then. Far as I’ve known, Unity has always invoked default constructors when deserialising plain C# objects. I’ve submitted a problem on the docs; worth doing the same on your end too.

Ok thank you for clarifying. I’ve submitted a problem too now :slight_smile:

1 Like

That sounds more like a bad concept / implementation of your class. The parameterless constructor is always supposed to be invoked. Yes, some serialization systems do not invoke the constructor, especially since it can not be guaranteed that one exists. Technically the .NET framework allows the allocation of a new object without initialization (through FormatterServices.GetUninitializedObject). However the parameterless constructor shouldn’t really cause the object to be in an invalid state.

Since DateTime is a struct, you may want to just wrap the DateTime in a serializable struct as well. Also keep in mind that Unity has the ISerializationCallbackReceiver interface to provide custom conversion methods before serialization and after deserialization. This should be the prefered way to implement wrappers and the proper initialization of the wrapper. That “initialized” bool is generally a fragile solution and not really necessary. Your struct wouldn’t really need a DateTime field as you could just create a property that does the ticks conversion on the fly. Keep in mind that DateTime is just a struct and therefore copied anyways. The DateTime struct is just a wrapper for a 64 bit ticks variable anyways. Inside that int64 value they included a few bits for the “kind”.

An easy way to get access to such an internal field without much hassle would be to use a struct with overlapping fields with the FieldOffset attribute. That way you can simply have two fields that occupy the same memory space.

I haven’t really tested this in Unity, but this should work I gues:

[System.Serializable, StructLayout(LayoutKind.Explicit)]
public struct SerializableDateTime
{
    [System.NonSerialized, FieldOffset(0)]
    public System.DateTime dateTime;
   
    [SerializeField, FieldOffset(0)]
    public long ticks;
   
    public SerializableDateTime(System.DateTime aDateTime)
    {
        ticks = 0L;
        dateTime = aDateTime;
    }
}

No need for any custom conversion code. The compiler forces me to initialize both fields, even though the last assignment will overwrite the first one since they share the same space. However the compiler doesn’t realise that. Also overlapping fields do not necessarily fully overlap. So the compiler wants to make sure that all the memory of the struct is initialized.

Here’s a fiddle with a small test / example. So the trick here is that the “ticks” field and the “dateTime” field simply share the same memory. So changing the dateTime field would automatically “change” the ticks field since it’s literally the same thing. Since Unity can serialize a long value, the value is serialized / deserialized out of the box.

Some time ago I made a FloatHelper struct to get access to the bits of a float and double values by simply augmenting a double, float and long value in a single struct. So that editor window actually stores just a single long value but displays the content as a double, float and integer at the same time.

2 Likes