Version: Unity 6.6 Alpha (6000.6)
Language : English
Serialization rules
Custom serialization

Serialization rules analyzer reference

Unity has a serialization Roslyn diagnostic analyzer that helps you write correct serialization code by detecting common mistakes in using Unity’s serialization system at compile time. It provides real-time feedback in your IDE to prevent serialization issues before they cause runtime problems.

For detailed information about Unity’s serialization rules, refer to Serialization rules. For a summary of common serialization issues and how to avoid them, refer to Avoid common serialization issues.

Analyzer rules

The serialization analyzer implements the following diagnostic rules:

For information about how analyzer rules apply to generic type arguments, refer to the Generics section. For information about suppressing rule warnings, refer to Suppressing warnings.

UAC1000: SerializeReference field type missing the Serializable attribute

Severity: Warning.

When a field is serialized by [SerializeReference], its type should have the [Serializable] attribute. If the type is missing [Serializable], Unity issues a warning. The field is serialized via [SerializeReference] even though the type isn’t considered serializable in other serialization contexts, which can lead to inconsistent behavior. To avoid the issue, add [Serializable] to the class or use the [MakeSerializable] attribute.

Marking the type with [Serializable] (or [MakeSerializable] when you can’t modify the type) makes the intent explicit and keeps behavior consistent across Unity’s serialization paths. Without it, a field with [SerializeReference] still persists the data, while other forms of serialization that rely on the type being serializable can skip it, leading to inconsistent results.

Incorrect example: Missing [Serializable] on a class used with [SerializeReference]:

// INCORRECT: Missing [Serializable] on CustomData
public class CustomData
{
    public int value;
}

public class MyBehaviour : MonoBehaviour
{
    [SerializeReference] public CustomData data; // UAC1000: CustomData lacks [Serializable]
}

Correct example: Add [Serializable] to the class:

// CORRECT: Add [Serializable] to the class
[System.Serializable]
public class CustomData
{
    public int value;
}

public class MyBehaviour : MonoBehaviour
{
    [SerializeReference] public CustomData data; // OK
}

UAC1001: Public field skipped by serialization due to missing the Serializable attribute

Severity: Warning.

Unity considers public fields of custom classes as candidates for serialization. Without the [Serializable] attribute on a type, Unity does not serialize fields of this type, and you can lose data when entering Play mode or when the sceneA Scene contains the environments and menus of your game. Think of each unique Scene file as a unique level. In each Scene, you place your environments, obstacles, and decorations, essentially designing and building your game in pieces. More info
See in Glossary
is saved.

To fix this issue, you have multiple options depending on your intent:

  • Add [Serializable] to the field’s type (if serialization is needed).
  • Make the field private (if serialization isn’t needed).
  • Add [System.NonSerialized] to the field (to explicitly mark it as non-serialized).
  • Convert the field into a property (Unity doesn’t serialize properties), if you only need a public API surface.

Without the [Serializable] attribute, Unity skips the field during serialization, causing data loss. This is problematic because the field compiles successfully but loses its value during serialization and deserialization cycles.

Incorrect example: Missing [Serializable] on a class used as a serialized field:

// INCORRECT: Missing [Serializable] on PlayerStats
public class PlayerStats
{
    public int health;
    public int mana;
}

public class Player : MonoBehaviour
{
    public PlayerStats stats; // UAC1001: PlayerStats lacks [Serializable]
}

Correct example: Add [Serializable] to the class:

// CORRECT: Add [Serializable] to the class
[System.Serializable]
public class PlayerStats
{
    public int health;
    public int mana;
}

public class Player : MonoBehaviour
{
    public PlayerStats stats; // OK - will be serialized inline
}

UAC1002: Inheritance hierarchy incomplete serialization

Severity: Warning.

Every class in a serialization hierarchy must be explicitly marked with [Serializable], because the attribute isn’t inherited from a base class to its derived classes. If any class in the hierarchy is missing [Serializable], Unity might not serialize the fields declared on that class, which can result in unexpected data loss when entering Play mode, saving scenes, or performing domain reloads.

If you can’t modify a base class (for example, it comes from a third-party library or a precompiled assembly), use the [MakeSerializable] assembly-level attribute to mark it as serializable without changing its source code. Fixing this warning also enables the analyzer to check the base class’s own fields. Refer to Limitation: base class field analysis for more details.

Incorrect example: Inheritance hierarchy missing [Serializable] on a base class:

// INCORRECT: Missing [Serializable] on base class
public class BaseEntity
{
    public string name;
}

[System.Serializable]
public class Enemy : BaseEntity  // UAC1002: Enemy's hierarchy incomplete (BaseEntity lacks [Serializable])
{
    public int damage;
}

Correct example: Mark each class in the hierarchy with [Serializable]:

// CORRECT: Mark each class in the hierarchy with [Serializable]
[System.Serializable]
public class BaseEntity
{
    public string name;
}

[System.Serializable]
public class Enemy : BaseEntity
{
    public int damage;
}

public class GameManager : MonoBehaviour
{
    public Enemy enemy; // OK - entire hierarchy is serializable
}

Alternative correct example: Use [MakeSerializable] for inaccessible base classes:

If you can’t modify the base class (for example, it comes from a precompiled assembly or third-party library), you can use the [MakeSerializable] assembly attribute:

