Skip to content

Commit 447a9c9

Browse files
authored
Merge pull request #951 from haskell/generically
Add Generically instances
2 parents db6c23e + 3ce5a56 commit 447a9c9

File tree

6 files changed

+64
-1
lines changed

6 files changed

+64
-1
lines changed

aeson.cabal

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ library
107107
-- Compat
108108
build-depends:
109109
base-compat-batteries >=0.10.0 && <0.13
110+
, generically >=0.1 && <0.2
110111
, time-compat >=1.9.6 && <1.10
111112

112113
if !impl(ghc >=8.6)
@@ -194,6 +195,7 @@ test-suite aeson-tests
194195
, dlist
195196
, filepath
196197
, generic-deriving >=1.10 && <1.15
198+
, generically
197199
, ghc-prim >=0.2
198200
, hashable
199201
, indexed-traversable

benchmarks/aeson-benchmarks.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ library
3131
, data-fix
3232
, deepseq
3333
, dlist
34+
, generically
3435
, ghc-prim
3536
, hashable
3637
, indexed-traversable

changelog.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ For the latest version of this document, please see [https://github.com/haskell/
88
- Export `mapWithKey` from `Data.Aeson.KeyMap` module.
99
- Export `ifromJSON` and `iparse` from `Data.Aeson.Types`. Add `iparseEither`.
1010
- Add `MonadFix Parser` instance.
11+
- Make `Semigroup Series` slightly lazier
12+
- Add instances for `Generically` type
1113

1214
### 2.0.3.0
1315

src/Data/Aeson/Types/FromJSON.hs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,9 @@ import Data.Word (Word16, Word32, Word64, Word8)
118118
import Foreign.Storable (Storable)
119119
import Foreign.C.Types (CTime (..))
120120
import GHC.Generics
121+
#if !MIN_VERSION_base(4,17,0)
122+
import GHC.Generics.Generically (Generically (..), Generically1 (..))
123+
#endif
121124
import Numeric.Natural (Natural)
122125
import Text.ParserCombinators.ReadP (readP_to_S)
123126
import Unsafe.Coerce (unsafeCoerce)
@@ -362,6 +365,12 @@ genericLiftParseJSON opts pj pjl = fmap to1 . gParseJSON opts (From1Args pj pjl)
362365
-- instance 'FromJSON' Coord
363366
-- @
364367
--
368+
-- or using the [DerivingVia extension](https://downloads.haskell.org/ghc/9.2.3/docs/html/users_guide/exts/deriving_via.html)
369+
--
370+
-- @
371+
-- deriving via 'Generically' Coord instance 'FromJSON' Coord
372+
-- @
373+
--
365374
-- The default implementation will be equivalent to
366375
-- @parseJSON = 'genericParseJSON' 'defaultOptions'@; if you need different
367376
-- options, you can customize the generic decoding by defining:
@@ -386,6 +395,10 @@ class FromJSON a where
386395
. V.toList
387396
$ a
388397

398+
-- | @since 2.1.0.0
399+
instance (Generic a, GFromJSON Zero (Rep a)) => FromJSON (Generically a) where
400+
parseJSON = coerce (genericParseJSON defaultOptions :: Value -> Parser a)
401+
389402
-------------------------------------------------------------------------------
390403
-- Classes and types for map keys
391404
-------------------------------------------------------------------------------
@@ -575,6 +588,12 @@ typeOf v = case v of
575588
-- instance 'FromJSON' a => 'FromJSON1' (Pair a)
576589
-- @
577590
--
591+
-- or
592+
--
593+
-- @
594+
-- deriving via 'Generically1' (Pair a) instance 'FromJSON1' (Pair a)
595+
-- @
596+
--
578597
-- If the default implementation doesn't give exactly the results you want,
579598
-- you can customize the generic decoding with only a tiny amount of
580599
-- effort, using 'genericLiftParseJSON' with your preferred 'Options':
@@ -597,6 +616,11 @@ class FromJSON1 f where
597616
liftParseJSONList :: (Value -> Parser a) -> (Value -> Parser [a]) -> Value -> Parser [f a]
598617
liftParseJSONList f g v = listParser (liftParseJSON f g) v
599618

619+
-- | @since 2.1.0.0
620+
instance (Generic1 f, GFromJSON One (Rep1 f)) => FromJSON1 (Generically1 f) where
621+
liftParseJSON :: forall a. (Value -> Parser a) -> (Value -> Parser [a]) -> Value -> Parser (Generically1 f a)
622+
liftParseJSON = coerce (genericLiftParseJSON defaultOptions :: (Value -> Parser a) -> (Value -> Parser [a]) -> Value -> Parser (f a))
623+
600624
-- | Lift the standard 'parseJSON' function through the type constructor.
601625
parseJSON1 :: (FromJSON1 f, FromJSON a) => Value -> Parser (f a)
602626
parseJSON1 = liftParseJSON parseJSON parseJSONList

src/Data/Aeson/Types/ToJSON.hs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
{-# LANGUAGE FlexibleContexts #-}
55
{-# LANGUAGE FlexibleInstances #-}
66
{-# LANGUAGE FunctionalDependencies #-}
7+
{-# LANGUAGE InstanceSigs #-}
78
{-# LANGUAGE GADTs #-}
89
{-# LANGUAGE NoImplicitPrelude #-}
910
{-# LANGUAGE OverloadedStrings #-}
@@ -68,6 +69,7 @@ import qualified Data.Aeson.Key as Key
6869
import qualified Data.Aeson.KeyMap as KM
6970
import Data.Attoparsec.Number (Number(..))
7071
import Data.Bits (unsafeShiftR)
72+
import Data.Coerce (coerce)
7173
import Data.DList (DList)
7274
import Data.Fixed (Fixed, HasResolution, Nano)
7375
import Data.Foldable (toList)
@@ -101,6 +103,9 @@ import Data.Word (Word16, Word32, Word64, Word8)
101103
import Foreign.Storable (Storable)
102104
import Foreign.C.Types (CTime (..))
103105
import GHC.Generics
106+
#if !MIN_VERSION_base(4,17,0)
107+
import GHC.Generics.Generically (Generically (..), Generically1 (..))
108+
#endif
104109
import Numeric.Natural (Natural)
105110
import qualified Data.Aeson.Encoding as E
106111
import qualified Data.Aeson.Encoding.Internal as E (InArray, comma, econcat, retagEncoding, key)
@@ -256,6 +261,12 @@ genericLiftToEncoding opts te tel = gToJSON opts (To1Args te tel) . from1
256261
-- 'toEncoding' = 'genericToEncoding' 'defaultOptions'
257262
-- @
258263
--
264+
-- or more conveniently using the [DerivingVia extension](https://downloads.haskell.org/ghc/9.2.3/docs/html/users_guide/exts/deriving_via.html)
265+
--
266+
-- @
267+
-- deriving via 'Generically' Coord instance 'ToJSON' Coord
268+
-- @
269+
--
259270
-- If on the other hand you wish to customize the generic decoding, you have
260271
-- to implement both methods:
261272
--
@@ -272,7 +283,7 @@ genericLiftToEncoding opts te tel = gToJSON opts (To1Args te tel) . from1
272283
-- Previous versions of this library only had the 'toJSON' method. Adding
273284
-- 'toEncoding' had two reasons:
274285
--
275-
-- 1. toEncoding is more efficient for the common case that the output of
286+
-- 1. 'toEncoding' is more efficient for the common case that the output of
276287
-- 'toJSON' is directly serialized to a @ByteString@.
277288
-- Further, expressing either method in terms of the other would be
278289
-- non-optimal.
@@ -321,6 +332,11 @@ class ToJSON a where
321332
toEncodingList :: [a] -> Encoding
322333
toEncodingList = listEncoding toEncoding
323334

335+
-- | @since 2.1.0.0
336+
instance (Generic a, GToJSON' Value Zero (Rep a), GToJSON' Encoding Zero (Rep a)) => ToJSON (Generically a) where
337+
toJSON = coerce (genericToJSON defaultOptions :: a -> Value)
338+
toEncoding = coerce (genericToEncoding defaultOptions :: a -> Encoding)
339+
324340
-------------------------------------------------------------------------------
325341
-- Object key-value pairs
326342
-------------------------------------------------------------------------------
@@ -615,6 +631,14 @@ class ToJSON1 f where
615631
liftToEncodingList :: (a -> Encoding) -> ([a] -> Encoding) -> [f a] -> Encoding
616632
liftToEncodingList f g = listEncoding (liftToEncoding f g)
617633

634+
-- | @since 2.1.0.0
635+
instance (Generic1 f, GToJSON' Value One (Rep1 f), GToJSON' Encoding One (Rep1 f)) => ToJSON1 (Generically1 f) where
636+
liftToJSON :: forall a. (a -> Value) -> ([a] -> Value) -> Generically1 f a -> Value
637+
liftToJSON = coerce (genericLiftToJSON defaultOptions :: (a -> Value) -> ([a] -> Value) -> f a -> Value)
638+
639+
liftToEncoding :: forall a. (a -> Encoding) -> ([a] -> Encoding) -> Generically1 f a -> Encoding
640+
liftToEncoding = coerce (genericLiftToEncoding defaultOptions :: (a -> Encoding) -> ([a] -> Encoding) -> f a -> Encoding)
641+
618642
-- | Lift the standard 'toJSON' function through the type constructor.
619643
toJSON1 :: (ToJSON1 f, ToJSON a) => f a -> Value
620644
toJSON1 = liftToJSON toJSON toJSONList

tests/UnitTests.hs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@
99
{-# LANGUAGE QuasiQuotes #-}
1010
{-# LANGUAGE RecursiveDo #-}
1111

12+
#if __GLASGOW_HASKELL__ >= 806
13+
{-# LANGUAGE DerivingVia #-}
14+
{-# LANGUAGE StandaloneDeriving #-}
15+
#endif
16+
1217
-- For Data.Aeson.Types.camelTo
1318
{-# OPTIONS_GHC -fno-warn-deprecations #-}
1419

@@ -55,6 +60,7 @@ import Data.Text (Text)
5560
import Data.Time (UTCTime)
5661
import Data.Time.Format.Compat (parseTimeM, defaultTimeLocale)
5762
import GHC.Generics (Generic)
63+
import GHC.Generics.Generically (Generically (..))
5864
import Instances ()
5965
import Numeric.Natural (Natural)
6066
import System.Directory (getDirectoryContents)
@@ -97,9 +103,13 @@ data Wibble = Wibble {
97103

98104
instance FromJSON Wibble
99105

106+
#if __GLASGOW_HASKELL__ >= 806
107+
deriving via Generically Wibble instance ToJSON Wibble
108+
#else
100109
instance ToJSON Wibble where
101110
toJSON = genericToJSON defaultOptions
102111
toEncoding = genericToEncoding defaultOptions
112+
#endif
103113

104114
-- Test that if we put a bomb in a data structure, but only demand
105115
-- part of it via lazy encoding, we do not unexpectedly fail.

0 commit comments

Comments
 (0)