8

I have a type

{-# LANGUAGE DeriveGeneric   #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE MultiWayIf      #-}

import GHC.Generics
import Data.Aeson.TH
import Data.Aeson.Types

data MyJSONObject = MyJSONObject
  { name    :: String
  , ptype   :: Maybe String
  , pid     :: Maybe String
  , subject :: Maybe String
  , message :: Maybe String
  } deriving (Show, Generic)

with a great many more Maybe String fields. My FromJSON and ToJSON are provided by the TemplateHaskell function

$(deriveJSON defaultOptions
  {
    omitNothingFields  = True
  , fieldLabelModifier = \f -> if
      | f == "ptype" -> "type" -- reserved keyword
      | f == "pid"   -> "id"   -- Prelude function name
      | otherwise    -> f
  } ''MyJSONObject)

Ultimately, the output of the program is a JSON document intended to be consumed by an application that does not permit some fields to have null values, even if it does allow these fields to not exist. In other words, it's perfectly fine for subject to not be present in the JSON document, but if it does exist, its value cannot be null. My expectation was that omitNothingFields would handle this requirement, but this does not appear to be the case: the decoded JSON still has Nothing values for fields that are not present, and the encoded JSON has null values for those fields. The former case is fine; the latter case is not, hence the question.

Am I misusing, or misunderstanding the purpose of, omitNothingFields? How can I ignore fields with Nothing/null values?

user4601931
  • 4,982
  • 5
  • 30
  • 42
  • 1
    Works for me. (`encode (MyJSONObject "foo" Nothing Nothing Nothing Nothing)` gives `"{\"name\":\"foo\"}"`.) What version of aeson are you using? – Daniel Wagner Nov 03 '17 at 16:58
  • @DanielWagner I'm using `aeson-1.1.2.0`. My problem turned out to be not implementing `FromJSON/ToJSON` for `data MyOtherJSONObject = MyOtherJSONObject { objects :: [MyJSONObject] } deriving (Show, Generic)` (which is the data I _really_ wanted to `decode`/`encode`) with the `omitNothingFields` option. Why I needed it there, too, is beyond me. I thought my issue was with the `MyJSONObject` instances. – user4601931 Nov 03 '17 at 18:36

1 Answers1

14

Works for me. Try using Generics deriving instead of TH. Maybe that's doing it.

*Main Data.Aeson> decode "{\"name\":\"str\"}" :: Maybe MyJSONObject
Just (MyJSONObject {name = "str", ptype = Nothing, pid = Nothing, subject = Nothing, message = Nothing})
*Main Data.Aeson> encode (MyJSONObject "str" Nothing Nothing Nothing Nothing)
"{\"name\":\"str\"}"

The full code is

{-# LANGUAGE DeriveGeneric   #-}
import GHC.Generics
import Data.Aeson
import Data.Aeson.Types

data MyJSONObject = MyJSONObject
  { name    :: String
  , ptype   :: Maybe String
  , pid     :: Maybe String
  , subject :: Maybe String
  , message :: Maybe String
  } deriving (Show, Generic)

instance ToJSON MyJSONObject where
  toJSON = genericToJSON defaultOptions
    { omitNothingFields = True }

instance FromJSON MyJSONObject where
  parseJSON = genericParseJSON defaultOptions
    { omitNothingFields = True }

Using GHC 8.2.1, aeson-1.1.2.0.

Mateusz Kowalczyk
  • 2,036
  • 1
  • 15
  • 29
  • Thanks for your answer. To be more precise, I later define `data MyOtherJSONObject = MyOtherJSONObject { objects :: [MyJSONObject] } deriving (Show, Generic)` and it's this object I want to omit Nothing/null from. Evidently I needed to put the `omitNothingFields` option in that instance, too. Why I needed to do that, I'm not so sure. It's still the case that `decode` returns a bunch of Nothing values, but like I said, that's less important to me. Thanks again. – user4601931 Nov 03 '17 at 18:34