// CORRECT: Use [MakeSerializable] for inaccessible base classes
[assembly: UnityEngine.Serialization.MakeSerializable(typeof(ThirdPartyLibrary.BaseEntity))]

namespace ThirdPartyLibrary
{
    // This class is from a precompiled assembly - you can't modify it
    public class BaseEntity
    {
        public string name;
    }
}

namespace YourGame
{
    [System.Serializable]
    public class Enemy : ThirdPartyLibrary.BaseEntity
    {
        public int damage;
    }

    public class GameManager : MonoBehaviour
    {
        public Enemy enemy; // OK - BaseEntity marked serializable via [MakeSerializable]
    }
}

UAC1003: [SerializeReference] used on struct

Severity: Error.

The [SerializeReference] attribute can only be used on reference types (classes). It enables reference semantics such as null support, polymorphism, and shared references, which are incompatible with value types. Structs are value types and are always serialized inline, so using [SerializeReference] on a struct causes a runtime error.

Incorrect example: Using [SerializeReference] on a struct:

// INCORRECT: Using [SerializeReference] on a struct
[System.Serializable]
public struct Vector3D
{
    public float x, y, z;
}

public class MyBehaviour : MonoBehaviour
{
    [SerializeReference] public Vector3D position; // UAC1003: Error - can't use [SerializeReference] on struct
}

Correct example: Remove [SerializeReference] from the struct field:

// CORRECT: Remove [SerializeReference] from the struct field
[System.Serializable]
public struct Vector3D
{
    public float x, y, z;
}

public class MyBehaviour : MonoBehaviour
{
    public Vector3D position; // OK - structs are always serialized inline
}

Alternative correct example: Use a class:

// CORRECT: Use a class instead of a struct
[System.Serializable]
public class Vector3D
{
    public float x, y, z;
}

public class MyBehaviour : MonoBehaviour
{
    [SerializeReference] public Vector3D position; // OK - class can use [SerializeReference]
}

UAC1004: [SerializeReference] used on primitive or enum type

Severity: Warning.

Built-in types (int, float, bool, etc.) and enums are value types, so they are always serialized inline. Unity silently ignores [SerializeReference] on these types instead of producing a runtime error. The behavior is different for structs, where Unity raises errors which are caught by UAC1003). Removing the unnecessary [SerializeReference] makes the serialization intent of the field clearer.

Incorrect example: Using [SerializeReference] on a primitive or enum:

// INCORRECT: Using [SerializeReference] on a primitive and an enum
public enum GameState
{
    Menu,
    Playing,
    Paused
}

public class GameController : MonoBehaviour
{
    [SerializeReference] public int score; // UAC1004: int is a primitive type
    [SerializeReference] public GameState state; // UAC1004: GameState is an enum
}

Correct example: Remove [SerializeReference] from primitive and enum fields:

// CORRECT: Remove [SerializeReference] from primitive and enum fields
public enum GameState
{
    Menu,
    Playing,
    Paused
}

public class GameController : MonoBehaviour
{
    public int score; // OK - primitives don't need [SerializeReference]
    public GameState state; // OK - enums don't need [SerializeReference]
}

UAC1005: Direct self-reference cycle in serialized class

Severity: Warning.

When a class contains a field of its own type, it creates a direct self-reference cycle. Unity’s inline serialization copies object graphs by value, which can’t represent cycles. To avoid infinite loops, Unity ignores any field of a type that contains a direct self-reference cycle. This causes data loss that can be difficult to debug, because the field compiles without errors but is always null after deserialization. UAC1006 complements this rule by reporting the same problem at the field’s usage location rather than the type declaration.

There are two common, valid ways to represent self-reference data:

  • Use [SerializeReference] on the link field (supports cycles and reference semantics).
  • Use a collection (for example, List<Node> or Node[]) to represent children or links. Collections can be empty and the object graph can remain cycle-free, but cycles (including adding this) still can’t be represented correctly without the [SerializeReference] attribute.

Incorrect example: Direct self-reference cycle in an inline-serialized class:

// INCORRECT: Direct self-reference cycle
[System.Serializable]
public class Node
{
    public string data;
    public Node next; // UAC1005: Direct self-reference - Unity will ignore this field
}

public class LinkedList : MonoBehaviour
{
    public Node head; // After serialization, head will always be null
}

Correct example: Use [SerializeReference] for self-referential links:

// CORRECT: Use [SerializeReference] for self-referential links
[System.Serializable]
public class Node
{
    public string data;
    [SerializeReference] public Node next; // OK - [SerializeReference] supports cycles
}

public class LinkedList : MonoBehaviour
{
    [SerializeReference] public Node head; // OK - preserves linked structure
}

Alternative correct example: Redesign to avoid cycles:

// CORRECT: Redesign to avoid cycle
[System.Serializable]
public class NodeData
{
    public string data;
    public int nextIndex; // Store index instead of direct reference
}

public class LinkedList : MonoBehaviour
{
    public NodeData[] nodes; // OK - no direct cycles
}

UAC1006: Field uses type with direct self-reference cycle

Severity: Warning.

When you use a type that contains a direct self-reference cycle, Unity ignores that field during serialization. This warning appears at the usage location so you can notice data loss even when the problematic type is declared elsewhere. Refer to UAC1005 for details on what causes a direct self-reference cycle and how to fix it.

Incorrect example: Field uses a type with a direct self-reference cycle:

// INCORRECT: Field uses type with direct self-reference cycle
[System.Serializable]
public class TreeNode
{
    public string value;
    public TreeNode left; // Direct self-reference
    public TreeNode right; // Direct self-reference
}

public class Tree : MonoBehaviour
{
    public TreeNode root; // UAC1006: TreeNode contains self-reference cycles
    // After serialization, the root field will be ignored and remain null
}

Correct example: Use [SerializeReference] for cyclic references:

// CORRECT: Use [SerializeReference] for cyclic references
[System.Serializable]
public class TreeNode
{
    public string value;
    [SerializeReference] public TreeNode left; // Fixed with [SerializeReference]
    [SerializeReference] public TreeNode right; // Fixed with [SerializeReference]
}

public class Tree : MonoBehaviour
{
    [SerializeReference] public TreeNode root; // OK - preserves tree structure
}

UAC1007: Deep self-reference cycle in serialized class

Severity: Warning.

When a class contains fields that create an indirect cycle (A > B > A or A > B > C > A), Unity still serializes the structure, but limits recursion to 10 levels. This is different from direct cycles, which are ignored entirely. After 10 levels Unity logs a warning and discards the remaining data, which can cause data loss in deeply nested trees or graphs. This warning is reported on the field that participates in the cycle. UAC1008 reports the same problem at the usage location.

Incorrect example: Deep self-reference cycle with inline serialization:

// INCORRECT: Deep self-reference cycle with inline serialization
[System.Serializable]
public class Parent
{
    public string name;
    public Child child; // UAC1007: Creates deep cycle (Parent → Child → Parent)
}

[System.Serializable]
public class Child
{
    public string name;
    public Parent parent; // Creates the cycle back to Parent
}

public class Family : MonoBehaviour
{
    public Parent root; // Will serialize up to 10 levels: Parent→Child→Parent→Child...
}

Correct example: Use [SerializeReference] for deep cycles:

// CORRECT: Use [SerializeReference] for deep cycles
[System.Serializable]
public class Parent
{
    public string name;
    [SerializeReference] public Child child; // OK - [SerializeReference] handles cycles
}

[System.Serializable]
public class Child
{
    public string name;
    [SerializeReference] public Parent parent; // OK - preserves bidirectional relationship
}

public class Family : MonoBehaviour
{
    [SerializeReference] public Parent root; // OK - no depth limit
}

Alternative correct example: Redesign to avoid cycles:

// CORRECT: Redesign to avoid cycles
[System.Serializable]
public class Parent
{
    public string name;
    public List<string> childNames; // Store references differently
}

[System.Serializable]
public class Child
{
    public string name;
    public string parentName; // Use name lookup instead of direct reference
}

public class Family : MonoBehaviour
{
    public List<Parent> parents; // OK - no cycles
    public List<Child> children; // OK - no cycles
}

UAC1008: Field uses type with deep self-reference cycle

Severity: Warning.

When you use a type that contains a deep self-reference cycle, Unity serializes it with a 10-level recursion limit, which can cause silent data loss in deeply nested instances. This warning appears at the usage location so you can catch the issue even when the problematic type is declared elsewhere. Refer to UAC1007 for details on what causes a deep cycle and how to fix it.

Incorrect example: Field uses a type with a deep self-reference cycle:

// INCORRECT: Field uses type with deep self-reference cycle
[System.Serializable]
public class A
{
    public B b; // Creates deep cycle: A -> B -> A
}

[System.Serializable]
public class B
{
    public A a; // Completes the cycle
}

public class Host : MonoBehaviour
{
    public A root; // UAC1008: A contains a deep self-reference cycle
}

Correct example: Use [SerializeReference] to avoid depth limits:

// CORRECT: Use [SerializeReference] to avoid depth limits
[System.Serializable]
public class GraphNode
{
    public string id;
    [SerializeReference] public List<GraphNode> connections; // OK - no depth limit
}

public class Graph : MonoBehaviour
{
    [SerializeReference] public List<GraphNode> nodes; // OK - handles arbitrary depth
}

Alternative correct example: Use IDs:

// CORRECT: Use IDs instead of direct references
[System.Serializable]
public class GraphNode
{
    public string id;
    public List<string> connectionIds; // Store IDs instead of references
}

public class Graph : MonoBehaviour
{
    public List<GraphNode> nodes; // OK - no cycles, resolve IDs at runtime
    
    public GraphNode FindNode(string id)
    {
        return nodes.Find(n => n.id == id);
    }
}

UAC1009: Unsupported collection type for serialization

Severity: Warning.

Unity’s serialization system only supports two collection types: single-dimensional arrays (T[]) and List<T>. Fields that use unsupported collection types aren’t serialized, which can cause data loss because the field compiles and works at runtime but loses its value after a serialization cycle (for example, when saving a scene or reloading the domain).

Unsupported collection types include:

  • Multidimensional arrays (for example, int[,], int[,,]).
  • Jagged arrays (for example, int[][]).
  • Nested lists (for example, List<List<T>>).
  • HashSet<T>, Queue<T>, Stack<T>, and other collections from System.Collections.Generic.
  • Collection-of-dictionary shapes where the collection element type is a dictionary (for example, List<Dictionary<TKey, TValue>> and Dictionary<TKey, TValue>[]).

Incorrect example: Using unsupported collection types for serialization:

