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.

EchoObject is the universal intermediate representation that sits between your .NET objects and any serialized format. Every serialized value — whether a primitive integer, a nested compound object, or a heterogeneous list — is expressed as an EchoObject tagged with an EchoType discriminant. Because EchoObject is format-agnostic, a single in-memory tree can be written to binary, text, JSON, BSON, YAML, or XML without re-serializing the original .NET objects.

EchoType Enum

EchoType identifies what kind of data an EchoObject holds. Every instance has exactly one TagType.
ValueDescription
NullRepresents the absence of a value.
ByteUnsigned 8-bit integer (byte).
sByteSigned 8-bit integer (sbyte).
ShortSigned 16-bit integer (short).
IntSigned 32-bit integer (int).
LongSigned 64-bit integer (long).
UShortUnsigned 16-bit integer (ushort).
UIntUnsigned 32-bit integer (uint).
ULongUnsigned 64-bit integer (ulong).
FloatSingle-precision floating point (float).
DoubleDouble-precision floating point (double).
DecimalDecimal (decimal).
StringUTF-16 string (string).
ByteArrayRaw byte array (byte[]).
BoolBoolean (bool).
ListOrdered list of EchoObject children.
CompoundNamed dictionary of EchoObject children (key → value).

Constructors

Each primitive constructor sets TagType to the matching EchoType and stores the value internally.
new EchoObject()                        // EchoType.Null
new EchoObject(byte value)              // EchoType.Byte
new EchoObject(sbyte value)             // EchoType.sByte
new EchoObject(short value)             // EchoType.Short
new EchoObject(int value)               // EchoType.Int
new EchoObject(long value)              // EchoType.Long
new EchoObject(ushort value)            // EchoType.UShort
new EchoObject(uint value)              // EchoType.UInt
new EchoObject(ulong value)             // EchoType.ULong
new EchoObject(float value)             // EchoType.Float
new EchoObject(double value)            // EchoType.Double
new EchoObject(decimal value)           // EchoType.Decimal
new EchoObject(string value)            // EchoType.String  (null → "")
new EchoObject(byte[] value)            // EchoType.ByteArray
new EchoObject(bool value)              // EchoType.Bool
new EchoObject(EchoType type, object? value)   // explicit type + boxed value
new EchoObject(List<EchoObject> tags)   // EchoType.List, sets Parent/ListIndex on children

Static factories

Prefer these helpers over calling the EchoType-overload constructor directly — they set up the internal dictionary/list and parent-tracking in one step.
EchoObject compound = EchoObject.NewCompound();
EchoObject list     = EchoObject.NewList();

Properties

TagType
EchoType
The discriminant that identifies what kind of data this object holds. Read-only after construction (except when a delta operation mutates a node in-place).
Value
object?
The raw boxed value. Getting returns the internal _value field directly. Setting calls SetValue(value), which validates the new value against the current TagType and performs numeric conversions where possible.
Parent
EchoObject?
The parent EchoObject, or null for a root node. Set automatically when the object is added to a compound or list.
CompoundKey
string?
The key under which this object is stored in its parent compound. null when the parent is a list or when there is no parent.
ListIndex
int?
The zero-based index at which this object sits in its parent list. null when the parent is a compound or when there is no parent.
Count
int
The number of direct children. Returns the dictionary size for Compound, the list length for List, and 0 for all primitive types including Null.
IsPrimitive
bool
true for every EchoType except Compound, List, and Null — i.e., all types that carry a single scalar value.
Tags
Dictionary<string, EchoObject>
Direct accessor for the underlying dictionary of named children. Only valid when TagType == EchoType.Compound; returns null (via cast) for any other type. Prefer the compound indexer or Get/TryGet for safe access.
List
List<EchoObject>
Direct accessor for the underlying ordered list of children. Only valid when TagType == EchoType.List; returns null (via cast) for any other type. Prefer Get(int) or ListAdd/ListRemoveAt for safe access.

Value Accessors

