While working on a custom codable prototype, I stumbled across something I hadn't paid much attention to before. Rather than sidetrack that thread further, I'm spinning this off into a separate question:
It's completely understandable why JSONDecoder.decode is marked throwing, but why is JSONEncoder.encode marked throwing as well? When it does throw, is it essentially only in cases of programmer errors or API contract violations – cases that are typically better handled with precondition checks or similar mechanisms?
All encoding containers support throwing to indicate "this value is not valid to encode in this context" (see EncodingError.invalidValue, the only case of EncodingError). What this means depends on the encoding format, and the value passed in.
For example, JSON as a format does not support floating-point NaN or infinity values, and so those values need to be handled in some form. By default, if you try to encode one of these values, it will throw an error, but it does offer JSONEncoder.NonConformingFloatEncodingStrategy as a way to alter the behavior.
These aren't handled using fatalError or precondition because they're not programmer error — a perfectly valid value for one format may be unrepresentable in another, and it's perfectly reasonable to want to encode an Encodable type into one of several different formats. At the individual encode(to:) layer, you may not know what format you're encoding to, and there may not necessarily be a better way to handle not being able to encode a given value than by bubbling the information up (instead of taking down the entire process).
In addition to what @itaiferber mentioned above, some types like Predicate which use CodableWithConfiguration may use this mechanism to throw an error when the configuration cannot permit encoding of a value (for example, if a type/keypath was not added to the allowlist in the PredicateCodableConfiguration). So in addition to an encoder throwing when the format can't handle a certain otherwise-valid value, a type itself may throw when the value can't be encoded without additional context/configuration.