// INCORRECT: Using unsupported collection types
public class InventorySystem : MonoBehaviour
{
    public int[,] grid; // UAC1009: Multidimensional array not supported
    public int[][] jaggedArray; // UAC1009: Jagged array not supported
    public List<Dictionary<string, int>> itemCountsHistory; // UAC1009: Collection element type is Dictionary<,>
    public Dictionary<string, int>[] itemCountsByLevel; // UAC1009: Array element type is Dictionary<,>
    public HashSet<string> unlockedAchievements; // UAC1009: HashSet not supported
    public Queue<string> messageQueue; // UAC1009: Queue not supported
    public Stack<string> undoStack; // UAC1009: Stack not supported
}

Correct example: Mark runtime-only collections as [NonSerialized]:

If you don’t need these fields to persist (for example, they are runtime-only data structures), you can either make them non-serialized (for example by making the field non-public) or mark them with [System.NonSerialized] if they must remain a public field. This acknowledges they won’t be serialized and suppresses the warning:

// CORRECT: Mark runtime-only collections as [NonSerialized]
public class InventorySystem : MonoBehaviour
{
    [System.NonSerialized] public List<Dictionary<string, int>> itemCountsHistory; // OK - explicitly marked as non-serialized
    [System.NonSerialized] public HashSet<string> unlockedAchievements; // OK - runtime-only data
    [System.NonSerialized] public Queue<string> messageQueue; // OK - transient queue
    [System.NonSerialized] public Stack<string> undoStack; // OK - runtime undo stack
    
    private void Awake()
    {
        // Initialize runtime collections
        itemCountsHistory = new List<Dictionary<string, int>>();
        unlockedAchievements = new HashSet<string>();
        messageQueue = new Queue<string>();
        undoStack = new Stack<string>();
    }
}

Alternative correct example: Use supported collection types:

If you need the data to persist, convert to supported collection types:

// CORRECT: Use supported collection types
[System.Serializable]
public struct KeyValuePair
{
    public string key;
    public int value;
}

public class InventorySystem : MonoBehaviour
{
    public List<KeyValuePair> itemCounts; // OK - List is supported
    public List<string> unlockedAchievements; // OK - List instead of HashSet
    public List<string> messageQueue; // OK - List instead of Queue
    public List<string> undoStack; // OK - List instead of Stack
    
    // Helper methods to maintain collection semantics
    public int GetItemCount(string itemName)
    {
        var pair = itemCounts.Find(p => p.key == itemName);
        return pair.value;
    }
    
    public void SetItemCount(string itemName, int count)
    {
        int index = itemCounts.FindIndex(p => p.key == itemName);
        if (index >= 0)
            itemCounts[index] = new KeyValuePair { key = itemName, value = count };
        else
            itemCounts.Add(new KeyValuePair { key = itemName, value = count });
    }
}

Alternative correct example: Use a custom serializable wrapper with ISerializationCallbackReceiver:

If you need the full semantics of a HashSet or Dictionary while also keeping the data serialized, you can create a wrapper class that implements ISerializationCallbackReceiver. This allows you to store data in a Unity-supported format (arrays or List<T>) and convert to and from the runtime collection in the serialization callbacks:

// CORRECT: Use a custom serializable wrapper
[System.Serializable]
public class SerializableHashSet<T> : ISerializationCallbackReceiver
{
    [SerializeField] private List<T> items = new List<T>();
    private HashSet<T> hashSet;

    public void OnBeforeSerialize()
    {
        if (hashSet == null) hashSet = new HashSet<T>(items);
        items.Clear();
        items.AddRange(hashSet);
    }

    public void OnAfterDeserialize()
    {
        hashSet = new HashSet<T>(items);
    }

    // Add collection methods (Add, Remove, Contains, Count, etc.) as needed.
}

public class AchievementManager : MonoBehaviour
{
    public SerializableHashSet<string> unlockedAchievements; // OK - custom serializable wrapper
}

The same pattern works for Dictionary<TKey, TValue> using parallel key-value lists. For complete, ready-to-use implementations of SerializableHashSet<T> and SerializableDictionary<TKey, TValue>, refer to Custom serialization.

Note: UAC1009 also applies when unsupported collection types appear as elements inside a supported collection. For example, List<Dictionary<string, int>> and Dictionary<string, int>[] are both flagged because their elements can’t be serialized by Unity.

UAC1010: [SerializeField] used on non-serializable type

Severity: Warning.

Applying [SerializeField] to a field does not make its type serializable. The attribute instructs Unity to save the field, but if the field’s type is not serializable, Unity does not save the value and the field resets after a serialization cycle (for example, when reloading a scene, reloading the domain, or reopening the project).

To fix this warning, choose the option that matches your intent:

  • If serialization is needed, make the field’s type serializable. For example, add [Serializable], or use [MakeSerializable] if you can’t modify the type.
  • If serialization is not needed, remove [SerializeField], or make the field non-serialized in another way.
  • If the field must remain public but should not be serialized, add [System.NonSerialized] to make that explicit.

Incorrect example: Using [SerializeField] on a non-serializable type:

// INCORRECT: Using [SerializeField] on a non-serializable type
public class MyWindowState : BaseState
{
    public List<int> expandedItems;
}

public class MyWindowManager : ScriptableObject
{
    [SerializeField] private MyWindowState m_WindowState; // UAC1010: Unity won't persist this field
}

