Getting started
Pack a value to bytes with ByteWriter and ByteReader, and a type to JSON, from a VRChat world in ordinary C#.
Pack to bytes
ByteWriter builds a buffer; ByteReader reads it back, in the same order:
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, 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:
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:
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:
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 is every member of both halves.