ISerializationCallbackReceiver not working for structs?

I’m trying to create a simple struct that wraps a string and a cached hash code value for when I need to use a string for dictionary lookups a lot. But I’m having problems using ISerializationCallbackReceiver.

states that

“The interface can be used on MonoBehaviours like in the example, but it can also be used on custom classes and structs”

a simple example of

HashedString test1 = new HashedString("Text1");
string test2 = JsonUtility.ToJson(test1);
HashedString test3 = JsonUtility.FromJson<HashedString>(test2);

Does not work for me if HashedString is a struct but changing it into a class makes this run as intended.
Is the documentation false? am I using ISerializationCallbackReceiver in a wrong way? or is this a bug on newer releases as I just got the new Unity 5.4.1 today.

I would very much like to use a struct for this

Here is my HashedString code:

//This define option forces HashedString class to only consider hashes when comparing for equality
//#define HASHED_STRING_FORCE_HASH_EQUALS

using System;
using System.Runtime.Serialization;
using UnityEngine;

namespace Common.Text
{
    /// <summary>
    /// Class to wrap a string with its cached hash value,
    /// as default string implementation calculates it on demand.
    /// This is useful for cases where string hashes are often requested.
    /// </summary>
    /// <seealso cref="System.IEquatable{Common.Text.HashedString}" />
    /// <seealso cref="System.IComparable{Common.Text.HashedString}" />
    /// <seealso cref="System.IComparable" />
    [Serializable]
    public struct HashedString : IEquatable<HashedString>, IComparable<HashedString>, IComparable, ISerializationCallbackReceiver
    {
        /// <summary>
        /// The string text.
        /// </summary>
        [SerializeField]
        private string m_Text;

        /// <summary>
        /// The cached string hash value.
        /// </summary>
        [NonSerialized]
        private int m_Hash;

        /// <summary>
        /// The string text.
        /// </summary>
        public string Text { get { return m_Text; } }

        /// <summary>
        /// Initializes a new instance of the <see cref="HashedString"/> struct.
        /// </summary>
        /// <param name="i_Text">The string text.</param>
        public HashedString(string i_Text)
        {
            m_Text = i_Text;
            m_Hash = i_Text.GetHashCode();
        }

        /// <summary>
        /// Performs an implicit conversion from <see cref="HashedString"/> to <see cref="System.String"/>.
        /// </summary>
        /// <param name="i_Obj">The object.</param>
        /// <returns>
        /// The text string of <see cref="HashedString"/>.
        /// </returns>
        public static implicit operator string(HashedString i_Obj)
        {
            return i_Obj.m_Text;
        }

        [OnDeserialized]
        private void InitValuesOnDeserialized(StreamingContext context)
        {
            m_Hash = m_Text.GetHashCode();
        }

        /// <summary>
        /// Performs an implicit conversion from <see cref="System.String"/> to <see cref="HashedString"/>.
        /// </summary>
        /// <param name="i_Text">The <see cref="HashedString"/> text.</param>
        /// <returns>
        /// The <see cref="HashedString"/> representation of the string.
        /// </returns>
        public static implicit operator HashedString(string i_Text)
        {
            return new HashedString(i_Text);
        }

        /// <summary>
        /// Implements the operator ==.
        /// </summary>
        /// <param name="i_ObjA">The object a.</param>
        /// <param name="i_ObjB">The object b.</param>
        /// <returns>
        /// The equality result.
        /// </returns>
        public static bool operator ==(HashedString i_ObjA, HashedString i_ObjB)
        {
#if UNITY_EDITOR
            if ((i_ObjA.m_Text == i_ObjB.m_Text) != (i_ObjA.m_Hash == i_ObjB.m_Hash))
            {
#if HASHED_STRING_FORCE_HASH_EQUALS
                Debug.LogErrorFormat(
#else
                Debug.LogWarningFormat(
#endif
                    "Incorrect IntelligentTextId early match due to a hash clash ({0} == {1})", i_ObjA.m_Text, i_ObjB.m_Text);
            }
#endif

#if HASHED_STRING_FORCE_HASH_EQUALS
            return i_ObjA.Hash == i_ObjB.Hash;
#else
            if (i_ObjA.m_Hash == i_ObjB.m_Hash)
            {
                return i_ObjA.m_Text == i_ObjB.m_Text;
            }
            return false;
#endif
        }

        /// <summary>
        /// Implements the operator !=.
        /// </summary>
        /// <param name="i_ObjA">The object a.</param>
        /// <param name="i_ObjB">The object b.</param>
        /// <returns>
        /// The result of the operator.
        /// </returns>
        public static bool operator !=(HashedString i_ObjA, HashedString i_ObjB)
        {
            return !(i_ObjA == i_ObjB);
        }

        /// <summary>
        /// Returns a hash code for this instance.
        /// </summary>
        /// <returns>
        /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. 
        /// </returns>
        public override int GetHashCode()
        {
            return m_Hash;
        }

        /// <summary>
        /// Determines whether the specified <see cref="System.Object" />, is equal to this instance.
        /// </summary>
        /// <param name="i_Obj">The <see cref="System.Object" /> to compare with this instance.</param>
        /// <returns>
        ///   <c>true</c> if the specified <see cref="System.Object" /> is equal to this instance; otherwise, <c>false</c>.
        /// </returns>
        public override bool Equals(object i_Obj)
        {
            HashedString hashedText = (HashedString)i_Obj;
            return this == hashedText;
        }

        /// <summary>
        /// Determines whether the specified <see cref="Common.Text.HashedString" />, is equal to this instance.
        /// </summary>
        /// <param name="i_Obj">The <see cref="Common.Text.HashedString" /> to compare with this instance.</param>
        /// <returns>
        ///   <c>true</c> if the specified <see cref="Common.Text.HashedString" /> is equal to this instance; otherwise, <c>false</c>.
        /// </returns>
        public bool Equals(HashedString i_Other)
        {
            return this == i_Other;
        }

        /// <summary>
        /// Compares the specified <see cref="System.Object" /> to this instance.
        /// </summary>
        /// <param name="i_Obj">The object.</param>
        /// <returns>
        /// -1 if this instance is less than the given object, 1 if greater and 0 otherwisse.
        /// </returns>
        public int CompareTo(object i_Obj)
        {
            HashedString hashedText = (HashedString)i_Obj;
            return m_Text.CompareTo(hashedText.m_Text);
        }

        /// <summary>
        /// Compares the specified <see cref="Common.Text.HashedString" /> to this instance.
        /// </summary>
        /// <param name="i_Other">The object.</param>
        /// <returns>
        /// -1 if this instance is less than the given object, 1 if greater and 0 otherwisse.
        /// </returns>
        public int CompareTo(HashedString i_Other)
        {
            return m_Text.CompareTo(i_Other.m_Text);
        }

        public void OnBeforeSerialize()
        { }

        public void OnAfterDeserialize()
        {
            m_Hash = m_Text.GetHashCode();
        }
    }
}

The documentation seems to be contradictory. The same documentation page reads:

The interface can be used on
MonoBehaviours like in the example,
but it can also be used on custom
classes and structs.

And:

Currently only classes will receive
the callbacks. Structs will not.

This has been patched on the Unity beta docs, and reflects what you are experiencing. At least for now, it does not work on structs.