Type-safe JSON deserialization
You need to:


Get the annotated signature of a target_class' __init__ method;
Apply whatever arguments come from the provided JSON string;
Check that all arguments are present;
Check that all arguments conform to the annotations of the class' __init__.


All except the last step is pretty straightforward using the inspect module:

import json
import inspect
from typing import Callable, Any, T


def deserialize_json(target_class: Callable[[Any], T], object_repr: str) -&gt; T:
    data = json.loads(object_repr)
    signature = inspect.signature(target_class)
    bound_signature = signature.bind(**data)
    bound_signature.apply_defaults()
    return target_class(**bound_signature.arguments)


inspect.Signature.bind validate arguments pretty much the same as a real call to target_class.__init__ would; raising TypeError if a positionnal argument is missing or if an extra keyword argument is found.

Now you "just" need to validate the type of the arguments based on the annotations. A simple but potentially sufficient way of doing would check the annotation attribute of each parameter of the Signature and check it is the right type using isinstance:

def deserialize_json(target_class: Callable[[Any], T], object_repr: str) -&gt; T:
    data = json.loads(object_repr)
    signature = inspect.signature(target_class)
    bound_signature = signature.bind(**data)
    bound_signature.apply_defaults()

    for name, value in bound_signature.arguments.items():
        expected_type = signature.parameters[name].annotation
        if not isinstance(value, expected_type):
            raise TypeError('&lt;error message&gt;')

    return target_class(**bound_signature.arguments)


Note that this simple example would most likely have troubles validating Generic types or parametrized Generic types. Adapting the code to fit those needs is left as an exercise for the reader (but Python 3.7 have better support for such checks).



As a side note, if all target classes ressemble the Foo example, and you don't mind having them immutable, you can make use of typing.NamedTuple:

class Foo(typing.NamedTuple):
    val: int
    msg: str
    frac: float = 1.0


print(Foo(1, 'bar'))  # Foo(val=1, msg='bar', frac=1.0)
print(Foo(2, 'baz', 0.0))  # Foo(val=2, msg='baz', frac=0.0)
print(Foo(3))  # TypeError: __new__() missing 1 required positional argument: 'msg'


or switch to Python 3.7 and use full-blown dataclasses.