## Pack to bytes

`ByteWriter` builds a buffer; `ByteReader` reads it back, in the same order:

```csharp
using Udonite.Serialization;

byte[] payload = new ByteWriter()
    .WriteInt32(playerId)
    .WriteString(name)
    .ToArray();

ByteReader reader = new ByteReader(payload);
int id = reader.ReadInt32();
string readName = reader.ReadString();
```

Both are covered in full in [Udonite Net's messages guide](/docs/net/messages), which is what
they were built for — a `NetworkEvent`'s `Serialize(ByteWriter)`/`Deserialize(ByteReader)`.
Nothing about them is network-specific though: reach for the same pair anywhere you want a
compact, self-owned binary shape — a save format, a file, anything you control both ends of.

**A read past the end does not throw.** It logs and returns the type's default, because Udon has
no exception handling to unwind into and a malformed or truncated buffer must not take the
reading behaviour down with it.

## Pack a type to JSON

A type opts in by implementing `IJsonSerializable` — writing its own `ToJson()`/`FromJson()`,
because Udon has no reflection to do that walk for you:

```csharp
using Udonite.Serialization;
using VRC.SDK3.Data;

public class Player : IJsonSerializable
{
    public string Name;
    public int Score;

    public DataToken ToJson()
    {
        DataDictionary dict = new DataDictionary();
        dict.Add("name", Name);
        dict.Add("score", Score);
        return dict;
    }

    public void FromJson(DataToken token)
    {
        DataDictionary dict = token.DataDictionary;
        Name = dict["name"].String;
        Score = (int)dict["score"].Double;
    }
}
```

**Read a number as `.Double`, never `.Int`.** Every JSON number parses back as `TokenType.Double`,
even one written as a bare integer — `.Int` throws on it. This is a fact about `VRCJson` itself,
not something this package can paper over.

Serializing and deserializing never mention `DataToken` at the call site:

```csharp
Player player = new Player { Name = "Ada", Score = 42 };

string json = Json.Serialize(player);          // {"name":"Ada","score":42}, or null
Player restored = Json.Deserialize<Player>(json); // a new Player, or null on bad JSON
```

`Deserialize<T>` builds the instance itself — `T` needs a parameterless constructor
(`where T : new()`), which the compiler resolves at the call site rather than at runtime, the
same way it resolves any other generic constraint. It is not reflection, and it works even though
Udon has none.

**Nothing is inspected automatically.** A field `ToJson()` does not mention is never sent, and a
type that does not implement `IJsonSerializable` at all is a compile error against
`Serialize<T>` — never a partially-serialized object at runtime.

## Building JSON with no fixed type

For JSON assembled ad hoc — no `IJsonSerializable` behind it — the non-generic overloads work
directly on `DataToken`:

```csharp
DataDictionary dict = new DataDictionary();
dict.Add("ok", true);

string json = Json.Serialize(dict);          // or Json.Serialize(dict, pretty: true)
DataToken? parsed = Json.Deserialize(json);
```

This is the one place `DataToken` is unavoidable, because it is VRChat's own JSON value model —
a `DataDictionary` for an object, a `DataList` for an array, a scalar token for anything simpler.

## Where to go next

[Reference](/docs/serialization/reference) is every member of both halves.
