Hello,
Double checking my work implementing IUserType. Implementing to facilitate mapping types to postgres Npgsql. Specifically in one case to JSON/JSONB column types, facilitated by either string, then to Newtonsoft.Json.Linq JObject or JArray, both JContainer, depending on the use case.
Best I can figure, Assemble and Disassemble are somewhat core and central of such an implementation. Around the NullSafeGet and Set, for instance. Almost to a point where it might be worth providing a generic serialization implementation, interface, etc, but starting from here:
public virtual object NullSafeGet(DbDataReader rs, string[] names, ISessionImplementor session, object owner)
{
    this.VerifyNullSafeNames(names: names);
    var name = names[0];
    var ordinal = rs.GetOrdinal(name);
    var value = rs[ordinal];
    return value switch
    {
        null => null,
        P p => Assemble(p, owner),
        _ => throw new InvalidOperationException($"Unable to get null safe value, names: [{string.Join(", ", names)}].")
    };
}
public void NullSafeSet(DbCommand cmd, object value, int index, ISessionImplementor session)
{
    // We expect there to be an parameter of this type.
    if (cmd.Parameters.TryGetValue<NpgsqlParameter>(index, out var arg))
    {
        // Parameter Value may be either null, of types R or P, otherwise throw.
        arg.Value = value switch
        {
            null => null,
            R r => Disassemble(r),
            P p => p,
            _ => throw new InvalidOperationException($"Unable to set null safe value type '{value.GetType()}' index {index}.")
        };
        // Indeed return here since we do not want to throw the default parameter ex.
        return;
    }
    throw new InvalidOperationException($"Unable to set null safe parameter value index {index}.");
}
Here I verify names apart from the implementation. Also I look up the postgres Npgsql parameter in this instance.
But the core of the approach, I think, are Assemble and Disassemble, from what I can gather. Everything revolves around that, including DeepCopy.
Mostly everything else is pretty boilerplate, in my estimation, so any specialization can focus on the A/D overrides. Sometimes perhaps also the Equals.
public abstract class NpgsqlJsonCustomTypeBase<P, R> : IUserType
{
    // ...
}
Have verified through mappings and to a test project, seems to satisfy things.
Posting here in case I am missing something, perhaps there are gaps I am unaware of.
Appreciate the feedback.
Best regards,
Michael W. Powell