Each typed accessor provides a strongly-typed getter and setter. Numeric accessors use Convert and therefore perform widening/narrowing coercions; non-matching types throw InvalidCastException.
PropertyTypeNotes
BoolValueboolDirect cast; throws on non-Bool types.
ByteValuebyteConvert.ToByte.
sByteValuesbyteConvert.ToSByte.
ShortValueshortConvert.ToInt16.
IntValueintConvert.ToInt32.
LongValuelongConvert.ToInt64.
UShortValueushortConvert.ToUInt16.
UIntValueuintConvert.ToUInt32.
ULongValueulongConvert.ToUInt64.
FloatValuefloatConvert.ToSingle.
DoubleValuedoubleConvert.ToDouble.
DecimalValuedecimalConvert.ToDecimal.
ByteArrayValuebyte[]Direct cast; throws on non-ByteArray types.
StringValuestringNumeric types use InvariantCulture. Null"NULL". ByteArray → Base64. Throws on Compound/List.
var tag = new EchoObject(3.14f);
float v = tag.FloatValue;   // 3.14f
tag.FloatValue = 2.71f;     // via SetValue
string s = tag.StringValue; // "3.14" (InvariantCulture)

Compound Methods

Compound methods operate on EchoObject instances whose TagType is EchoType.Compound. Most throw InvalidOperationException when called on a non-compound node.

Indexer — this[string tagName]

public EchoObject this[string tagName] { get; set; }
Gets or sets a child by name. The setter clears the old child’s parent/key tracking before replacing it. Throws ArgumentException if the new value already has a parent (clone it first).
compound["health"] = new EchoObject(100);
int hp = compound["health"].IntValue;

Get(string)

public EchoObject? Get(string tagName)
Returns the child with the given name, or null if it does not exist.
tagName
string
The key to look up. Must not be null or empty.

TryGet(string, out EchoObject?)

public bool TryGet(string tagName, out EchoObject? result)
Pattern-matching lookup. Returns true and populates result if the key exists; returns false and sets result to null otherwise.
if (compound.TryGet("velocity", out var vel))
    Console.WriteLine(vel!.FloatValue);

Contains(string)

public bool Contains(string tagName)
Returns true if a child with tagName exists in this compound.

Add(string, EchoObject)

public void Add(string name, EchoObject newTag)
Adds a new child. Throws ArgumentException if:
  • name is null or empty.
  • newTag is null or references this node.
  • newTag already has a parent (clone it with Clone() first).
  • A child with the same name already exists (use the indexer to overwrite).
name
string
The key to store the child under.
newTag
EchoObject
The child to add.

Remove(string)

public bool Remove(string name)
Removes the child with the given name, clears its parent/key tracking, and returns true. Returns false if the key does not exist.

Rename(string, string)

public void Rename(string oldName, string newName)
Renames an existing child key. Throws ArgumentException if oldName does not exist or newName is already taken.
oldName
string
The current key of the child.
newName
string
The desired new key.

GetNames()

public IEnumerable<string> GetNames()
Returns all keys in this compound (dictionary key order is not guaranteed).

GetAllTags()

public IEnumerable<EchoObject> GetAllTags()
Returns all child values in this compound.

GetStoredType()

public Type? GetStoredType()
When the compound was serialized with type metadata (i.e., a $type key is present), resolves and returns the stored Type. Returns null if no type metadata was written.
Use GetStoredType() in network-facing code to verify the incoming payload carries an expected type before deserializing it, preventing type-confusion attacks.
Type? t = echoFromNetwork.GetStoredType();
if (t == null || !typeof(IPacket).IsAssignableFrom(t))
    throw new SecurityException("Unexpected payload type.");

var packet = Serializer.Deserialize<IPacket>(echoFromNetwork);

List Methods

List methods operate on EchoObject instances whose TagType is EchoType.List. All mutating methods keep ListIndex values consistent across the entire list.

Indexer — this[int tagIdx]

public EchoObject this[int tagIdx] { get; set; }
Gets or sets the element at the given zero-based index. The setter clears the replaced element’s parent and index tracking.

Get(int)

public EchoObject Get(int tagIdx)
Returns the element at tagIdx. Throws InvalidOperationException on non-list nodes.

ListAdd(EchoObject)

public void ListAdd(EchoObject tag)
Appends tag to the end of the list and sets its Parent and ListIndex. Throws if tag already has a parent.
tag
EchoObject
The element to append. Must not already have a parent.

ListInsert(int, EchoObject)

public void ListInsert(int index, EchoObject tag)
Inserts tag at index and shifts all subsequent elements’ ListIndex values up by one.
index
int
The zero-based insertion position. Must be in [0, Count].
tag
EchoObject
The element to insert. Must not already have a parent.

ListRemove(EchoObject)

public void ListRemove(EchoObject tag)
Finds tag by reference and removes it, updating all subsequent ListIndex values. No-op if tag is not in the list.

ListRemoveAt(int)

