You pretty much need to use a variant, or of course have an army of polymorphic VIs which demand every type be accounted for.
I have done what you're describing using variants. They do have meta information, which you take advantage of. I don't have the info handy, but there is a library meant to handle variants, I believe it is called VariantType.lvlib and is somewhere in your vi.lib/utilities folder. A word of caution though that since these VIs are not on the palette NI might change them some day...
The key is recursion. Take the variant, act accordingly if it is a primitive type (bool, double, etc). If it's a complex type (cluster, array, waveform, etc) do what you need to do then recurse on the contained type(s).
Note because variants do involve a lot of copying, this can get prohibitive for deep/large data structures. You will never get as good performance as doing a "hardwired" VI for whatever your type is.
I ultimately abandoned my variant approach because serializing my main data structures could take minutes using this method (compared to seconds by keeping things type specific). I still use it for simple or small data structs though.