I've tried this approach, and it's not great. You either have to pack a lot of noisy information in the JSON for the deserializer to work reliably, or you kludge it to try and guess, and then you find yourself designing your data structures around all the kludges.
You inevitably run the risk that your deserializer can, effectively, make arbitrary function calls. And your data is tied not only to your language, but also needs to find classes in specific modules.
I don't think I need to run arbitrary code to take a dict and make it into an instance of a specific class whose members are that dict, but this may depend on your choice of dynamic language!
Edit: I guess you mean the deserializer is running arbitrary code to import the necessary classes if they are not already imported. I would prefer that it fail in this case. I think it's not too much trouble to ask users who insist on using classes to have them around before instantiating them.
I mean that fundamentally the deserializer has lookup function and it's going to lookup anything it's told to.
You're right, some languages are worse about this, e.g. Python's pickle will perform arbitrary code executions.
But it's easy enough to restrict it to a set of known good constructors. You still have the classic late-binding dilemma: it's flexibility we don't actually want. Why should my find_matching_socks function ever have to worry that it might be passed basketballs?
The usual answer is, "it's fine, it'll just crash if they do that."
It might crash, or it might hang, or silently corrupt something. An attacker can stick the wrong kind of object into your code anywhere they like.
You inevitably run the risk that your deserializer can, effectively, make arbitrary function calls. And your data is tied not only to your language, but also needs to find classes in specific modules.