There are several cases where the native protocol limits our ability to support functions due to argument or result types that are only known at execution time. For example, we can't support fromJson() in the selection clause or toJson() outside of the selection clause.
To support these functions and other use cases, we should add a dynamic type to the native protocol. The serialization format for this would be a <type> field (i.e. a numeric typecode, as defined in the native protocol spec) followed by the serialized value. When deserializing values, drivers would first read the typecode to determine the type, then decode the remainder of the bytes accordingly. How this type information is exposed to the end user is up to the driver authors.