Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

Already on GitHub? Sign in to your account

Add (.=?) :: Text -> Maybe a -> Pair constructor? #87

Closed
treep opened this Issue Aug 27, 2012 · 5 comments

Comments

Projects
None yet
4 participants

treep commented Aug 27, 2012

This constructor should allow to use key-value pairs in the object's list that will generate JSON only if the value satisfies certain conditions (e.g. it is Nothing).

Here is an example:

{-# LANGUAGE OverloadedStrings #-}

import Data.Aeson

data Foo = Foo
  { a :: Maybe Int
  , b :: Maybe Int
  }

instance ToJSON Foo where
  toJSON (Foo a b) = object [ "a" .= a, "b" .=? b ]
*Main> encode $ Foo (Just 1) (Just 2)
Chunk "{\"a\":1,\"b\":2}" Empty
*Main> encode $ Foo (Just 1) Nothing
Chunk "{\"a\":1}" Empty
*Main> encode $ Foo Nothing Nothing
Chunk "{\"a\":null}" Empty

And one possible implementation:

diff --git a/Data/Aeson.hs b/Data/Aeson.hs
index d3db4c4..c288b93 100644
--- a/Data/Aeson.hs
+++ b/Data/Aeson.hs
@@ -30,6 +30,7 @@ module Data.Aeson
     , ToJSON(..)
     -- * Constructors and accessors
     , (.=)
+    , (.=?)
     , (.:)
     , (.:?)
     , (.!=)
diff --git a/Data/Aeson/Generic.hs b/Data/Aeson/Generic.hs
index 0c312b0..4349bc3 100644
--- a/Data/Aeson/Generic.hs
+++ b/Data/Aeson/Generic.hs
@@ -170,14 +170,14 @@ toJSON_generic = generic
         -- Use an array if the are no field names, but elide singleton arrays,
         -- and use an object if there are field names.
         encodeConstr c [] = String . constrString $ c
-        encodeConstr c as = object [(constrString c, encodeArgs c as)]
+        encodeConstr c as = object [Just (constrString c, encodeArgs c as)]

         constrString = pack . showConstr

         encodeArgs c = encodeArgs' (constrFields c)
         encodeArgs' [] [j] = j
         encodeArgs' [] js  = Array . V.fromList $ js
-        encodeArgs' ns js  = object $ zip (map pack ns) js
+        encodeArgs' ns js  = object $ map Just $ zip (map pack ns) js


 fromJSON :: (Data a) => Value -> Result a
diff --git a/Data/Aeson/Types.hs b/Data/Aeson/Types.hs
index 178a69f..1a1b502 100644
--- a/Data/Aeson/Types.hs
+++ b/Data/Aeson/Types.hs
@@ -34,6 +34,7 @@ module Data.Aeson.Types
     , ToJSON(..)
     -- * Constructors and accessors
     , (.=)
+    , (.=?)
     , (.:)
     , (.:?)
     , (.!=)
diff --git a/Data/Aeson/Types/Class.hs b/Data/Aeson/Types/Class.hs
index 8ae9704..fb38c22 100644
--- a/Data/Aeson/Types/Class.hs
+++ b/Data/Aeson/Types/Class.hs
@@ -1,6 +1,7 @@
 {-# LANGUAGE CPP, DeriveDataTypeable, FlexibleContexts, FlexibleInstances,
     GeneralizedNewtypeDeriving, IncoherentInstances, OverlappingInstances,
-    OverloadedStrings, UndecidableInstances, ViewPatterns #-}
+    OverloadedStrings, UndecidableInstances, ViewPatterns, MultiParamTypeClasses,
+    FunctionalDependencies #-}

 #ifdef GENERICS
 {-# LANGUAGE DefaultSignatures #-}
@@ -36,6 +37,7 @@ module Data.Aeson.Types.Class
     , (.:?)
     , (.!=)
     , (.=)
+    , (.=?)
     , typeMismatch
     ) where

@@ -711,11 +713,21 @@ instance FromJSON a => FromJSON (Last a) where
     parseJSON = fmap Last . parseJSON
     {-# INLINE parseJSON #-}

+class ToMaybe f a | f -> a where
+  toMaybe :: f -> Maybe a
+
+instance ToMaybe (Maybe a) a where
+  toMaybe = id
+
 -- | Construct a 'Pair' from a key and a value.
 (.=) :: ToJSON a => Text -> a -> Pair
-name .= value = (name, toJSON value)
+name .= value = Just (name, toJSON value)
 {-# INLINE (.=) #-}

+(.=?) :: (ToJSON a, ToMaybe f a) => Text -> f -> Pair
+name .=? value = maybe Nothing (name .=) $ toMaybe value
+{-# INLINE (.=?) #-}
+
 -- | Convert a value from JSON, failing if the types do not match.
 fromJSON :: (FromJSON a) => Value -> Result a
 fromJSON = parse parseJSON
diff --git a/Data/Aeson/Types/Internal.hs b/Data/Aeson/Types/Internal.hs
index 974e1b5..f31aef3 100644
--- a/Data/Aeson/Types/Internal.hs
+++ b/Data/Aeson/Types/Internal.hs
@@ -43,6 +43,7 @@ import Data.Typeable (Typeable)
 import Data.Vector (Vector)
 import qualified Data.HashMap.Strict as H
 import qualified Data.Vector as V
+import Data.Maybe (catMaybes)

 -- | The result of running a 'Parser'.
 data Result a = Error String
@@ -213,11 +214,11 @@ parseEither :: (a -> Parser b) -> a -> Either String b
 parseEither m v = runParser (m v) Left Right

 -- | A key\/value pair for an 'Object'.
-type Pair = (Text, Value)
+type Pair = Maybe (Text, Value)

 {-# INLINE parseEither #-}
 -- | Create a 'Value' from a list of name\/value 'Pair's.  If duplicate
 -- keys arise, earlier keys and their associated values win.
 object :: [Pair] -> Value
-object = Object . H.fromList
+object = Object . H.fromList . catMaybes
 {-# INLINE object #-}

It should work for each instance of ToMaybe.

nice feature

Will be nice to have.

All haskellers from LOR likes this feature ;)

Owner

bos commented Oct 12, 2012

I am filled with regret for ever having introduced these operators, because now everyone who uses aeson wants a new operator or four :-(

So my default response to further requests like this is "no thanks". Sorry.

@bos bos closed this Oct 12, 2012

treep commented Oct 13, 2012

                Strict          Optional

ToJSON          (.=)            ✗

FromJSON        (.:)            (.:?)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment