I didn’t mention this in my first reply because it can be hard to explain the distinction, but you could still put reusable decoders in your other modules if there are clear data types that make sense as domain concepts:
For example, if you have a module for a “friends list” view, what I’m cautioning against is having that module define an arbitrary json structure:
-- example of what to avoid doing:
module FriendsList exposing (Msg, State, decoder, view)
decoder : Decoder (State, Cmd Msg)
view : State -> Html Msg
-- another example of what to avoid:
module FriendsList exposing (Flags, Msg, State, decoder, init, view)
decoder : Decoder Flags
init : Flags -> (State, Cmd Msg)
view : State -> Html Msg
In the above examples, the decoders are meant to be reusable, but they will be hard to maintain and are an unstable interface because they don’t really have any intuitive meaning as a domain concept.
In contrast, perhaps the idea of a “Friend” does make sense as a domain concept, so you might end up with a reusable decoder like this:
-- example of what might be better:
module FriendsList exposing (Friend, State, friendDecoder, view)
friendDecoder : Decoder Friend
init : State
view : List Friend -> State -> Html msg
Now the module is providing a decoder for a useful type instead of for a meaningless, arbitrary type. This can make your top-level decoder have less code while still letting the top-level have control over the JSON schema and decoupling it from the implementation of the other modules.
Though with that said, JSON decoders tend to be low-risk to maintain, so personally I’d probably start by making a potentially overly-verbose top-level decoder, then spend the most time focusing on cleaning up your module hierarchy and reducing the number of places you have to pass Cmds and Msgs around, and then if there’s more time after that, take a look at refactoring your top-level decoder.