If you wanted type safety with JSON in Go then don’t use the marshal interface. Period.
And even for unmarshalling you could get a compile time error if you try to unmarshall to a type that doesn't support it.
Go has this concept baked into the language. For example read and write interfaces so you can write data to an endpoint without having to worry about whether it’s a file system object, TCP/UDP socket, gzip achieve, and a plethora of other options too.
It’s a nice convenience feature and makes up considerably for the restrictions that the Go type system places.
And if you need higher performance code, or compile time safety, or have any other constraints that limit your ability to use these generalised interfaces, then you can use different APIs from the Go standard library to achieve the same thing.
You don’t need to call json.Marshal() if you don’t want to. It’s an option, but it’s just one API into the JSON toolbox.
Compile time cannot check for runtime values (not even in Rust) - is your position that fields can only be populated at compile time?