by-other-names 1.2.1.0 → 1.2.2.0
raw patch · 5 files changed
+205/−51 lines, 5 filesPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
API changes (from Hackage documentation)
- ByOtherNames.Aeson: instance (ByOtherNames.Internal.Aliased 'ByOtherNames.Aeson.JSON r, ByOtherNames.Internal.GSum ByOtherNames.Aeson.Impossible (GHC.Generics.Rep r)) => Data.Aeson.Types.ToJSON.ToJSON (ByOtherNames.Aeson.JSONEnum r)
- ByOtherNames.Aeson: instance (ByOtherNames.Internal.Aliased 'ByOtherNames.Aeson.JSON r, ByOtherNames.Internal.GSum Data.Aeson.Types.FromJSON.FromJSON (GHC.Generics.Rep r)) => Data.Aeson.Types.FromJSON.FromJSON (ByOtherNames.Aeson.JSONEnum r)
- ByOtherNames.Aeson: instance (x GHC.Types.~ Data.Void.Void) => ByOtherNames.Aeson.Impossible x
+ ByOtherNames.Aeson: GeneralJSONEnum :: r -> GeneralJSONEnum rubric r
+ ByOtherNames.Aeson: GeneralJSONRecord :: r -> GeneralJSONRecord rubric objectName r
+ ByOtherNames.Aeson: GeneralJSONSum :: r -> GeneralJSONSum rubric objectName r
+ ByOtherNames.Aeson: instance (ByOtherNames.Internal.Aliased 'ByOtherNames.Aeson.JSON r, ByOtherNames.Internal.GSum ByOtherNames.Constraint.Impossible (GHC.Generics.Rep r)) => Data.Aeson.Types.FromJSON.FromJSON (ByOtherNames.Aeson.JSONEnum r)
+ ByOtherNames.Aeson: instance (ByOtherNames.Internal.Aliased 'ByOtherNames.Aeson.JSON r, ByOtherNames.Internal.GSum ByOtherNames.Constraint.Impossible (GHC.Generics.Rep r)) => Data.Aeson.Types.ToJSON.ToJSON (ByOtherNames.Aeson.JSONEnum r)
+ ByOtherNames.Aeson: instance forall rubric1 (objectName :: GHC.Types.Symbol) (rubric2 :: rubric1) r. (GHC.TypeLits.KnownSymbol objectName, ByOtherNames.Internal.Rubric rubric2, ByOtherNames.Internal.Aliased rubric2 r, ByOtherNames.Internal.AliasType rubric2 GHC.Types.~ Data.Aeson.Key.Key, ByOtherNames.Internal.GRecord Data.Aeson.Types.FromJSON.FromJSON (GHC.Generics.Rep r)) => Data.Aeson.Types.FromJSON.FromJSON (ByOtherNames.Aeson.GeneralJSONRecord rubric2 objectName r)
+ ByOtherNames.Aeson: instance forall rubric1 (objectName :: GHC.Types.Symbol) (rubric2 :: rubric1) r. (GHC.TypeLits.KnownSymbol objectName, ByOtherNames.Internal.Rubric rubric2, ByOtherNames.Internal.Aliased rubric2 r, ByOtherNames.Internal.AliasType rubric2 GHC.Types.~ Data.Aeson.Key.Key, ByOtherNames.Internal.GSum Data.Aeson.Types.FromJSON.FromJSON (GHC.Generics.Rep r)) => Data.Aeson.Types.FromJSON.FromJSON (ByOtherNames.Aeson.GeneralJSONSum rubric2 objectName r)
+ ByOtherNames.Aeson: instance forall rubric1 (rubric2 :: rubric1) r (objectName :: GHC.Types.Symbol). (ByOtherNames.Internal.Rubric rubric2, ByOtherNames.Internal.AliasType rubric2 GHC.Types.~ Data.Aeson.Key.Key, ByOtherNames.Internal.Aliased rubric2 r, ByOtherNames.Internal.GRecord Data.Aeson.Types.ToJSON.ToJSON (GHC.Generics.Rep r)) => Data.Aeson.Types.ToJSON.ToJSON (ByOtherNames.Aeson.GeneralJSONRecord rubric2 objectName r)
+ ByOtherNames.Aeson: instance forall rubric1 (rubric2 :: rubric1) r (objectName :: GHC.Types.Symbol). (ByOtherNames.Internal.Rubric rubric2, ByOtherNames.Internal.Aliased rubric2 r, ByOtherNames.Internal.AliasType rubric2 GHC.Types.~ Data.Aeson.Key.Key, ByOtherNames.Internal.GSum Data.Aeson.Types.ToJSON.ToJSON (GHC.Generics.Rep r)) => Data.Aeson.Types.ToJSON.ToJSON (ByOtherNames.Aeson.GeneralJSONSum rubric2 objectName r)
+ ByOtherNames.Aeson: instance forall rubric1 (rubric2 :: rubric1) r. (ByOtherNames.Internal.Rubric rubric2, ByOtherNames.Internal.AliasType rubric2 GHC.Types.~ Data.Aeson.Key.Key, ByOtherNames.Internal.Aliased rubric2 r, ByOtherNames.Internal.GSum ByOtherNames.Constraint.Impossible (GHC.Generics.Rep r)) => Data.Aeson.Types.FromJSON.FromJSON (ByOtherNames.Aeson.GeneralJSONEnum rubric2 r)
+ ByOtherNames.Aeson: instance forall rubric1 (rubric2 :: rubric1) r. (ByOtherNames.Internal.Rubric rubric2, ByOtherNames.Internal.AliasType rubric2 GHC.Types.~ Data.Aeson.Key.Key, ByOtherNames.Internal.Aliased rubric2 r, ByOtherNames.Internal.GSum ByOtherNames.Constraint.Impossible (GHC.Generics.Rep r)) => Data.Aeson.Types.ToJSON.ToJSON (ByOtherNames.Aeson.GeneralJSONEnum rubric2 r)
+ ByOtherNames.Aeson: newtype GeneralJSONEnum rubric r
+ ByOtherNames.Aeson: newtype GeneralJSONRecord rubric objectName r
+ ByOtherNames.Aeson: newtype GeneralJSONSum rubric objectName r
+ ByOtherNames.Constraint: class (x ~ Void) => Impossible x
+ ByOtherNames.Constraint: instance (x GHC.Types.~ Data.Void.Void) => ByOtherNames.Constraint.Impossible x
Files
- CHANGELOG.md +13/−1
- by-other-names.cabal +1/−1
- lib/ByOtherNames/Aeson.hs +181/−48
- lib/ByOtherNames/Constraint.hs +9/−1
- tests/tests.hs +1/−0
CHANGELOG.md view
@@ -1,4 +1,16 @@-+1.2.2.0+=======++- GeneralJSONEnum : like JSONEnum, but lets you define FromJSON/ToJSON instances+ for a newtype that use the generic representation of the wrapped type,+ *without* requiring FromJSON/ToJSON instances from the wrapped type. So it's+ different from GeneralizedNewtypeDeriving: only the generic rep of the wrapped+ type is used!++ JSONEnum instances implemented in terms of GeneralJSONEnum.++- GeneralJSONRecord and GeneralJSONSum.+ 1.2.1.0 =======
by-other-names.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.0 name: by-other-names-version: 1.2.1.0+version: 1.2.2.0 synopsis: Give aliases to record fields. description: Give aliases to record fields.
lib/ByOtherNames/Aeson.hs view
@@ -1,11 +1,15 @@ {-# LANGUAGE AllowAmbiguousTypes #-} {-# LANGUAGE ApplicativeDo #-} {-# LANGUAGE BlockArguments #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE FunctionalDependencies #-}+{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE DerivingVia #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE PolyKinds #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE StandaloneKindSignatures #-}@@ -61,7 +65,7 @@ -- $ aliasListEnd -- :} ----- Some limitations:+-- Some observations: -- -- - Fields in branches of sum types can't have selectors. When there is more than one field in a branch, they are parsed as a JSON Array. --@@ -95,7 +99,10 @@ JSONRecord (..), JSONSum (..), JSONEnum (..),-+ -- ** Advanced JSON helpers+ GeneralJSONRecord (..),+ GeneralJSONSum (..),+ GeneralJSONEnum (..), -- * Re-exports from ByOtherNames Aliased (aliases), aliasListBegin,@@ -119,6 +126,7 @@ import Data.Void import GHC.Generics import GHC.TypeLits+import ByOtherNames.Constraint -- | Aliases for JSON serialization fall under this 'Rubric'. -- The constructor 'JSON' is used as a type, with DataKinds.@@ -135,6 +143,9 @@ type JSONRecord :: Symbol -> Type -> Type newtype JSONRecord objectName r = JSONRecord r +deriving via (GeneralJSONRecord 'JSON objectName r) instance (KnownSymbol objectName, Aliased 'JSON r, GRecord FromJSON (Rep r)) => FromJSON (JSONRecord objectName r) +deriving via (GeneralJSONRecord 'JSON objectName r) instance (Aliased 'JSON r, GRecord ToJSON (Rep r)) => ToJSON (JSONRecord objectName r)+ -- | Helper newtype for deriving 'FromJSON' and 'ToJSON' for sum types, -- using DerivingVia. --@@ -142,6 +153,9 @@ type JSONSum :: Symbol -> Type -> Type newtype JSONSum objectName r = JSONSum r +deriving via (GeneralJSONSum 'JSON objectName r) instance (KnownSymbol objectName, Aliased 'JSON r, GSum FromJSON (Rep r)) => FromJSON (JSONSum objectName r) +deriving via (GeneralJSONSum 'JSON objectName r) instance (Aliased 'JSON r, GSum ToJSON (Rep r)) => ToJSON (JSONSum objectName r)+ -- | Helper newtype for deriving 'FromJSON' and 'ToJSON' for enum-like sum types, -- using DerivingVia. --@@ -149,36 +163,12 @@ type JSONEnum :: Type -> Type newtype JSONEnum r = JSONEnum r -------instance (Aliased JSON r, GSum FromJSON (Rep r)) => FromJSON (JSONEnum r) where- parseJSON v =- let parsers =- gToSum @FromJSON- (aliases @JSONRubric @JSON @r)- ( \a -> \case- ZeroSlots x -> EnumBranchParser \case- String a' | a == fromText a' -> pure x- _ -> mempty- SingleSlot _ -> EnumBranchParser mempty- ManySlots _ -> EnumBranchParser mempty- )- Proxy- Proxy- parserForValue v = asum $ fmap (($ v) . runEnumBranchParser) parsers- in JSONEnum . to <$> parserForValue v+deriving via (GeneralJSONEnum 'JSON r) instance (Aliased 'JSON r, GSum Impossible (Rep r)) => FromJSON (JSONEnum r) +deriving via (GeneralJSONEnum 'JSON r) instance (Aliased 'JSON r, GSum Impossible (Rep r)) => ToJSON (JSONEnum r) newtype EnumBranchParser v = EnumBranchParser {runEnumBranchParser :: Value -> Parser v} deriving stock (Functor) -instance (Aliased JSON r, GSum Impossible (Rep r)) => ToJSON (JSONEnum r) where- toJSON (JSONEnum o) =- let (key, slots) = gFromSum @Impossible @(Rep r) @Key @Value @Value (aliases @JSONRubric @JSON @r) absurd (from @r o)- in case slots of- [] -> String (toText key)- [_] -> error "never happens"- _ -> error "never happens"- newtype BranchParser v = BranchParser {runBranchParser :: Object -> Parser v} deriving stock (Functor) @@ -200,15 +190,52 @@ -- ---class (x ~ Void) => Impossible x -instance (x ~ Void) => Impossible x+newtype FieldParser a = FieldParser (Object -> Parser a)+ deriving (Functor, Applicative) via ((->) Object `Compose` Parser) -instance (KnownSymbol objectName, Aliased JSON r, GSum FromJSON (Rep r)) => FromJSON (JSONSum objectName r) where+-- | A more flexible version of 'JSONSum' that lets you use any 'Rubric' whose+-- 'AliasType' is 'Data.Aeson.Key'.+-- +-- It allows deriving 'FromJSON' and 'ToJSON' for a newtype, using the generic+-- 'Rep' and the aliases of the underlying type, but __without__ defining+-- 'FromJSON' and 'ToJSON' instances for the underlying type.+-- +-- >>> :{+-- data Summy+-- = Aa Int+-- | Bb Bool+-- | Cc+-- deriving (Read, Show, Eq, Generic)+-- data JSONLocal+-- -- We define a local rubric type to avoid colliding "Aliased" instances over Foo.+-- instance Rubric JSONLocal where+-- type AliasType JSONLocal = Key+-- instance Aliased JSONLocal Summy where+-- aliases =+-- aliasListBegin+-- $ alias @"Aa" "Aax"+-- $ alias @"Bb" "Bbx"+-- $ alias @"Cc" "Cc1"+-- $ aliasListEnd+-- newtype SummyN = SummyN Summy+-- deriving (FromJSON, ToJSON) via (GeneralJSONSum JSONLocal "obj" Summy)+-- :}+--+--+type GeneralJSONSum :: rubric -> Symbol -> Type -> Type+newtype GeneralJSONSum rubric objectName r = GeneralJSONSum r++instance (+ KnownSymbol objectName, + Rubric rubric, + Aliased rubric r, + AliasType rubric ~ Key, + GSum FromJSON (Rep r)) => FromJSON (GeneralJSONSum rubric objectName r) where parseJSON v = let parsers = gToSum @FromJSON- (aliases @JSONRubric @JSON @r)+ (aliases @_ @rubric @r) ( \a -> \case ZeroSlots v -> BranchParser \o -> do Null :: Value <- o .: a@@ -229,38 +256,141 @@ pure (r, vs) ) parserForObject o = asum $ fmap (($ o) . runBranchParser) parsers- in JSONSum . to <$> withObject (symbolVal (Proxy @objectName)) parserForObject v+ in GeneralJSONSum . to <$> withObject (symbolVal (Proxy @objectName)) parserForObject v ++instance (+ Rubric rubric, + Aliased rubric r, + AliasType rubric ~ Key, + GSum ToJSON (Rep r)) => ToJSON (GeneralJSONSum rubric objectName r) where+ toJSON (GeneralJSONSum o) =+ let (key, slots) = gFromSum @ToJSON @(Rep r) @Key @Value @Value (aliases @_ @rubric @r) toJSON (from @r o)+ in case slots of+ [] -> object [(key, Null)]+ [x] -> object [(key, toJSON x)]+ xs -> object [(key, toJSON xs)]+++-- | A more flexible version of 'JSONRecord' that lets you use any 'Rubric' whose+-- 'AliasType' is 'Data.Aeson.Key'.+-- +-- It allows deriving 'FromJSON' and 'ToJSON' for a newtype, using the generic+-- 'Rep' and the aliases of the underlying type, but __without__ defining+-- 'FromJSON' and 'ToJSON' instances for the underlying type.+-- +-- >>> :{+-- data Foo = Foo {aa :: Int, bb :: Bool, cc :: Char}+-- deriving (Read, Show, Eq, Generic)+-- data JSONLocal+-- -- We define a local rubric type to avoid colliding "Aliased" instances over Foo.+-- instance Rubric JSONLocal where+-- type AliasType JSONLocal = Key+-- instance Aliased JSONLocal Foo where+-- aliases =+-- aliasListBegin+-- $ alias @"aa" "aax"+-- $ alias @"bb" "bbx"+-- $ alias @"cc" "ccx"+-- $ aliasListEnd+-- newtype FooN = FooN Foo+-- deriving (FromJSON, ToJSON) via (GeneralJSONRecord JSONLocal "obj" Foo)+-- :} -- ---instance (KnownSymbol objectName, Aliased JSON r, GRecord FromJSON (Rep r)) => FromJSON (JSONRecord objectName r) where+type GeneralJSONRecord :: rubric -> Symbol -> Type -> Type+newtype GeneralJSONRecord rubric objectName r = GeneralJSONRecord r++instance (KnownSymbol objectName, + Rubric rubric, + Aliased rubric r, + AliasType rubric ~ Key, + GRecord FromJSON (Rep r)) + => FromJSON (GeneralJSONRecord rubric objectName r) where parseJSON v = let FieldParser parser = gToRecord @FromJSON- (aliases @JSONRubric @JSON @r)+ (aliases @_ @rubric @r) (\fieldName -> FieldParser (\o -> explicitParseField parseJSON o fieldName)) objectName = symbolVal (Proxy @objectName)- in JSONRecord . to <$> withObject objectName parser v+ in GeneralJSONRecord . to <$> withObject objectName parser v -newtype FieldParser a = FieldParser (Object -> Parser a)- deriving (Functor, Applicative) via ((->) Object `Compose` Parser)+instance (Rubric rubric, + AliasType rubric ~ Key, + Aliased rubric r, + GRecord ToJSON (Rep r)) => ToJSON (GeneralJSONRecord rubric objectName r) where+ toJSON (GeneralJSONRecord o) =+ object $ Data.Foldable.toList $ gFromRecord @ToJSON @(Rep r) @Key (aliases @_ @rubric @r) (\a v -> (a, toJSON v)) (from @r o) +-- | A more flexible version of 'JSONEnum' that lets you use any 'Rubric' whose+-- 'AliasType' is 'Data.Aeson.Key'.+-- +-- It allows deriving 'FromJSON' and 'ToJSON' for a newtype, using the generic+-- 'Rep' and the aliases of the underlying type, but __without__ defining+-- 'FromJSON' and 'ToJSON' instances for the underlying type.+-- +-- >>> :{+-- data Enumy+-- = Xx+-- | Yy+-- | Zz+-- deriving (Read, Show, Eq, Generic)+-- data JSONLocal+-- -- We define a local rubric type to avoid colliding "Aliased" instances over Enumy.+-- instance Rubric JSONLocal where+-- type AliasType JSONLocal = Key+-- instance Aliased JSONLocal Enumy where+-- aliases =+-- aliasListBegin+-- $ alias @"Xx" "x"+-- $ alias @"Yy" "y"+-- $ alias @"Zz" "z"+-- $ aliasListEnd+-- -- We use the underlying Enumy type in DerivingVia.+-- newtype EnumyN = EnumyN Enumy+-- deriving (FromJSON, ToJSON) via (GeneralJSONEnum JSONLocal Enumy)+-- :} -- ---instance (Aliased JSON r, GSum ToJSON (Rep r)) => ToJSON (JSONSum objectName r) where- toJSON (JSONSum o) =- let (key, slots) = gFromSum @ToJSON @(Rep r) @Key @Value @Value (aliases @JSONRubric @JSON @r) toJSON (from @r o)- in case slots of- [] -> object [(key, Null)]- [x] -> object [(key, toJSON x)]- xs -> object [(key, toJSON xs)]+type GeneralJSONEnum :: rubric -> Type -> Type+newtype GeneralJSONEnum rubric r = GeneralJSONEnum r -- ---instance (Aliased JSON r, GRecord ToJSON (Rep r)) => ToJSON (JSONRecord objectName r) where- toJSON (JSONRecord o) =- object $ Data.Foldable.toList $ gFromRecord @ToJSON @(Rep r) @Key (aliases @JSONRubric @JSON @r) (\a v -> (a, toJSON v)) (from @r o)+instance (+ Rubric rubric, + AliasType rubric ~ Key, + Aliased rubric r, + GSum Impossible (Rep r)) => FromJSON (GeneralJSONEnum rubric r) where+ parseJSON v =+ let parsers =+ gToSum @Impossible+ (aliases @_ @rubric @r)+ ( \a -> \case+ ZeroSlots x -> EnumBranchParser \case+ String a' | a == fromText a' -> pure x+ _ -> mempty+ SingleSlot _ -> EnumBranchParser mempty+ ManySlots _ -> EnumBranchParser mempty+ )+ Proxy+ Proxy+ parserForValue v = asum $ fmap (($ v) . runEnumBranchParser) parsers+ in GeneralJSONEnum . to <$> parserForValue v +instance (+ Rubric rubric, + AliasType rubric ~ Key, + Aliased rubric r, + GSum Impossible (Rep r)) + => ToJSON (GeneralJSONEnum rubric r) where+ toJSON (GeneralJSONEnum o) =+ let (key, slots) = gFromSum @Impossible @(Rep r) @Key @Value @Value (aliases @_ @rubric @r) absurd (from @r o)+ in case slots of+ [] -> String (toText key)+ [_] -> error "never happens"+ _ -> error "never happens"+ -- $setup -- -- >>> :set -XBlockArguments@@ -271,6 +401,9 @@ -- >>> :set -XMultiParamTypeClasses -- >>> :set -XDeriveGeneric -- >>> :set -XOverloadedStrings+-- >>> :set -XTypeFamilies+-- >>> :set -XDerivingStrategies+-- >>> :set -XDerivingVia -- >>> import ByOtherNames.Aeson -- >>> import Data.Aeson -- >>> import Data.Aeson.Types
lib/ByOtherNames/Constraint.hs view
@@ -1,5 +1,7 @@ {-# LANGUAGE FlexibleInstances #-}-module ByOtherNames.Constraint (Top) where+{-# LANGUAGE TypeFamilies #-}+module ByOtherNames.Constraint (Top, Impossible) where+import Data.Void (Void) -- | A constraint that can always be satisfied. --@@ -7,3 +9,9 @@ -- class Top x instance Top x++-- | A constraint that can't be satisfied.+--+-- Mostly useful with enum-like sum types to denote that they don't have fields.+class (x ~ Void) => Impossible x+instance (x ~ Void) => Impossible x
tests/tests.hs view
@@ -19,6 +19,7 @@ JSONRubric (JSON), JSONSum (..), JSONEnum (..),+ GeneralJSONEnum (..), alias, aliasListBegin, aliasListEnd,