unjson 0.15.3 → 0.15.4
raw patch · 5 files changed
+205/−126 lines, 5 filesdep ~aesondep ~basePVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependency ranges changed: aeson, base
API changes (from Hackage documentation)
- Data.Unjson: instance (Data.Typeable.Internal.Typeable v, Data.Unjson.Unjson v) => Data.Unjson.Unjson (Data.HashMap.Base.HashMap Data.Text.Internal.Lazy.Text v)
- Data.Unjson: instance (Data.Typeable.Internal.Typeable v, Data.Unjson.Unjson v) => Data.Unjson.Unjson (Data.HashMap.Base.HashMap Data.Text.Internal.Text v)
- Data.Unjson: instance (Data.Typeable.Internal.Typeable v, Data.Unjson.Unjson v) => Data.Unjson.Unjson (Data.HashMap.Base.HashMap GHC.Base.String v)
- Data.Unjson: instance (GHC.Classes.Eq a, Data.Hashable.Class.Hashable a, Data.Unjson.Unjson a, Data.Typeable.Internal.Typeable a) => Data.Unjson.Unjson (Data.HashSet.Base.HashSet a)
+ Data.Unjson: instance (Data.Typeable.Internal.Typeable v, Data.Unjson.Unjson v) => Data.Unjson.Unjson (Data.Aeson.KeyMap.KeyMap v)
+ Data.Unjson: instance (GHC.Classes.Eq a, Data.Hashable.Class.Hashable a, Data.Unjson.Unjson a, Data.Typeable.Internal.Typeable a) => Data.Unjson.Unjson (Data.HashSet.Internal.HashSet a)
+ Data.Unjson.Internal.Aeson.Compat: convertPair :: (Text, Value) -> Pair
+ Data.Unjson.Internal.Aeson.Compat: fromString :: String -> Key
+ Data.Unjson.Internal.Aeson.Compat: fromText :: Text -> Key
+ Data.Unjson.Internal.Aeson.Compat: lazyKeyMapFromList :: [(Key, v)] -> KeyMap v
+ Data.Unjson.Internal.Aeson.Compat: testErrorMessage :: Text
+ Data.Unjson.Internal.Aeson.Compat: toString :: Key -> String
+ Data.Unjson.Internal.Aeson.Compat: toText :: Key -> Text
+ Data.Unjson.Internal.Aeson.Compat: type LazyKeyMap = KeyMap
- Data.Unjson: [MapUnjsonDef] :: Typeable k => UnjsonDef k -> (HashMap Text k -> Result v) -> (v -> HashMap Text k) -> UnjsonDef v
+ Data.Unjson: [MapUnjsonDef] :: Typeable k => UnjsonDef k -> (KeyMap k -> Result v) -> (v -> KeyMap k) -> UnjsonDef v
- Data.Unjson: mapOf :: Typeable x => UnjsonDef x -> UnjsonDef (HashMap Text x)
+ Data.Unjson: mapOf :: Typeable x => UnjsonDef x -> UnjsonDef (LazyKeyMap x)
Files
- CHANGELOG.md +5/−0
- src/Data/Unjson.hs +103/−104
- src/Data/Unjson/Internal/Aeson/Compat.hs +77/−0
- test/Test.hs +15/−17
- unjson.cabal +5/−5
CHANGELOG.md view
@@ -1,3 +1,8 @@+# unjson-0.15.4 (2022-02-26)+* Add support for aeson >= 2.+* Add support for GHC 9.0 and 9.2.+* Drop support for GHC < 8.8.+ # unjson-0.15.3 (2020-05-05) * GHC 8.8 and 8.10 support
src/Data/Unjson.hs view
@@ -1,5 +1,4 @@ {-# LANGUAGE CPP #-}- -- | @Unjson@: bidirectional JSON (de)serialization with strong error -- reporting capabilities and automatic documentation generation. --@@ -84,75 +83,87 @@ -- 'unjsonToJSON', 'unjsonToByteStringLazy', -- 'unjsonToByteStringBuilder' and 'render'. module Data.Unjson-(--- * Serialization to JSON- unjsonToJSON-, unjsonToJSON'-, unjsonToByteStringLazy-, unjsonToByteStringLazy'-, unjsonToByteStringBuilder-, unjsonToByteStringBuilder'-, unjsonToByteStringBuilder''-, Options(..)+ ( -- * Serialization to JSON+ unjsonToJSON+ , unjsonToJSON'+ , unjsonToByteStringLazy+ , unjsonToByteStringLazy'+ , unjsonToByteStringBuilder+ , unjsonToByteStringBuilder'+ , unjsonToByteStringBuilder''+ , Options(..) --- * Data definitions-, Unjson(..)-, UnjsonDef(..)+ -- * Data definitions+ , Unjson(..)+ , UnjsonDef(..) --- ** Objects-, objectOf-, field-, fieldBy-, fieldOpt-, fieldOptBy-, fieldDef-, fieldDefBy-, fieldReadonly-, fieldReadonlyBy-, FieldDef(..)--- ** Arrays-, arrayOf-, arrayWithModeOf-, arrayWithModeOf'-, arrayWithPrimaryKeyOf-, arrayWithModeAndPrimaryKeyOf-, ArrayMode(..)--- ** Maps, enums, sums-, mapOf-, enumOf-, enumUnjsonDef-, disjointUnionOf-, unionOf--- ** Helpers-, unjsonAeson-, unjsonAesonWithDoc+ -- ** Objects+ , objectOf+ , field+ , fieldBy+ , fieldOpt+ , fieldOptBy+ , fieldDef+ , fieldDefBy+ , fieldReadonly+ , fieldReadonlyBy+ , FieldDef(..)+ -- ** Arrays+ , arrayOf+ , arrayWithModeOf+ , arrayWithModeOf'+ , arrayWithPrimaryKeyOf+ , arrayWithModeAndPrimaryKeyOf+ , ArrayMode(..)+ -- ** Maps, enums, sums+ , mapOf+ , enumOf+ , enumUnjsonDef+ , disjointUnionOf+ , unionOf+ -- ** Helpers+ , unjsonAeson+ , unjsonAesonWithDoc --- * Documentation rendering-, render-, renderForPath-, renderDoc-, renderDocForPath+ -- * Documentation rendering+ , render+ , renderForPath+ , renderDoc+ , renderDocForPath --- * Parsing and updating-, parse-, update-, Result(..)-, Anchored(..)-, Problem-, Problems-, Path(..)-, PathElem(..)+ -- * Parsing and updating+ , parse+ , update+ , Result(..)+ , Anchored(..)+ , Problem+ , Problems+ , Path(..)+ , PathElem(..) -, unjsonInvmapR-, unjsonIsConstrByName-, unjsonIPv4AsWord32-)-where+ , unjsonInvmapR+ , unjsonIsConstrByName+ , unjsonIPv4AsWord32+ ) where +import Control.Applicative.Free+import Control.Exception+import Control.Monad+import Data.Data+import Data.Fixed+import Data.Functor.Invariant+import Data.Hashable+import Data.Maybe+import Data.Monoid hiding (Ap)+import Data.Primitive.Types+import Data.Scientific+import Data.Time.Clock+import Data.Time.LocalTime+import Foreign.Storable import qualified Data.Aeson as Aeson import qualified Data.Aeson.Types as Aeson+import qualified Data.ByteString.Builder as Builder import qualified Data.ByteString.Lazy as BSL-import qualified Data.ByteString.Lazy.Builder as Builder import qualified Data.HashSet as HashSet import qualified Data.IntMap as IntMap import qualified Data.IntSet as IntSet@@ -166,36 +177,21 @@ import qualified Data.Vector.Primitive import qualified Data.Vector.Storable import qualified Data.Vector.Unboxed-import Data.Typeable-import Data.Data-import Data.Maybe-import Data.Monoid hiding (Ap)-import Data.Primitive.Types-import Data.Hashable-import Data.Scientific-import Data.Time.LocalTime-import Data.Time.Clock-import Data.Fixed-import Foreign.Storable-import Control.Applicative.Free-import Data.Functor.Invariant-import qualified Data.HashMap.Strict as HashMap-import qualified Data.HashMap.Lazy as LazyHashMap-import Control.Exception -import Control.Monad hiding (fail)-import Control.Monad.Fail (MonadFail (..)) import Data.Bits-import Data.Word+import Data.Char import Data.Int-import Data.Ratio import Data.List+import Data.Ratio+import Data.Word+import qualified Data.Unjson.Internal.Aeson.Compat as AC import qualified Text.ParserCombinators.ReadP as ReadP-import Data.Char-import Prelude hiding (fail)- import qualified Text.PrettyPrint.HughesPJ as P +#if !MIN_VERSION_aeson(2,0,1)+import qualified Data.HashMap.Strict as HashMap+#endif+ -- | Describe a path from root JSON element to a specific -- position. JSON has only two types of containers: objects and -- arrays, so there are only two types of keys needed to index into@@ -379,27 +375,30 @@ mapFst f (a,b) = (f a, b) instance (Typeable v, Unjson v) => Unjson (Map.Map String v)- where unjsonDef = invmap (Map.fromList . map (mapFst Text.unpack) . HashMap.toList)- (HashMap.fromList . map (mapFst Text.pack) . Map.toList)+ where unjsonDef = invmap (Map.fromList . map (mapFst AC.toString) . AC.toList)+ (AC.fromList . map (mapFst AC.fromString) . Map.toList) unjsonDef instance (Typeable v, Unjson v) => Unjson (Map.Map Text.Text v)- where unjsonDef = invmap (Map.fromList . HashMap.toList)- (HashMap.fromList . Map.toList)+ where unjsonDef = invmap (Map.fromList . fmap (mapFst AC.toText) . AC.toList)+ (AC.fromList . fmap (mapFst AC.fromText) . Map.toList) unjsonDef instance (Typeable v, Unjson v) => Unjson (Map.Map LazyText.Text v)- where unjsonDef = invmap (Map.fromList . map (mapFst LazyText.fromStrict) . HashMap.toList)- (HashMap.fromList . map (mapFst LazyText.toStrict) . Map.toList)+ where unjsonDef = invmap (Map.fromList . map (mapFst (LazyText.fromStrict . AC.toText)) . AC.toList)+ (AC.fromList . map (mapFst (AC.fromText . LazyText.toStrict)) . Map.toList) unjsonDef+instance (Typeable v, Unjson v) => Unjson (AC.KeyMap v)+ where unjsonDef = MapUnjsonDef unjsonDef pure id++#if !MIN_VERSION_aeson(2,0,1) instance (Typeable v, Unjson v) => Unjson (HashMap.HashMap String v) where unjsonDef = invmap (HashMap.fromList . map (mapFst Text.unpack) . HashMap.toList) (HashMap.fromList . map (mapFst Text.pack) . HashMap.toList) unjsonDef-instance (Typeable v, Unjson v) => Unjson (HashMap.HashMap Text.Text v)- where unjsonDef = MapUnjsonDef unjsonDef pure id instance (Typeable v, Unjson v) => Unjson (HashMap.HashMap LazyText.Text v) where unjsonDef = invmap (HashMap.fromList . map (mapFst LazyText.fromStrict) . HashMap.toList) (HashMap.fromList . map (mapFst LazyText.toStrict) . HashMap.toList) unjsonDef+#endif instance (Unjson a,Unjson b) => Unjson (a,b) where unjsonDef = TupleUnjsonDef $ fmap pure@@ -590,7 +589,7 @@ TupleUnjsonDef :: Ap (TupleFieldDef k) (Result k) -> UnjsonDef k DisjointUnjsonDef :: Text.Text -> [(Text.Text, k -> Bool, Ap (FieldDef k) (Result k))] -> UnjsonDef k UnionUnjsonDef :: [(k -> Bool, Ap (FieldDef k) (Result k))] -> UnjsonDef k- MapUnjsonDef :: Typeable k => UnjsonDef k -> (HashMap.HashMap Text.Text k -> Result v) -> (v -> HashMap.HashMap Text.Text k) -> UnjsonDef v+ MapUnjsonDef :: Typeable k => UnjsonDef k -> (AC.KeyMap k -> Result v) -> (v -> AC.KeyMap k) -> UnjsonDef v instance Invariant UnjsonDef where invmap f g (SimpleUnjsonDef name p s) = SimpleUnjsonDef name (fmap f . p) (s . g)@@ -690,15 +689,15 @@ (ArrayModeParseAndOutputSingle,[b]) -> unjsonToJSON' opt f b (_,c) -> Aeson.toJSON (map (unjsonToJSON' opt f) c) unjsonToJSON' opt (ObjectUnjsonDef f) a =- Aeson.object (objectDefToArray (nulls opt) (unjsonToJSON' opt) a f)+ Aeson.object $ AC.convertPair <$> (objectDefToArray (nulls opt) (unjsonToJSON' opt) a f) unjsonToJSON' opt (TupleUnjsonDef f) a = Aeson.toJSON (tupleDefToArray (unjsonToJSON' opt) a f) unjsonToJSON' opt (DisjointUnjsonDef k l) a =- Aeson.object ((k,Aeson.toJSON nm) : objectDefToArray (nulls opt) (unjsonToJSON' opt) a f)+ Aeson.object $ AC.convertPair <$> ((k,Aeson.toJSON nm) : objectDefToArray (nulls opt) (unjsonToJSON' opt) a f) where [(nm,_,f)] = filter (\(_,is,_) -> is a) l unjsonToJSON' opt (UnionUnjsonDef l) a =- Aeson.object (objectDefToArray (nulls opt) (unjsonToJSON' opt) a f)+ Aeson.object $ AC.convertPair <$> (objectDefToArray (nulls opt) (unjsonToJSON' opt) a f) where [(_,f)] = filter (\(is,_) -> is a) l unjsonToJSON' opt (MapUnjsonDef f _ g) a =@@ -794,7 +793,7 @@ unjsonToByteStringBuilder'' level opt (MapUnjsonDef f _ g) a = unjsonGroup level opt (Builder.char8 '{') (Builder.char8 '}') serx obj where- obj = LazyHashMap.toList (fmap (unjsonToByteStringBuilder'' (level + indent opt) opt f) (g a))+ obj = AC.toList (fmap (unjsonToByteStringBuilder'' (level + indent opt) opt f) (g a)) serx (key,val) = Builder.lazyByteString (Aeson.encode (Aeson.toJSON key)) <> Builder.char8 ':' <> (if pretty opt then Builder.char8 ' ' else mempty) <> val @@ -881,7 +880,7 @@ parseUpdating (DisjointUnjsonDef k l) ov v = case Aeson.parseEither Aeson.parseJSON v of- Right v' -> case HashMap.lookup k v' of+ Right v' -> case AC.lookup (AC.fromText k) v' of Just x -> case Aeson.parseEither Aeson.parseJSON x of Right xx -> case filter (\(nm,_,_) -> nm==xx) l of [(_,_,f)] -> join (runAp (lookupByFieldDef v' ov) f)@@ -896,7 +895,7 @@ fail e parseUpdating (UnionUnjsonDef l) ov v = case Aeson.parseEither Aeson.parseJSON v of- Right v' -> case filter (\(_,f) -> isJust (mapM_ (\k -> HashMap.lookup k v') (listRequiredKeys f))) l of+ Right v' -> case filter (\(_,f) -> isJust (mapM_ (\k -> AC.lookup (AC.fromText k) v') (listRequiredKeys f))) l of ((_,f):_) -> join (runAp (lookupByFieldDef v' ov) f) _ -> fail $ "union value type could not be recognized based on presence of keys" Left e ->@@ -905,7 +904,7 @@ = case Aeson.parseEither Aeson.parseJSON v of Right v' -> let hov = fmap h ov in- join $ fmap g $ HashMap.traverseWithKey (\k1 v1 -> resultPrependKey k1 $ parseUpdating f (join (fmap (HashMap.lookup k1) hov)) v1) v'+ join $ fmap g $ AC.traverseWithKey (\k1 v1 -> resultPrependKey (AC.toText k1) $ parseUpdating f (join (fmap (AC.lookup k1) hov)) v1) v' Left e -> fail e @@ -973,20 +972,20 @@ lookupByFieldDef :: Aeson.Object -> Maybe s -> FieldDef s a -> Result a lookupByFieldDef v ov (FieldReqDef name _docstring f valuedef)- = resultPrependKey name $ case HashMap.lookup name v of+ = resultPrependKey name $ case AC.lookup (AC.fromText name) v of Just x -> parseUpdating valuedef (fmap f ov) x Nothing -> case ov of Just xov -> Result (f xov) [] Nothing -> fail "missing key" lookupByFieldDef v ov (FieldDefDef name _docstring def f valuedef)- = resultPrependKey name $ case HashMap.lookup name v of+ = resultPrependKey name $ case AC.lookup (AC.fromText name) v of Just Aeson.Null -> Result def [] Just x -> parseUpdating valuedef (fmap f ov) x Nothing -> case ov of Just xov -> Result (f xov) [] Nothing -> Result def [] lookupByFieldDef v ov (FieldOptDef name _docstring f valuedef)- = resultPrependKey name $ case HashMap.lookup name v of+ = resultPrependKey name $ case AC.lookup (AC.fromText name) v of Just Aeson.Null -> Result Nothing [] Just x -> case ov of Just xov -> fmap Just (parseUpdating valuedef (f xov) x)@@ -1168,7 +1167,7 @@ -- > objectOf $ pure X -- > <*> field "xmap" xMap -- > "Map string to Y value"-mapOf :: Typeable x => UnjsonDef x -> UnjsonDef (LazyHashMap.HashMap Text.Text x)+mapOf :: Typeable x => UnjsonDef x -> UnjsonDef (AC.LazyKeyMap x) mapOf def = MapUnjsonDef def pure id -- | Provide sum type support. Bidirectional case matching in Haskell
+ src/Data/Unjson/Internal/Aeson/Compat.hs view
@@ -0,0 +1,77 @@+{-# LANGUAGE CPP #-}+module Data.Unjson.Internal.Aeson.Compat+ ( module Map+#if !MIN_VERSION_aeson(2,0,1)+ , KeyMap+#endif+ , fromText+ , toText+ , convertPair+ , LazyKeyMap+ , testErrorMessage+ , fromString+ , toString+ , lazyKeyMapFromList+ ) where++import Data.Text+import qualified Data.Aeson.Types as A++#if MIN_VERSION_aeson(2,0,1)++import Data.Aeson.KeyMap as Map+import qualified Data.Aeson.Key as K++fromText :: Text -> K.Key+fromText = K.fromText++toText :: K.Key -> Text+toText = K.toText++fromString :: String -> K.Key+fromString = K.fromString++toString :: K.Key -> String+toString = K.toString++convertPair :: (Text, A.Value) -> A.Pair+convertPair (t, v) = (K.fromText t, v)++type LazyKeyMap = KeyMap++testErrorMessage :: Text+testErrorMessage = "Error in $: parsing KeyMap failed, expected Object, but encountered String"++lazyKeyMapFromList :: [(K.Key, v)] -> Map.KeyMap v+lazyKeyMapFromList = Map.fromList++#else++import Data.HashMap.Strict as Map+import Data.Hashable (Hashable)+import qualified Data.HashMap.Lazy as LazyHashMap++type KeyMap a = Map.HashMap Text a+type LazyKeyMap a = LazyHashMap.HashMap Text a++fromText :: Text -> Text+fromText = id++toText :: Text -> Text+toText = id++fromString :: String -> String+fromString = id++toString :: String -> String+toString = id++convertPair :: (Text, A.Value) -> (Text, A.Value)+convertPair = id++testErrorMessage :: Text+testErrorMessage = "Error in $: parsing HashMap ~Text failed, expected Object, but encountered String"++lazyKeyMapFromList :: (Eq k, Hashable k) => [(k, v)] -> HashMap k v+lazyKeyMapFromList = LazyHashMap.fromList+#endif
test/Test.hs view
@@ -2,24 +2,20 @@ {-# OPTIONS_GHC -fno-warn-type-defaults #-} module Main where -import Prelude hiding (fail)-import qualified Data.Text as Text-import qualified Data.ByteString.Lazy.Char8 as BSL-import Data.Int-import Data.Typeable-import Data.Unjson-import qualified Data.Aeson as Aeson import Data.Aeson ((.=))-import Data.List import Data.Data import Data.Functor.Invariant-import Control.Monad.Fail (MonadFail(fail))+import Data.Int+import Data.List+import Data.Unjson+import System.Exit (ExitCode (..), exitWith) import Test.HUnit-import qualified Data.HashMap.Strict as HashMap-import qualified Data.HashMap.Lazy as LazyHashMap+import qualified Data.Aeson as Aeson+import qualified Data.ByteString.Lazy.Char8 as BSL import qualified Data.Map as Map+import qualified Data.Text as Text -import System.Exit (ExitCode (..), exitWith)+import qualified Data.Unjson.Internal.Aeson.Compat as AC default (Text.Text, String, Int, Double) @@ -181,8 +177,10 @@ (Anchored (Path [ PathElemKey "port" ]) "parsing Integer failed, expected Number, but encountered Object") (iss!!1) assertEqual "Credentials must be object error info is present"- (Anchored (Path [ PathElemKey "credentials"- ]) "Error in $: parsing HashMap ~Text failed, expected Object, but encountered String") (iss!!2)+ (Anchored + (Path [ PathElemKey "credentials" ]) + AC.testErrorMessage+ ) (iss!!2) return () test_tuple_parsing :: Test@@ -661,7 +659,7 @@ <*> field "a_map" id "The only map"- let unjsonMapByExplicit :: (Unjson a, Typeable a) => UnjsonDef (HashMap.HashMap Text.Text a)+ let unjsonMapByExplicit :: (Unjson a, Typeable a) => UnjsonDef (AC.KeyMap a) unjsonMapByExplicit = objectOf $ pure id <*> fieldBy "a_map" id@@ -672,10 +670,10 @@ assertEqual "Parsing keeps proper order in Data.Map" (Map.fromList [("k1"::String, 12::Int),("k2", 1122), ("a4", 666)]) val0 let Result val1 iss1 = parse unjsonMapByInstance jsonEmbedded assertEqual "No problems" [] iss1- assertEqual "Parsing keeps proper order" (HashMap.fromList [("k1"::String, 12::Int),("k2", 1122), ("a4", 666)]) val1+ assertEqual "Parsing keeps proper order" (AC.fromList [(AC.fromString ("k1" :: String), 12::Int),("k2", 1122), ("a4", 666)]) val1 let Result val2 iss2 = parse unjsonMapByExplicit jsonEmbedded assertEqual "No problems" [] iss2- assertEqual "Parsing keeps proper order" (LazyHashMap.fromList [("k1"::Text.Text, 12::Int),("k2", 1122), ("a4", 666)]) val2+ assertEqual "Parsing keeps proper order" (AC.lazyKeyMapFromList [(AC.fromText ("k1"::Text.Text), 12::Int),("k2", 1122), ("a4", 666)]) val2 return ()
unjson.cabal view
@@ -1,6 +1,6 @@ cabal-version: 1.18 name: unjson-version: 0.15.3+version: 0.15.4 synopsis: Bidirectional JSON parsing and generation. description: Bidirectional JSON parsing and generation with automatic documentation support.@@ -15,7 +15,7 @@ category: Data build-type: Simple extra-source-files: README.md, CHANGELOG.md-tested-with: GHC ==8.0.2 || ==8.2.2 || ==8.4.4 || ==8.6.5 || ==8.8.3 || ==8.10.1+tested-with: GHC ==8.8.4 || ==8.10.7 || ==9.0.2 || ==9.2.1 source-repository head type: git@@ -28,10 +28,10 @@ library exposed-modules: Data.Unjson- -- other-modules:+ Data.Unjson.Internal.Aeson.Compat -- other-extensions:- build-depends: base >= 4.9 && < 5,- aeson >= 1.0 && < 1.5,+ build-depends: base >= 4.13 && < 5,+ aeson >= 1.0, attoparsec, bytestring >= 0.10, containers,