Meliora Islands Save System

Items in the player's inventory, buildings placed in the world and decorations placed in the buildings all need to be saved or sent somewhere.
When the server is shutting down, data needs to be saved to disk using json. When you're doing things in the game, data needs to be transfered online in byte arrays.
Both use-cases require different methods of packing data but we wanted a clean API to accommodate all different kinds of data.

Luckily, clever use of interfaces saves the day.

public void OnSerialize(ISerializer s)
{
    s.Serialize("hp", ref health);
}

This method handles all four cases of saving and loading to json and byte arrays, it just depends on which ISerializer is used.

public interface ISerializer
{
    void Serialize<T>(string key, ref T value);
}

When it's time to save the data, a serializer is used. This will put the health variable at either the json key 'hp' or in the next index in a byte array.
During loading of data, the deserializer can look at at the corresponding json key or array index and pass data through the ref keyword.

Various TypeSerializers were made to make sure different types of data are handled correctly.
The UShortSerializer makes sure every ushort value is saved and loaded correctly, both for json and a byte array.

public class UShortSerializer : Serializer<ushort>
{
    protected override void SerializeByteArr(ushort value, List<byte> bytes)
    {
        bytes.Add((byte)(value >> 8));
        bytes.Add((byte)value);
    }

    protected override ushort DeserializeByteArr(byte[] bytes, ref int offset, ref ushort valueRef)
        => valueRef = (ushort)(bytes[offset++] << 8 | bytes[offset++]);

    protected override void SerializeJson(object name, ushort value, JToken token)
        => JsonSerializeHelp(name, value, token);

    protected override ushort DeserializeJson(object name, JToken token, ref ushort valueRef) 
        => valueRef = ((JValue)JsonDeserializeHelp(name, token))?.GetUShort() ?? valueRef;
}

The custom DatabaseEntrySerializer only saves a single id when saving data. When loading it will load the correct database entry.
If we needed data in XML format, new serializer and deserializer scripts could be added without changing the game code.