type Person { name: String }
function parsePerson(json: String): Person { ... }
function usePerson(person: Person) { ... }
The compiler will guarantee that usePerson will always get the correct shape of data for it's person variable. The only function you need to worry about is parsePerson, which will generally throw a runtime error if the incoming JSON does not conform to the expected shape. And in most web frameworks, the JSON to type parsing is done for you so once the framework hands you data in the type you are asking for you can expect it to be correct.Whether the language runtime also uses type checks while running depends entirely on the language and the runtime.
IIRC, the JVM (to take an example) more or less throws away a lot of the static type information -- which is often too generic to be useful in guiding optimization -- in favor of runtime profiling, and several of its key optimizations involve inserting runtime type checks.
In practice, with parsing, this usually means that runtime type metadata is used. Effectively, types become your schema, and you can validate against them as needed.
Then again, if both ends use schema, then comparing schemas is easy and gives you fair chance to fingers the place that differ between systems.
If there is one place or lib as a source of schema on both ends, then mismatch is just a result of different version being imported by them - something easy to check.
The difference is that in a statically-typed system, you're validating data at your boundaries such that they comply with your nominal types that are then compiler-guaranteed to be internally consistent.