Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/ProwlEngine/Prowl.Echo/llms.txt

Use this file to discover all available pages before exploring further.

Prowl.Echo gives you fine-grained control over the serialization pipeline through a set of purpose-built attributes. Rather than serializing everything by default, Echo follows a clear set of rules — and these attributes let you override them precisely where needed, without touching your serialization logic.

Default Serialization Rules

Before diving into the attributes, it helps to understand what Echo serializes without any annotation at all.

Serialized by default

Public instance fields declared on the type or any base class

Never serialized

Properties, static fields, const fields, and readonly fields
Properties are not serialized. This is intentional — Echo mirrors Unity’s serializer behavior, which serializes fields only. If you expose data through a property backed by a private field, add [SerializeField] to the backing field.
Private and protected fields are also skipped by default. Use [SerializeField] to opt them in individually.

Attributes Reference

[SerializeField]

Forces a private or protected field into the serialization pipeline. Without this attribute, non-public fields are invisible to Echo.
public class Character
{
    public string Name = "Hero";

    [SerializeField]
    private int _experiencePoints = 0;

    [SerializeField]
    protected float _stamina = 100f;
}
Use [SerializeField] to encapsulate your data behind access modifiers while still persisting internal state. This keeps your public API clean without sacrificing save/load fidelity.

[SerializeIgnore]

Excludes a public field from serialization entirely. Echo also respects the standard [NonSerialized] attribute, so either works — but [SerializeIgnore] is the Echo-native choice.
public class Enemy
{
    public string Name = "Goblin";
    public int Health = 50;

    // Runtime cache — no need to persist this
    [SerializeIgnore]
    public string CachedDisplayName = "";

    // Also recognized by Echo
    [NonSerialized]
    public float LastSeenTime;
}
Both [SerializeIgnore] and [NonSerialized] cause the field to be skipped. If you’re writing new code, prefer [SerializeIgnore] for clarity of intent within the Echo ecosystem.

[FormerlySerializedAs(string oldName)]

Allows a renamed field to still deserialize from data that was saved under the old name. You can stack multiple [FormerlySerializedAs] attributes on a single field to cover a chain of past names.
public class Player
{
    [FormerlySerializedAs("health")]
    [FormerlySerializedAs("hp")]
    public int HitPoints = 100;
}
When deserializing, Echo first tries the current field name (HitPoints), then falls back to health, then to hp — in the order they are declared. This lets you rename fields freely across save-file versions without breaking existing data.
Stack attributes oldest-to-newest for clarity, and keep the oldest names around as long as you need to support legacy save files. There is no runtime cost for former names that aren’t found in the data.

[IgnoreOnNull]

Skips writing the field to the serialized output when its value is null. If the field is non-null, it serializes normally. This trims output size for optional data that is typically absent.
public class Item
{
    public string Name = "Sword";

    [IgnoreOnNull]
    public string? Description = null;

    [IgnoreOnNull]
    public List<string>? Tags = null;
}
When Description and Tags are null, they are omitted from the serialized compound entirely. On deserialization, absent fields simply leave the corresponding field at its default value — no error is raised.
[IgnoreOnNull] only affects writing. Reading is unaffected: if the key is present in the data, the field is populated normally regardless of this attribute.

[SerializeIf(string conditionMemberName)]

Makes serialization of a field conditional. The named member — a property, field, or parameterless method — is evaluated at runtime; if it returns false, the field is skipped. The condition member must return bool and be accessible (public or non-public) on the same type.
public class GameState
{
    [SerializeIf(nameof(IsDebugMode))]
    public string DebugData = "";

    [SerializeIf(nameof(HasInventory))]
    public List<string> Inventory = new();

    public bool IsDebugMode => false;

    private bool HasInventory => Inventory.Count > 0;
}
Echo resolves the condition member in this order:
1

Property

Looks for a readable bool property with the given name (public or non-public, instance).
2

Field

Falls back to a bool field with the given name.
3

Method

Falls back to a parameterless method returning bool.
If the condition member cannot be found or does not return bool, Echo defaults to serializing the field and logs a warning via Serializer.Logger. If the condition member is found but throws an exception during evaluation, Echo still defaults to serializing the field and logs an error instead.
The condition is evaluated during serialization only. It does not affect deserialization — if the key is present in the data, it will always be read back regardless of the current condition value.

[FixedEchoStructure]

Applied to a class or struct, this attribute tells Echo that the type has a stable, fixed field order and will not change between versions. Echo uses this to enable positional (ordinal-based) serialization, which skips embedding field names in the output — producing significantly smaller and faster output.
[FixedEchoStructure]
public struct Vector3
{
    public float X;
    public float Y;
    public float Z;
}

[GenerateSerializer]
[FixedEchoStructure]
public partial struct Color
{
    public float R;
    public float G;
    public float B;
    public float A;
}
Because field names are not stored, adding, removing, or reordering fields in a [FixedEchoStructure] type is a breaking change for any previously serialized data. Only apply this attribute to types whose layout you consider permanently stable, such as math primitives, network packets, or protocol messages.
[FixedEchoStructure] pairs especially well with [GenerateSerializer] for maximum throughput — see the Performance guide for benchmark results.

Build docs developers (and LLMs) love