public void ListRemoveAt(int index)
Removes the element at index, clears its parent/index tracking, and shifts subsequent elements.
index
int
The zero-based index of the element to remove. Must be in [0, Count).

ListClear()

public void ListClear()
Removes all elements, clearing Parent and ListIndex on each one.
var list = EchoObject.NewList();
list.ListAdd(new EchoObject(1));
list.ListAdd(new EchoObject(2));
list.ListAdd(new EchoObject(3));

list.ListRemoveAt(1);   // removes 2; list is now [1, 3]
int count = list.Count; // 2

Query Methods

Query methods work across the full tree of an EchoObject and do not require the root to be a specific type.

Find(string)

public EchoObject? Find(string path)
Navigates a slash-separated path and returns the node at that location, or null if any segment is missing. List segments are parsed as zero-based integers.
path
string
A /-separated path such as "stats/stamina" or "Players/0/Name". An empty or whitespace path returns this.
EchoObject? name = root.Find("Players/0/Name");

TryFind(string, out EchoObject?)

public bool TryFind(string path, out EchoObject? tag)
Pattern-matching wrapper around Find. Returns true and populates tag on success.

GetValue<T>(string, T?)

public T? GetValue<T>(string path, T? defaultValue = default)
Navigates to path and attempts to convert the found node’s value to T via Convert.ChangeType. Returns defaultValue if the path is not found or the conversion fails.
path
string
The slash-separated path to the target node.
defaultValue
T?
The value to return when the path does not exist or conversion fails. Defaults to default(T).
int stamina = root.GetValue<int>("stats/stamina", 0);

GetEchoAt(string)

public EchoObject? GetEchoAt(string path)
Shorthand for GetValue<EchoObject>(path).

GetListAt(string)

public List<EchoObject>? GetListAt(string path)
Shorthand for GetValue<List<EchoObject>>(path).

GetDictionaryAt(string)

public Dictionary<string, EchoObject>? GetDictionaryAt(string path)
Shorthand for GetValue<Dictionary<string, EchoObject>>(path).

Where(Func<EchoObject, bool>)

public IEnumerable<EchoObject> Where(Func<EchoObject, bool> predicate)
Filters the direct children of this node. For List nodes, filters list elements; for Compound nodes, filters tag values. Returns an empty enumerable for all other types.

Select<T>(Func<EchoObject, T>)

public IEnumerable<T> Select<T>(Func<EchoObject, T> selector)
Projects the direct children of this node using selector. Behavior mirrors Where — operates on immediate children only.
// Extract all integer scores from a compound of player records
IEnumerable<int> scores = scoreboard.Select(tag => tag["score"].IntValue);

FindAll(Func<EchoObject, bool>)

public IEnumerable<EchoObject> FindAll(Func<EchoObject, bool> predicate)
Performs a depth-first recursive search of the entire subtree rooted at this node and returns all nodes that satisfy predicate, including this node itself if it matches.
// Find every string node in the tree
IEnumerable<EchoObject> strings = root.FindAll(n => n.TagType == EchoType.String);

Exists(string)

public bool Exists(string path)
Returns true if Find(path) would return a non-null node.

GetPathsTo(Func<EchoObject, bool>)

public IEnumerable<string> GetPathsTo(Func<EchoObject, bool> predicate)
Performs a depth-first recursive search and returns the slash-separated path string for every node that satisfies predicate.
IEnumerable<string> paths = root.GetPathsTo(n => n.TagType == EchoType.Bool);
// e.g. ["settings/soundEnabled", "settings/fullscreen"]

GetPath()

public string GetPath()
Returns this node’s absolute path from its root ancestor, built by walking Parent links upward and joining compound keys/list indices with /.

GetRelativePath(EchoObject, EchoObject)

public static string GetRelativePath(EchoObject from, EchoObject to)
Returns the slash-separated path from from to to, where to must be a descendant of from. Throws ArgumentException if to is not reachable from from.
from
EchoObject
The ancestor node to treat as the path root.
to
EchoObject
The descendant node to locate.

Delta Methods

Deltas capture the structural differences between two EchoObject trees as a compact list of named operations, making them ideal for network state synchronization and incremental saves.

CreateDelta(EchoObject, EchoObject)

public static EchoObject CreateDelta(EchoObject from, EchoObject to)
Compares from (baseline) and to (modified) and produces a compound EchoObject containing an "Operations" list. Each operation is itself a compound describing a SetValue, AddCompoundTag, RemoveCompoundTag, AddListItem, or RemoveListItem action at a specific path.
from
EchoObject
The original/baseline state.
to
EchoObject
The modified/target state.
Returns — A compound EchoObject containing all operations needed to transform from into to.

