That doesn't handle custom JSON schemas at all. I don't know much about Elm but at work in Haskell we mostly write FromJSON instances manually. It's probably the same amount of code as the data type definition.
This is the way to go, if you are tied to JSON for some reason. A far better solution is to get rid of JSON if most of your stack is a typed stack and then convert to JSON as late as possible.
Custom JSON which people invent as they go along is just going to present your system with pain over time.
Do you have examples of things that don't fit the vanilla FromJSON ?
From the top of my head, there's lensed things (which can easily be done once with a LensJSON typeclass), and things which use a different serialization (e.g. no "tag" field, etc) for which there is little you can do, if each of your data types has its own standard. If they do, though, you could easily define a typeclass for each source (e.g. StackoverflowJSON, HackernewsJSON, etc).
We don't do type classes for this purpose. If you have to interface with some JSON with predefined schema, we just write the instances manually. It's not that hard at all—usually it's just calling withObject with a series of lifted function applications `Constructor <$> (o .: "field1") <*> (o .: "field2")`. Remember the Parser type is a Functor/Applicative/Monad/Alternative/MonadPlus so there's a whole host of utilities for these classes that make writing such instances both simple and concise. Of course if you are doing simple things like removing the leading underscore on lenses data typed, just use TH to derive the instance, passing slightly modified `Options`.
If you need to manually handle tags, here's a snippet that can help you:
-- | Safely accesses a JSON object where the value at a key is text. It takes an
-- object, a key, and a continuation of what to do when this key is present.
--
-- Example:
--
-- @
-- data D = A | B | C Int
-- instance FromJSON D where
-- parseJSON = withObject "D" $ \o ->
-- o .:=> "tag" $ \case
-- "A" -> pure A
-- "B" -> pure B
-- "C" -> C <$> (o .: "theInt")
-- t -> fail $ "Unrecognized tag in type D: " ++ unpack t
-- @
(.:=>) :: Object -> Text -> (Text -> Parser a) -> Parser a
o .:=> k = \m -> o .: k >>= withText ("Object with mandatory key " <> unpack k) m
One could use a generics library to emulate something like Go's struct field tags, which decouple "external" fields names from the actual field names. Something like this is explained from 48:50 in this talk: https://skillsmatter.com/skillscasts/10181-fun-with-sum-and-...
But perhaps it wouldn't save much code compared to custom-made parsers.
[0] https://github.com/jet/JsonSchemaProvider