Correct example: Remove [SerializeField] when persistence isn’t needed:

If the field doesn’t need to be serialized (for example, it’s runtime-only state), remove [SerializeField]:

// CORRECT: Remove [SerializeField] when persistence isn't needed
public class MyWindowState : BaseState
{
    public List<int> expandedItems;
}

public class MyWindowManager : ScriptableObject
{
    private MyWindowState m_WindowState; // OK - not serialized
}

Correct example: Mark the field as [NonSerialized] when persistence isn’t needed:

If the field must remain public but shouldn’t be serialized, add [System.NonSerialized]:

// CORRECT: Mark the field as [NonSerialized]
public class MyWindowState : BaseState
{
    public List<int> expandedItems;
}

public class MyWindowManager : ScriptableObject
{
    [System.NonSerialized] public MyWindowState windowState; // OK - explicitly not serialized
}

Correct example: Make the type [Serializable] when persistence is needed:

If you need the field to be serialized, make the type [Serializable]:

// CORRECT: Make the type [Serializable]
[System.Serializable]
public class MyWindowState : BaseState
{
    public List<int> expandedItems;
}

public class MyWindowManager : ScriptableObject
{
    [SerializeField] private MyWindowState m_WindowState; // OK - type is now serializable
}

UAC1011: Enum type exceeds 32-bit size limit

Severity: Warning.

Unity’s serialization system only supports enum types that are 32 bits or smaller (byte, sbyte, short, ushort, int, and uint). Enums with underlying types of long (64-bit signed) or ulong (64-bit unsigned) exceed this limit and aren’t serialized correctly, which causes data loss after a serialization cycle (for example, when saving a scene or reloading the domain).

Incorrect example: Using a 64-bit enum for serialization:

// INCORRECT: Using a 64-bit enum for serialization
public enum LargeEnum : long
{
    Value1 = 1L,
    Value2 = 2L,
    Value3 = long.MaxValue
}

public class GameData : MonoBehaviour
{
    public LargeEnum largeEnum; // UAC1011: 64-bit enum exceeds Unity's 32-bit limit
}

Correct example: Use a 32-bit-or-smaller enum underlying type:

// CORRECT: Use a 32-bit-or-smaller enum underlying type
public enum SmallEnum : int
{
    Value1 = 1,
    Value2 = 2,
    Value3 = int.MaxValue
}

public class GameData : MonoBehaviour
{
    public SmallEnum smallEnum; // OK - 32-bit enum is supported
}

Correct example: Mark the enum field as non-serialized:

If you don’t need the enum field to persist (for example, it’s runtime-only state), either make it non-public or mark it with [System.NonSerialized] if it must remain a public field:

// CORRECT: Mark the enum field as non-serialized
public enum LargeEnum : long
{
    Value1 = 1L,
    Value2 = 2L
}

public class GameData : MonoBehaviour
{
    [System.NonSerialized] public LargeEnum largeEnum; // OK - explicitly marked as non-serialized
    
    private void Awake()
    {
        // Initialize runtime enum value
        largeEnum = LargeEnum.Value1;
    }
}

UAC1012: Serializable dictionary with interface or abstract key or value type

Severity: Error.

Unity’s serialization system supports Dictionary<TKey, TValue> fields, but only when both TKey and TValue are concrete types. During deserialization Unity must instantiate each key and value, which it can’t do for interface or abstract types because they have no concrete implementation to instantiate. Using such a type for either side of the entry causes runtime errors when the dictionary is loaded.

UnityEngine.Object subclasses are an exception. Even abstract bases such as Renderer or Collider are valid as keys or values because Unity stores them as PPtr instance IDs and resolves the concrete instance during load. The carve-out applies whenever the key or value type derives from UnityEngine.Object, regardless of whether the type itself is abstract.

UAC1012 only fires when the dictionary is actually being serialized — that is, when [SerializeField] is present (see UAC1015). If the field has no [SerializeField] Unity skips serialization entirely, so the abstract / interface concern is moot until the user opts the field in.

One diagnostic is emitted per offending side. When both TKey and TValue are interface or abstract types, the analyzer reports two UAC1012 diagnostics on the same field — one identifying the key type and one identifying the value type — so you can see and fix both problems in a single pass.

Incorrect example: Interface key type:

// INCORRECT: IInventoryItem is an interface, so Unity can't instantiate keys
public interface IInventoryItem { }

[System.Serializable]
public class Sword : IInventoryItem { }

public class Inventory : MonoBehaviour
{
    [SerializeField] public Dictionary<IInventoryItem, int> counts; // UAC1012: interface key type
}

Incorrect example: Abstract value type:

// INCORRECT: AbstractEnemy is abstract, so Unity can't instantiate values
public abstract class AbstractEnemy { }

public class GameState : MonoBehaviour
{
    [SerializeField] public Dictionary<int, AbstractEnemy> enemiesById; // UAC1012: abstract value type
}

Incorrect example: Both key and value are interface or abstract types:

// INCORRECT: both sides are problematic — two UAC1012 diagnostics are reported
public interface IFoo { }
public abstract class AbstractBar { }

public class Host : MonoBehaviour
{
    // UAC1012 (key type 'IFoo') and UAC1012 (value type 'AbstractBar')
    [SerializeField] public Dictionary<IFoo, AbstractBar> data;
}

Correct example: Use a concrete key and value type:

// CORRECT: Concrete, [Serializable] types on both sides
[System.Serializable]
public class InventoryItem
{
    public string id;
}

public class Inventory : MonoBehaviour
{
    [SerializeField] public Dictionary<int, InventoryItem> itemsBySlot; // OK
}

Correct example: Abstract UnityEngine.Object subclass is allowed:

// CORRECT: Renderer is abstract but derives from UnityEngine.Object,
// so it's stored as a PPtr instance ID
public class MaterialOverrides : MonoBehaviour
{
    [SerializeField] public Dictionary<Renderer, Material> overrides; // OK
}

Correct example: Mark as [NonSerialized] when persistence isn’t needed:

// CORRECT: Skip serialization entirely
public class Inventory : MonoBehaviour
{
    [System.NonSerialized] public Dictionary<IInventoryItem, int> runtimeCounts; // OK
}

UAC1013: Serializable dictionary with IEnumerable key type

Severity: Error.

Dictionary keys must produce a stable hash for the lifetime of the entry. Most types that implement IEnumerable (for example, List<T> or arrays) are mutable, so their hash can change after they’re inserted, which corrupts the dictionary. Even when the contents don’t change, Unity’s serialization backend can’t round-trip these keys correctly because it serializes them by value but expects key identity to be preserved across save/load. UAC1013 reports any field whose dictionary key type implements IEnumerable, with two documented exceptions:

  • string. Even though it implements IEnumerable<char>, strings are immutable and Unity treats them as primitive keys.
  • Types that derive from UnityEngine.Object. These are stored as PPtr instance IDs, so the IEnumerable shape of the underlying type is irrelevant.

The check is transitive on composite struct keys. If a struct key contains a field whose type implements IEnumerable, the struct is also rejected, because the key’s hash depends on the unstable inner collection.

UAC1013 only fires when the dictionary is actually being serialized — that is, when [SerializeField] is present (see UAC1015). If the field has no [SerializeField] Unity skips serialization entirely, so the IEnumerable-key concern is moot until the user opts the field in.

Incorrect example: List or array key type:

// INCORRECT: List<T> and arrays are mutable IEnumerable types
public class TagBuckets : MonoBehaviour
{
    [SerializeField] public Dictionary<List<int>, string> byTags; // UAC1013: List<int> key
    [SerializeField] public Dictionary<int[], int> counts; // UAC1013: int[] key
}

Incorrect example: Struct key with an IEnumerable field:

// INCORRECT: Composite struct key contains a List<int> field
[System.Serializable]
public struct CompositeKey
{
    public int id;
    public List<int> tags; // Inner IEnumerable field invalidates the struct as a key
}

public class Buckets : MonoBehaviour
{
    [SerializeField] public Dictionary<CompositeKey, int> counts; // UAC1013: CompositeKey contains an IEnumerable field
}

Correct example: Use a hashable wrapper struct:

// CORRECT: Wrap the key data in a struct whose fields are all hashable primitives
[System.Serializable]
public struct TagSetKey
{
    public int hash; // Pre-computed stable hash of the tag set

    public override int GetHashCode() => hash;
    public override bool Equals(object obj) => obj is TagSetKey other && other.hash == hash;
}

public class TagBuckets : MonoBehaviour
{
    [SerializeField] public Dictionary<TagSetKey, string> byTags; // OK
}

Correct example: Flatten to a primitive key:

// CORRECT: Use a string or int key derived from the original collection
public class TagBuckets : MonoBehaviour
{
    [SerializeField] public Dictionary<string, string> byJoinedTags; // Key is a comma-joined tag string
}

Correct example: Mark as [NonSerialized] when persistence isn’t needed:

// CORRECT: Skip serialization entirely
public class TagBuckets : MonoBehaviour
{
    [System.NonSerialized] public Dictionary<List<int>, string> runtimeByTags; // OK
}

UAC1014: [SerializeReference] used on dictionary

Severity: Error.

The [SerializeReference] attribute enables polymorphic reference semantics for a single object reference, including null support and shared references. Unity’s dictionary backend serializes a Dictionary<TKey, TValue> as an inline array of key-value entries, not as a polymorphic single reference, so [SerializeReference] doesn’t apply to dictionary fields and must not be used on them. This rule mirrors UAC1003, which reports the same misuse on struct fields.

Incorrect example: Using [SerializeReference] on a dictionary field:

// INCORRECT: [SerializeReference] is invalid on Dictionary<,>
public class Inventory : MonoBehaviour
{
    [UnityEngine.SerializeReference] public Dictionary<string, int> counts; // UAC1014
}

Correct example: Replace [SerializeReference] with [SerializeField]:

// CORRECT: [SerializeField] routes the field through Unity's normal dictionary backend
public class Inventory : MonoBehaviour
{
    [SerializeField] public Dictionary<string, int> counts; // OK - serialized inline as entries
}

Correct example: Use [SerializeField] on a private field:

// CORRECT: [SerializeField] is the supported way to serialize a private dictionary field
public class Inventory : MonoBehaviour
{
    [SerializeField] private Dictionary<string, int> m_Counts; // OK
}

Correct example: Mark as [NonSerialized] when persistence isn’t needed:

// CORRECT: Skip serialization entirely
public class Inventory : MonoBehaviour
{
    [System.NonSerialized] public Dictionary<string, int> runtimeCounts; // OK
}

