I'm using Aeson to accept user configuration in JSON format, where some fields may be omitted and the default values would be used. According to doc I should write something like this:
import Data.Aeson
import GHC.Generics
data Person = Person
{ name :: String
, age :: Int
} deriving (Generic, Show)
instance FromJSON Person where
omittedField = Just $ Person "Unnamed" (-1)
parseJSON = genericParseJSON defaultOptions { allowOmittedFields = True }
main :: IO ()
main = do print (eitherDecode @Person "{}")
print (eitherDecode @Person "{\"name\":\"Bob\"}")
print (eitherDecode @Person "{\"name\":\"Bob\",\"age\":42}")
Does not really work:
Left "Error in $: parsing Main.Person(Person) failed, key \"name\" not found"
Left "Error in $: parsing Main.Person(Person) failed, key \"age\" not found"
Right (Person {name = "Bob", age = 42})
I believe the
omittedFielddefinition in yourFromJSONinstance applies to fields of typePersonin a larger data structure, not the fields ofPersonitself.One solution is to make names and ages into their own types, and define
omittedFieldfor those types.However, instead of using the arbitrary values
Name "Unnamed"andAge (-1)to signal the absence of a value, you could just as well wrap the fields ofPersoninMaybeinstead.If you need a partially-defined
PersonwithMaybe-wrapped fields in some parts of the code and a fully-definedPersonwith ordinary fields elsewhere, you could define them both.Then it’s straightforward to convert
PersonMaybe -> Maybe Personif needed. The “higher-kinded data” (HKD) pattern can help save some repetition, but it’s a bit more advanced and usually not worthwhile unless you have many fields and more than just 2 states.