Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I use Text.JSON.Generic with optional JSON fields?

Is it possible to use Text.JSON.Generic with a JSON record type that includes optional fields? I was hoping that this would "just work" if I declared the Haskell type as Maybe a, for example:

import Text.JSON.Generic

data Record = Record {
   myMandatoryField :: Integer,
   myOptionalField :: Maybe Integer
} deriving (Eq, Show, Data, Typeable)

but that doesn't do the right thing.

If it's not possible to make optional fields work with Text.JSON.Generic, is there an alternative Haskell-JSON data binding library that does work with optional fields?

like image 953
jchl Avatar asked Oct 29 '25 10:10

jchl


2 Answers

This seems to be a known issue with Generics-based parsing. Aeson has had the same issue and the maintainers have decided to deprecate that functionality in favor of a Template Haskell-based strategy: https://github.com/bos/aeson/issues/118

With Aeson, your code would look very similar:

import Data.Aeson
import Data.Aeson.TH

data Record = Record {
    myMandatoryField :: Integer,
    myOptionalField :: Maybe Integer
} deriving (Eq, Show)

$(deriveJSON id ''Record)

This way, the Maybe field encodes and decodes as expected:

$ ghci
λ :l Main.hs
Ok, modules loaded: Main.
λ encode $ Record 5 Nothing
"{\"myOptionalField\":null,\"myMandatoryField\":5}"
λ decode it :: Maybe Record
Just (Record {myMandatoryField = 5, myOptionalField = Nothing})

UPDATE: As mentioned in the comments, null-field omission is available with Template Haskell in Aeson HEAD, but that's not yet on Hackage. You can get that behavior today with hand-written FromJSON/ToJSON instances:

instance FromJSON Record where
    parseJSON = withObject "record" $ \o -> Record
        <$> o .: "myMandatoryField"
        <*> o .:? "myOptionalField"

instance ToJSON Record where
    toJSON (Record manf optf) = object $ catMaybes
        [ ("myMandatoryField" .=) <$> pure manf
        , ("myOptionalField" .=) <$> optf ]
like image 69
Mike Craig Avatar answered Oct 31 '25 01:10

Mike Craig


The behavior you're looking for is available in Aeson HEAD by using the new deriveJSON :: Options -> Name -> Q [Dec] TH generator. You can then set , omitNothingFields = True in an Options struct.

Not sure what the schedule for the next Hackage release is.

like image 20
J. Abrahamson Avatar answered Oct 31 '25 01:10

J. Abrahamson