UAC1015: Dictionary field missing the [SerializeField] attribute

Severity: Warning.

For most field types, Unity serializes public fields by default. Dictionary fields are an exception: Unity’s native dictionary serialization backend explicitly checks for the [SerializeField] attribute and skips dictionary fields without it, even when the field is public. This rule warns when a field declared as Dictionary<TKey, TValue> is in a serializable position (not [NonSerialized], no [SerializeReference] already on it) but is missing [SerializeField]. Without the attribute the dictionary will silently fail to persist its contents.

The rule does not fire if [SerializeReference] is present, because UAC1014 already surfaces that misuse — once you replace [SerializeReference] with [SerializeField], both diagnostics are resolved together.

Incorrect example: A public dictionary field without [SerializeField]:

// INCORRECT: Unity's dictionary backend skips this field because it has no [SerializeField]
public class Inventory : MonoBehaviour
{
    public Dictionary<int, string> counts; // UAC1015
}

Correct example: Add [SerializeField]:

// CORRECT: [SerializeField] tells Unity's dictionary backend to serialize the field
public class Inventory : MonoBehaviour
{
    [SerializeField] public Dictionary<int, string> counts; // OK
}

Correct example: Mark as [NonSerialized] when persistence isn’t needed:

// CORRECT: opt out of serialization entirely
public class Inventory : MonoBehaviour
{
    [System.NonSerialized] public Dictionary<int, string> runtimeCounts; // OK
}

UAC1016: Dictionary key or value type is not serializable

Severity: Error.

A serializable dictionary’s key and value types must themselves be serializable. This rule reports an error when either the key type or the value type fails the same serializability check the analyzer applies to ordinary fields: it must be a primitive, an enum, an array or List<T> of a serializable type, a [Serializable] class or struct, a UnityEngine.Object subclass (stored as a PPtr instance ID), a built-in supported type from the serialization whitelist, or a type that opts in via [MakeSerializable].

Dictionaries surface this as an error rather than a warning (compare UAC1010 for [SerializeField] on a non-serializable type, and UAC1001 for non-serializable list elements). A non-serializable dictionary key or value means the entry round-trip is broken outright — Unity can’t reconstruct the entry on deserialization, so silent data loss is more severe than the analogous List<T> case where the empty list is at least preserved.

UAC1016 only fires when the dictionary is actually being serialized — that is, when [SerializeField] is present (see UAC1015). If the field has no [SerializeField] Unity skips serialization entirely, so the K/V serializability concern is moot until the user opts the field in.

UAC1016 is suppressed when UAC1012 fires for the same field, because the interface- or abstract-type diagnostic already covers the same key or value position; layering “type is also not [Serializable]” on top of “type is an interface” would be redundant noise.

Incorrect example: Dictionary value type is missing [Serializable]:

public class DictValue
{
    public string key;
    public string value;
}

// INCORRECT: DictValue is a plain class without [Serializable], so the dictionary entries can't be serialized
public class Host : MonoBehaviour
{
    [SerializeField] public Dictionary<int, DictValue> data; // UAC1016 (value type)
}

Correct example: Mark the value type [Serializable]:

[System.Serializable]
public class DictValue
{
    public string key;
    public string value;
}

public class Host : MonoBehaviour
{
    [SerializeField] public Dictionary<int, DictValue> data; // OK
}

Correct example: Use a UnityEngine.Object subclass (PPtr):

// CORRECT: UnityEngine.Object subclasses serialize as PPtr instance IDs and don't need [Serializable]
public class Host : MonoBehaviour
{
    [SerializeField] public Dictionary<int, GameObject> objects; // OK
}

UAC1017: Tuple type not supported by serialization

Severity: Warning.

Unity’s serialization system doesn’t support tuple types (for example, (int x, int y) or ValueTuple<int, string>). Fields of tuple type are ignored during serialization with no runtime warning, which causes data loss when entering Play mode, saving scenes, or performing domain reloads.

To avoid the warning, choose the option that matches your intent:

  • If you need the field to persist, replace the tuple with a [Serializable] struct or class that holds the same fields.
  • If you don’t need the field to persist, make it private, or add [System.NonSerialized] if the field must remain public.

Incorrect example: Public field with tuple type:

// INCORRECT: Tuple type is ignored by serialization
public class GameData : MonoBehaviour
{
    public (int x, int y) position; // UAC1017: Tuple type is not supported by the serialization system
}

Correct example: Use a serializable struct:

// CORRECT: Use a [Serializable] struct instead of a tuple
[System.Serializable]
public struct Point2D
{
    public int x;
    public int y;
}

public class GameData : MonoBehaviour
{
    public Point2D position; // OK - serializable struct
}

Correct example: Mark as non-serialized if persistence isn’t needed:

// CORRECT: Mark as non-serialized if persistence isn't needed
public class GameData : MonoBehaviour
{
    [System.NonSerialized] public (int x, int y) position; // OK - explicitly not serialized
}

UAC1018: Field name conflicts with FormerlySerializedAs argument

Severity: Warning.

When you rename a serialized field, you can add [FormerlySerializedAs("previousName")] to the renamed field so Unity migrates the existing data from the previous name. If you later add another field whose name equals previousName, two fields in the same type map to the same serialized name, which causes incorrect serialization that can lose data after a serialization cycle (for example, when saving a scene or reloading the domain).

