I could be wrong about this, but I think that, like Elm, it's not technically Grain that throws the runtime exception. It's whatever is reading in the JSON.
In more detail:
It's purely functional, so it cannot handle I/O at all on its own. It needs some sort of harness into which it fits which handles that, and which then calls the pure functions in Elm / Grain. In Elm's case that harness is JavaScript, and it seems to be a similar case for Grain. (Maybe for Grain it's WebAssembly? I don't honestly know enough about Web Dev to say).
During I/O (including user input, and sending data over a network), runtime exceptions can still be thrown, but once it's been converted and passed into the functional part, it's in a sense correctly formatted by definition, and the purely functional Elm / Grain parts don't throw runtime exceptions at all.