On Odins Serialization Procotol

When serializing with Odin in MonoBehaviours, ScriptableObjects, etc, the first thing to understand is that Odin does not replace the Unity serializer, it extends it.

Odin achieves this by converting non-Unity serialized fields and properties into a data format that Unity can serialize; most often a byte array and a list of Unity objects. Odin stores this data in a SerializationData field. Unity's serializer will then kick in and serialize the Odin serialized data with its own serializer, and write it to a file somewhere.

This implementation gives a lot of advantages. It utilizes all of Unity's existing systems and thereby doesn't change the usual Unity workflow. It lets Unity handle the data and, more importantly, it lets Unity handle the serialization of its own object references. This allows for referencing Unity object pretty much wherever, and these references will still work in the player.

This implementation does also open up for the possibility for fields that are serialized twice; once by Unity and once by Odin. This is obviously less than ideal because it would only increase the byte size of the serialized data, without actually adding anything, and could also cause subtle issues if Odin and Unity ever start disagreeing on how things should be done. For this reason, Odin will try and intelligently filter out fields that should already be serialized by Unity.

This example demonstrates the basic idea. You can use the Serialization Debugger to verify this example and debug your own scripts as well.

public class MyScriptableObject : SerializedScriptableObject
{
	// Unity is perfectly capable of serializing primitives. This field is therefore skipped by Odin.
	public float PublicFloat;

	// This field will be skipped by both Unity and Odin.
	private float privateFloat;
	
	// OdinSerialize forces Odin to serialize the field. However, this field is also serialized
	// by Unity at the same time. This results in doubled data.
	[OdinSerialize]
	public float OdinAndUnityFloat;

	// In Odin the NonSerialized attribute is overruled by the OdinSerialize attribute.
	// Unity will not serialize this field, but Odin will.
	[NonSerialized, OdinSerialize]
	public float OdinOnlyFloat;
}

That, in and of itself, should be simple enough to understand. But it is also important to understand that the serializer is only selected at the root level; Any class members of a class or struct will be serialized by the serializer that serializes the "root" field that ultimately contains that value.

public class MyScriptableObject : SerializedScriptableObject
{
	// Since the MySerializedObject class has the Serializable attribute defined, then Unity
	// will serialize this field.
	// However, Unity will not serialize the Dictionary field, and therefore the Dictionary
	// field is not serialized.
	public MySerializedObject UnitySerialized;

	// Just like the other example above, Odin is forced to serialize this field.
	// The Dictionary will be saved.
	[NonSerialized, OdinSerialize]
	public MySerializedObject OdinSerialized;
}

// Another way to force Odin to serialize this class would be to remove the Serializable
// attribute from the MySerializedObject definition.
// This would work because Unity will only serialize types with the Serializable attribute.
[Serializable]
public class MySerializedObject
{
	// Using something like [NonSerialized, OdinSerialize] will not make a difference here,
	// because the serialization of this type depends upon the selected root-level serializer.
	public Dictionary<int, string> MyDictionary;
}

A good rule of thumb: If a property or field is not shown in the inspector (without using something like the ShowInInspector attribute), then that member is not being serialized.

When creating these classes it is worth considering whether or not you actually require the Odin Serializer. The Odin Serializer is very fast, but since it extends the Unity serializer, then it can only add to the serialization and deserialization time. On top of this, the Unity serializer, with its purposefully simple design and native implementation, is very, very fast - far faster than Odin. So if you can get away with just the Unity serializer, then that would definitely be the recommended way to go.