The analyzer reports the warning on the field whose name equals the string passed to [FormerlySerializedAs("...")] on another field in the same type. To resolve the conflict, rename the new field, remove it, mark it [NonSerialized] (for example, when the conflicting field is kept only as an [Obsolete] source-compatibility placeholder), or remove the matching [FormerlySerializedAs] argument from the renamed field.

Incorrect example: New field name conflicts with a FormerlySerializedAs argument:

// INCORRECT: textureAsset conflicts with [FormerlySerializedAs("textureAsset")] on icon
public class IconLibrary : MonoBehaviour
{
    [FormerlySerializedAs("textureAsset")]
    public Texture2D icon;
    public Texture2D textureAsset; // UAC1018: name conflicts with the [FormerlySerializedAs] argument above
}

Correct example: Rename the new field:

Give the new field a name that doesn’t conflict with any [FormerlySerializedAs] argument on another field:

// CORRECT: New field name doesn't conflict with any [FormerlySerializedAs] argument.
// Alternatively, if migration from "textureAsset" is no longer needed, remove
// the [FormerlySerializedAs] attribute and reuse "textureAsset" on the new field.
public class IconLibrary : MonoBehaviour
{
    [FormerlySerializedAs("textureAsset")]
    public Texture2D icon;
    public Texture2D fallbackTexture; // OK - distinct from the [FormerlySerializedAs] argument
}

Generics

When a field’s type is a constructed generic (for example, Holder<CustomData> or Holder<int>), the analyzer inspects each type parameter in the generic type definition to decide whether the corresponding type argument needs to be serializable.

The analyzer validates a type parameter only if it appears in a serialized field of the generic type definition. For each validated type parameter, the analyzer checks the corresponding type argument with the same rules it applies to direct fields. If the type argument violates a rule, the analyzer reports that rule.

List<T> is an exception. The analyzer checks the element type of List<T> as part of UAC1009 instead of the generic type argument rules described in this section.

Which rule the analyzer reports for an invalid type argument

The analyzer reports a different rule depending on why the type argument is invalid. The following example illustrates several rules. All of the analyzer rules apply to generic type arguments in the same way as for direct fields.

public class CustomData { public int value; }

public struct Vector3D { public float x, y, z; }

[System.Serializable]
public class Holder<T> { public T value; }

[System.Serializable]
public class RefHolder<T> { [SerializeReference] public T value; }

public class Player : MonoBehaviour
{
    public Holder<CustomData> data;           // UAC1001: CustomData lacks [Serializable]
    public RefHolder<Vector3D> pos;           // UAC1003: Vector3D is a struct, but RefHolder uses [SerializeReference] on T
    public Holder<int[][]> jaggedGrid;        // UAC1009: jagged array not supported
    public Holder<(int x, int y)> point;      // UAC1017: tuple not supported
    public Holder<PlayerStats> stats;         // OK - PlayerStats is serializable
}

When type arguments aren’t validated

If a type parameter doesn’t appear in any serialized field of the generic type definition (for example, the only field that uses T has [NonSerialized]), the corresponding type argument isn’t validated and the analyzer doesn’t report a warning.

[System.Serializable]
public class Holder<T> { [System.NonSerialized] public T value; }

public class Player : MonoBehaviour
{
    public Holder<Dictionary<string, int>> cache; // OK - T only used in [NonSerialized] field
}

Valid type arguments

When all type arguments satisfy the serialization rules, the analyzer reports no warning. For example, a generic wrapper around a [Serializable] type, a built-in primitive, or a supported collection produces no warning:

Suppressing warnings

If you have a valid reason to suppress a specific analyzer warning, you can use the #pragma warning disable directive:

#pragma warning disable UAC1001
public class IntentionallyUnserializedClass
{
    public int value;
}
#pragma warning restore UAC1001

Suppressing these warnings is not recommended, because they indicate important serialization issues that can cause problems at runtime.

Limitation: base class field analysis

The analyzer only analyzes fields on types that are serialization roots (types marked with [Serializable], types inheriting from UnityEngine.Object, etc.). The analyzer doesn’t analyze the fields of base classes that aren’t serialization roots, even if they are part of an inheritance hierarchy with a serialization root. In the following example, DerivedClass triggers the UAC1002 warning because its base class BaseClass lacks the [Serializable] attribute, but the fields on BaseClass itself (settings and data) don’t trigger any warnings because BaseClass isn’t a serialization root.

public class BaseClass
{
    public Dictionary<string, int> settings; // No UAC1015 — BaseClass isn't a serialization root
    public CustomData data; // No UAC1001 — BaseClass isn't a serialization root
}

public class CustomData { }

[System.Serializable]
public class DerivedClass : BaseClass // UAC1002: BaseClass lacks [Serializable]
{
    public int value; // This field is analyzed
}

To get warnings for the base class fields, mark the base class with the [Serializable] attribute so it becomes a serialization root:

[System.Serializable]
public class BaseClass
{
    public Dictionary<string, int> settings; // UAC1015: reported
    public CustomData data; // UAC1001: reported
}

public class CustomData { }

[System.Serializable]
public class DerivedClass : BaseClass
{
    public int value;
}

Mark base classes with the [Serializable] attribute if you need their fields to be validated. This makes them serialization roots and enables the analyzer to check their fields.

Additional resources

Serialization rules
Custom serialization