ApplyDelta(EchoObject, EchoObject)

public static EchoObject ApplyDelta(EchoObject baseline, EchoObject delta)
Applies the operations in delta to a clone of baseline and returns the result. The baseline is not mutated.
baseline
EchoObject
The original state to apply the delta to.
delta
EchoObject
A delta produced by CreateDelta.
Returns — A new EchoObject representing baseline after the delta is applied.
EchoObject stateA = Serializer.Serialize(gameStateA);
EchoObject stateB = Serializer.Serialize(gameStateB);

// Compute and transmit only the diff
EchoObject delta = EchoObject.CreateDelta(stateA, stateB);
byte[] payload = EchoBinaryFormat.Instance.WriteToBytes(delta);

// On the remote end
EchoObject receivedDelta = EchoBinaryFormat.Instance.ReadFromBytes(payload);
EchoObject reconstructed = EchoObject.ApplyDelta(stateA, receivedDelta);

I/O Methods — Binary

Binary format is the most compact and fastest format for storage and transmission.

WriteToBinary(BinaryWriter, BinarySerializationOptions?)

public void WriteToBinary(BinaryWriter writer, BinarySerializationOptions? options = null)
Writes this node to writer in Echo’s native binary format.
writer
BinaryWriter
The output writer.
options
BinarySerializationOptions?
default:"null"
Optional format configuration.

WriteToBinary(FileInfo, BinarySerializationOptions?)

public void WriteToBinary(FileInfo file, BinarySerializationOptions? options = null)
Opens file for writing and serializes this node to it. The stream is disposed automatically.

ReadFromBinary(BinaryReader, BinarySerializationOptions?) (static)

public static EchoObject ReadFromBinary(BinaryReader reader, BinarySerializationOptions? options = null)
Reads and returns an EchoObject from reader.

ReadFromBinary(FileInfo, BinarySerializationOptions?) (static)

public static EchoObject ReadFromBinary(FileInfo file, BinarySerializationOptions? options = null)
Opens file, reads an EchoObject, and returns it. The stream is disposed automatically.

I/O Methods — Text

The Echo text format is human-readable and suitable for configuration files and debugging.

WriteToString()

public string WriteToString()
Serializes this node to an Echo-format string.

WriteToString(FileInfo)

public void WriteToString(FileInfo file)
Writes this node to file in Echo text format.

ReadFromString(string) (static)

public static EchoObject ReadFromString(string input)
Parses an Echo-format string and returns the root EchoObject.

ReadFromString(FileInfo) (static)

public static EchoObject ReadFromString(FileInfo file)
Reads an Echo-format text file and returns the root EchoObject.
// Save and reload via text format
string text = echo.WriteToString();
EchoObject restored = EchoObject.ReadFromString(text);

I/O Methods — Interop Formats

These methods convert between EchoObject and widely-used interchange formats, enabling integration with systems that do not speak Echo’s native formats.

JSON

public string WriteToJson()
public static EchoObject ReadFromJson(string json)

BSON

public byte[] WriteToBson()
public static EchoObject ReadFromBson(byte[] bson)

YAML

public string WriteToYaml()
public static EchoObject ReadFromYaml(string yaml)

XML

public string WriteToXml()
public static EchoObject ReadFromXml(string xml)
// Export to JSON for a REST API response
string json = Serializer.Serialize(myDto).WriteToJson();

// Import from a YAML configuration file
EchoObject cfg = EchoObject.ReadFromYaml(File.ReadAllText("config.yaml"));
MyConfig config = Serializer.Deserialize<MyConfig>(cfg)!;

Utility

Clone()

public EchoObject Clone()
Returns a deep copy of this node and all its descendants. The clone has no parent, so it can be freely added to any other compound or list.
EchoObject copy = original.Clone();
anotherCompound["copy"] = copy; // Safe — copy.Parent is null

Equals(EchoObject?) and Operators

public bool Equals(EchoObject? other)
public static bool operator ==(EchoObject left, EchoObject right)
public static bool operator !=(EchoObject left, EchoObject right)
Structural (deep) equality. Two EchoObject instances are equal when they have the same TagType and recursively equal values. For Compound, key-value pairs are compared without regard to insertion order. For ByteArray, a byte-by-byte comparison is performed.
var a = new EchoObject(42);
var b = new EchoObject(42);
bool same = (a == b); // true — structural equality

Build docs developers (and LLMs) love