If you are parsing known JSON schemas, defining them as types instead of generic dictionary lookups makes this a type-error, not a implementation error.
I don’t think that’s painful at all. Yes you define it as a root class, with dependent classes as needed. This is the contract. It needs to be defined somehow, and IMO this is as good a way as any.
From there on you just use JSON.NET to convert your JSON-string to an instance of the root type, literally one line of code.
And after that you get code-completion and type-inference and compile-time checking for all data-object access.