aeson-via 0.1.0 → 0.1.1
raw patch · 3 files changed
+87/−7 lines, 3 filesdep +aeson-viadep +tastydep +tasty-hunitdep ~aesondep ~aeson-casingdep ~newtype-genericsPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependencies added: aeson-via, tasty, tasty-hunit
Dependency ranges changed: aeson, aeson-casing, newtype-generics, text
API changes (from Hackage documentation)
- AesonVia: instance (Control.Newtype.Generics.Newtype n, o Data.Type.Equality.~ Control.Newtype.Generics.O n, Data.Aeson.Types.FromJSON.FromJSON o) => Data.Aeson.Types.FromJSON.FromJSON (AesonVia.AesonNewtype n o)
- AesonVia: instance (Control.Newtype.Generics.Newtype n, o Data.Type.Equality.~ Control.Newtype.Generics.O n, Data.Aeson.Types.ToJSON.ToJSON o) => Data.Aeson.Types.ToJSON.ToJSON (AesonVia.AesonNewtype n o)
+ AesonVia: instance (Control.Newtype.Generics.Newtype n, o GHC.Types.~ Control.Newtype.Generics.O n, Data.Aeson.Types.FromJSON.FromJSON o) => Data.Aeson.Types.FromJSON.FromJSON (AesonVia.AesonNewtype n o)
+ AesonVia: instance (Control.Newtype.Generics.Newtype n, o GHC.Types.~ Control.Newtype.Generics.O n, Data.Aeson.Types.ToJSON.ToJSON o) => Data.Aeson.Types.ToJSON.ToJSON (AesonVia.AesonNewtype n o)
Files
- aeson-via.cabal +25/−6
- src/AesonVia.hs +11/−1
- test/Main.hs +51/−0
aeson-via.cabal view
@@ -4,10 +4,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: 60d4229649edb0ef7f1268f79df05c7fa9d34be348ef1a2d860902ccdc143242+-- hash: 318cc7b2c6d83b710b0f777d76624040eeabdc8b6851a92937edba2804dcffde name: aeson-via-version: 0.1.0+version: 0.1.1 synopsis: Wrappers to derive-via Aeson ToJSON/FromJSON typeclasses description: Please see the README on GitHub at <https://github.com/ejconlon/aeson-via#readme> category: Data@@ -35,9 +35,28 @@ src ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wpartial-fields -Wredundant-constraints -fno-warn-unused-top-binds build-depends:- aeson >=1.4- , aeson-casing >=0.2+ aeson >=1.5 && <1.6+ , aeson-casing >=0.2 && <0.3 , base >=4.12 && <5- , newtype-generics >=0.5- , text >=1.2+ , newtype-generics >=0.5 && <0.6+ , text >=1.2 && <1.3+ default-language: Haskell2010++test-suite aeson-via-test+ type: exitcode-stdio-1.0+ main-is: Main.hs+ other-modules:+ Paths_aeson_via+ hs-source-dirs:+ test+ ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wpartial-fields -Wredundant-constraints -fno-warn-unused-top-binds -threaded -rtsopts -with-rtsopts=-N+ build-depends:+ aeson >=1.5 && <1.6+ , aeson-casing >=0.2 && <0.3+ , aeson-via+ , base >=4.12 && <5+ , newtype-generics >=0.5 && <0.6+ , tasty >=1.2 && <1.3+ , tasty-hunit >=0.10 && <0.11+ , text >=1.2 && <1.3 default-language: Haskell2010
src/AesonVia.hs view
@@ -2,6 +2,8 @@ {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE UndecidableInstances #-} +-- | Wrappers to control generic 'ToJSON' and 'FromJSON' derivation with deriving-via.+-- See the test for example definitions and their encoding. module AesonVia ( AesonRecord (..) , AesonNewtype (..)@@ -11,7 +13,8 @@ ) where import Control.Newtype.Generics (Newtype, O, pack, unpack)-import Data.Aeson+import Data.Aeson (FromJSON (..), GFromJSON, GToEncoding, GToJSON, Options (..), ToJSON (..), Zero, defaultOptions,+ genericParseJSON, genericToEncoding, genericToJSON) import Data.Aeson.Casing (aesonPrefix, snakeCase) import Data.Proxy (Proxy (..)) import Data.Text (Text)@@ -39,14 +42,19 @@ -- Has classes +-- | Mostly an internal class directing constructor/field conversion. class HasJSONOptions a where getJSONOptions :: Proxy a -> Options +-- | Used with 'AesonTag' to define a prefix to be removed from a 'Bounded' 'Enum'.+-- For example, `data Foo = FooBar | FooBaz` would use the prefix `Foo` to yield converted string+-- values `bar` and `baz`. class HasTagPrefix a where getTagPrefix :: Proxy a -> Text -- Wrappers +-- | Generic deriving ToJSON/FromJSON via this uses 'HasTagPrefix' to turn 'Bounded' 'Enum' datatypes into enumerated strings. newtype AesonTag a = AesonTag { unAesonTag :: a } instance HasTagPrefix a => HasJSONOptions (AesonTag a) where@@ -59,6 +67,7 @@ instance (HasJSONOptions (AesonTag a), Generic a, GFromJSON Zero (Rep a)) => FromJSON (AesonTag a) where parseJSON = (AesonTag <$>) . genericParseJSON (getJSONOptions (Proxy :: Proxy (AesonTag a))) +-- | Generic deriving ToJSON/FromJSON via this removes the common field name prefix in the encoding. newtype AesonRecord a = AesonRecord { unAesonRecord :: a } instance HasJSONOptions (AesonRecord a) where@@ -71,6 +80,7 @@ instance (HasJSONOptions (AesonRecord a), Generic a, GFromJSON Zero (Rep a)) => FromJSON (AesonRecord a) where parseJSON = (AesonRecord <$>) . genericParseJSON (getJSONOptions (Proxy :: Proxy (AesonRecord a))) +-- | Generic deriving ToJSON/FromJSON via this yields an encoding equivalent to the wrapped type. newtype AesonNewtype n o = AesonNewtype { unAesonNewtype :: n } instance HasJSONOptions (AesonNewtype n o) where
+ test/Main.hs view
@@ -0,0 +1,51 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DerivingVia #-}+{-# LANGUAGE OverloadedStrings #-}++module Main+ ( main+ ) where++import AesonVia (AesonNewtype (..), AesonRecord (..), AesonTag (..), HasTagPrefix (..))+import Control.Newtype.Generics (Newtype)+import Data.Aeson (FromJSON, ToJSON, encode)+import qualified Data.Text.Lazy.Encoding as TLE+import GHC.Generics (Generic)+import Test.Tasty (TestTree, defaultMain, testGroup)+import Test.Tasty.HUnit (testCase, (@?=))++newtype Author = Author+ { _authorWrapped :: String+ } deriving (Eq, Show, Generic)+ deriving (ToJSON, FromJSON) via (AesonNewtype Author String)++instance Newtype Author++data Category =+ CategoryNews+ | CategoryOpinion+ | CategoryLies+ deriving (Eq, Show, Generic)+ deriving (ToJSON, FromJSON) via (AesonTag Category)++instance HasTagPrefix Category where+ getTagPrefix _ = "Category"++data Article = Article+ { _articleTitle :: String+ , _articleAuthor :: Author+ , _articleCategory :: Category+ } deriving (Eq, Show, Generic)+ deriving (ToJSON, FromJSON) via (AesonRecord Article)++testSomething :: TestTree+testSomething = testCase "something" $ do+ let article = Article "Throw the ball!" (Author "Dog") CategoryOpinion+ expected = "{\"title\":\"Throw the ball!\",\"author\":\"Dog\",\"category\":\"opinion\"}"+ actual = TLE.decodeUtf8 (encode article)+ actual @?= expected++main :: IO ()+main = defaultMain $ testGroup "AesonVia"+ [ testSomething+ ]