packages feed

autodocodec 0.2.0.4 → 0.2.0.5

raw patch · 7 files changed

+152/−38 lines, 7 filesPVP: minor bump suggested

API additions: PVP suggests at least a minor version bump

API changes (from Hackage documentation)

+ Autodocodec: parseJSONObjectVia :: ObjectCodec void a -> Object -> Parser a
+ Autodocodec: parseJSONObjectViaCodec :: HasObjectCodec a => Object -> Parser a
+ Autodocodec: toJSONObjectVia :: ObjectCodec a void -> a -> Object
+ Autodocodec: toJSONObjectViaCodec :: HasObjectCodec a => a -> Object
+ Autodocodec: toSeriesVia :: ObjectCodec a void -> a -> Series
+ Autodocodec: toSeriesViaCodec :: HasObjectCodec a => a -> Series
+ Autodocodec.Aeson: parseJSONObjectVia :: ObjectCodec void a -> Object -> Parser a
+ Autodocodec.Aeson: parseJSONObjectViaCodec :: HasObjectCodec a => Object -> Parser a
+ Autodocodec.Aeson: toJSONObjectVia :: ObjectCodec a void -> a -> Object
+ Autodocodec.Aeson: toJSONObjectViaCodec :: HasObjectCodec a => a -> Object
+ Autodocodec.Aeson: toSeriesVia :: ObjectCodec a void -> a -> Series
+ Autodocodec.Aeson: toSeriesViaCodec :: HasObjectCodec a => a -> Series
+ Autodocodec.Aeson.Decode: parseJSONObjectVia :: ObjectCodec void a -> Object -> Parser a
+ Autodocodec.Aeson.Decode: parseJSONObjectViaCodec :: HasObjectCodec a => Object -> Parser a
+ Autodocodec.Aeson.Encode: toJSONObjectVia :: ObjectCodec a void -> a -> Object
+ Autodocodec.Aeson.Encode: toJSONObjectViaCodec :: HasObjectCodec a => a -> Object
+ Autodocodec.Aeson.Encode: toSeriesVia :: ObjectCodec a void -> a -> Series
+ Autodocodec.Aeson.Encode: toSeriesViaCodec :: HasObjectCodec a => a -> Series

Files

CHANGELOG.md view
@@ -1,5 +1,12 @@ # Changelog +## [0.2.0.5] - 2023-08-27++### Added++* JSON Object-specific versions of encoding and decoding functions+* Documentation about how 'parseAlternative' and 'optionalField' together can be a pitfall.+ ## [0.2.0.4] - 2023-07-31  ### Added
autodocodec.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack  name:           autodocodec-version:        0.2.0.4+version:        0.2.0.5 synopsis:       Self-documenting encoder and decoder homepage:       https://github.com/NorfairKing/autodocodec#readme bug-reports:    https://github.com/NorfairKing/autodocodec/issues@@ -65,7 +65,7 @@       base >=4.7 && <5     , doctest   default-language: Haskell2010-  if impl(GHC < 9.0)+  if impl(GHC > 9.2)     buildable: True   else     buildable: False
src/Autodocodec.hs view
@@ -11,9 +11,19 @@     toEncodingViaCodec,     toEncodingVia, +    -- ** JSON Objects+    toJSONObjectViaCodec,+    toJSONObjectVia,+    toSeriesViaCodec,+    toSeriesVia,+     -- * Instantiating 'Aeson.FromJSON'     parseJSONViaCodec,     parseJSONVia,++    -- ** JSON Objects+    parseJSONObjectViaCodec,+    parseJSONObjectVia,      -- * Codec     JSONCodec,
src/Autodocodec/Aeson.hs view
@@ -11,9 +11,19 @@     toEncodingViaCodec,     toEncodingVia, +    -- ** JSON Objects+    toJSONObjectViaCodec,+    toJSONObjectVia,+    toSeriesViaCodec,+    toSeriesVia,+     -- * Instantiating @FromJSON@     parseJSONViaCodec,     parseJSONVia,++    -- ** JSON Objects+    parseJSONObjectViaCodec,+    parseJSONObjectVia,      -- * To makes sure we definitely export everything.     module Autodocodec.Aeson.Decode,
src/Autodocodec/Aeson/Decode.hs view
@@ -4,8 +4,20 @@ {-# LANGUAGE PartialTypeSignatures #-} {-# OPTIONS_GHC -fno-warn-partial-type-signatures -fno-warn-orphans #-} -module Autodocodec.Aeson.Decode where+module Autodocodec.Aeson.Decode+  ( -- * Decoding JSON Values+    parseJSONViaCodec,+    parseJSONVia, +    -- ** Decoding JSON Objects+    parseJSONObjectViaCodec,+    parseJSONObjectVia,++    -- ** Internal+    parseJSONContextVia,+  )+where+ import qualified Autodocodec.Aeson.Compat as Compat import Autodocodec.Class import Autodocodec.Codec@@ -27,6 +39,12 @@ -- | Implement 'JSON.parseJSON' via a given codec. parseJSONVia :: ValueCodec void a -> JSON.Value -> JSON.Parser a parseJSONVia = parseJSONContextVia++parseJSONObjectViaCodec :: HasObjectCodec a => JSON.Object -> JSON.Parser a+parseJSONObjectViaCodec = parseJSONObjectVia objectCodec++parseJSONObjectVia :: ObjectCodec void a -> JSON.Object -> JSON.Parser a+parseJSONObjectVia = parseJSONContextVia  -- | Parse via a general codec. --
src/Autodocodec/Aeson/Encode.hs view
@@ -3,8 +3,21 @@ {-# LANGUAGE PartialTypeSignatures #-} {-# OPTIONS_GHC -fno-warn-partial-type-signatures -fno-warn-orphans #-} -module Autodocodec.Aeson.Encode where+module Autodocodec.Aeson.Encode+  ( -- * Encoding JSON Values+    toJSONViaCodec,+    toJSONVia,+    toEncodingViaCodec,+    toEncodingVia, +    -- * Encoding JSON Objects+    toJSONObjectViaCodec,+    toJSONObjectVia,+    toSeriesViaCodec,+    toSeriesVia,+  )+where+ import qualified Autodocodec.Aeson.Compat as Compat import Autodocodec.Class import Autodocodec.Codec@@ -23,6 +36,34 @@ toJSONViaCodec :: HasCodec a => a -> JSON.Value toJSONViaCodec = toJSONVia codec +toJSONObjectViaCodec :: HasObjectCodec a => a -> JSON.Object+toJSONObjectViaCodec = toJSONObjectVia objectCodec++toJSONObjectVia :: ObjectCodec a void -> a -> JSON.Object+toJSONObjectVia = flip go+  where+    go :: a -> ObjectCodec a void -> JSON.Object+    go a = \case+      RequiredKeyCodec k c _ -> Compat.toKey k JSON..= toJSONVia c a+      OptionalKeyCodec k c _ -> case (a :: Maybe _) of+        Nothing -> mempty+        Just b -> Compat.toKey k JSON..= toJSONVia c b+      OptionalKeyWithDefaultCodec k c _ mdoc -> go (Just a) (OptionalKeyCodec k c mdoc)+      OptionalKeyWithOmittedDefaultCodec k c defaultValue mdoc ->+        if a == defaultValue+          then mempty+          else go a (OptionalKeyWithDefaultCodec k c defaultValue mdoc)+      BimapCodec _ g c -> go (g a) c+      PureCodec _ -> mempty+      EitherCodec _ c1 c2 -> case (a :: Either _ _) of+        Left a1 -> go a1 c1+        Right a2 -> go a2 c2+      DiscriminatedUnionCodec propertyName mapping _ ->+        case mapping a of+          (discriminatorValue, c) ->+            Compat.insert (Compat.toKey propertyName) (JSON.String discriminatorValue) $ go a c+      ApCodec oc1 oc2 -> go a oc1 <> go a oc2+ -- | Implement 'JSON.toJSON' via a given codec. toJSONVia :: ValueCodec a void -> a -> JSON.Value toJSONVia = flip go@@ -36,7 +77,7 @@       StringCodec _ -> toJSON (a :: Text)       NumberCodec _ _ -> toJSON (a :: Scientific)       ArrayOfCodec _ c -> toJSON (fmap (`go` c) (a :: Vector _))-      ObjectOfCodec _ oc -> JSON.Object (goObject a oc)+      ObjectOfCodec _ oc -> JSON.Object (toJSONObjectVia oc a)       HashMapCodec c -> JSON.liftToJSON (`go` c) (`go` listCodec c) (a :: HashMap _ _)       MapCodec c -> JSON.liftToJSON (`go` c) (`go` listCodec c) (a :: Map _ _)       ValueCodec -> (a :: JSON.Value)@@ -48,32 +89,38 @@       CommentCodec _ c -> go a c       ReferenceCodec _ c -> go a c -    goObject :: a -> ObjectCodec a void -> JSON.Object+-- | Implement 'JSON.toEncoding' via a type's codec.+toEncodingViaCodec :: HasCodec a => a -> JSON.Encoding+toEncodingViaCodec = toEncodingVia codec++toSeriesViaCodec :: HasObjectCodec a => a -> JSON.Series+toSeriesViaCodec = toSeriesVia objectCodec++toSeriesVia :: ObjectCodec a void -> a -> JSON.Series+toSeriesVia = flip goObject+  where+    goObject :: a -> ObjectCodec a void -> JSON.Series     goObject a = \case-      RequiredKeyCodec k c _ -> Compat.toKey k JSON..= go a c+      RequiredKeyCodec k c _ -> JSON.pair (Compat.toKey k) (toEncodingVia c a)       OptionalKeyCodec k c _ -> case (a :: Maybe _) of-        Nothing -> mempty-        Just b -> Compat.toKey k JSON..= go b c+        Nothing -> mempty :: JSON.Series+        Just b -> JSON.pair (Compat.toKey k) (toEncodingVia c b)       OptionalKeyWithDefaultCodec k c _ mdoc -> goObject (Just a) (OptionalKeyCodec k c mdoc)       OptionalKeyWithOmittedDefaultCodec k c defaultValue mdoc ->         if a == defaultValue           then mempty           else goObject a (OptionalKeyWithDefaultCodec k c defaultValue mdoc)+      PureCodec _ -> mempty :: JSON.Series       BimapCodec _ g c -> goObject (g a) c-      PureCodec _ -> mempty       EitherCodec _ c1 c2 -> case (a :: Either _ _) of         Left a1 -> goObject a1 c1         Right a2 -> goObject a2 c2       DiscriminatedUnionCodec propertyName mapping _ ->         case mapping a of           (discriminatorValue, c) ->-            Compat.insert (Compat.toKey propertyName) (JSON.String discriminatorValue) $ goObject a c+            JSON.pair (Compat.toKey propertyName) (JSON.toEncoding discriminatorValue) <> goObject a c       ApCodec oc1 oc2 -> goObject a oc1 <> goObject a oc2 --- | Implement 'JSON.toEncoding' via a type's codec.-toEncodingViaCodec :: HasCodec a => a -> JSON.Encoding-toEncodingViaCodec = toEncodingVia codec- -- | Implement 'JSON.toEncoding' via the given codec. toEncodingVia :: ValueCodec a void -> a -> JSON.Encoding toEncodingVia = flip go@@ -85,7 +132,7 @@       StringCodec _ -> JSON.text (a :: Text)       NumberCodec _ _ -> JSON.scientific (a :: Scientific)       ArrayOfCodec _ c -> JSON.list (`go` c) (V.toList (a :: Vector _))-      ObjectOfCodec _ oc -> JSON.pairs (goObject a oc)+      ObjectOfCodec _ oc -> JSON.pairs (toSeriesVia oc a)       HashMapCodec c -> JSON.liftToEncoding (`go` c) (`go` listCodec c) (a :: HashMap _ _)       MapCodec c -> JSON.liftToEncoding (`go` c) (`go` listCodec c) (a :: Map _ _)       ValueCodec -> JSON.value (a :: JSON.Value)@@ -96,27 +143,6 @@         Right a2 -> go a2 c2       CommentCodec _ c -> go a c       ReferenceCodec _ c -> go a c-    goObject :: a -> ObjectCodec a void -> JSON.Series-    goObject a = \case-      RequiredKeyCodec k c _ -> JSON.pair (Compat.toKey k) (go a c)-      OptionalKeyCodec k c _ -> case (a :: Maybe _) of-        Nothing -> mempty :: JSON.Series-        Just b -> JSON.pair (Compat.toKey k) (go b c)-      OptionalKeyWithDefaultCodec k c _ mdoc -> goObject (Just a) (OptionalKeyCodec k c mdoc)-      OptionalKeyWithOmittedDefaultCodec k c defaultValue mdoc ->-        if a == defaultValue-          then mempty-          else goObject a (OptionalKeyWithDefaultCodec k c defaultValue mdoc)-      PureCodec _ -> mempty :: JSON.Series-      BimapCodec _ g c -> goObject (g a) c-      EitherCodec _ c1 c2 -> case (a :: Either _ _) of-        Left a1 -> goObject a1 c1-        Right a2 -> goObject a2 c2-      DiscriminatedUnionCodec propertyName mapping _ ->-        case mapping a of-          (discriminatorValue, c) ->-            JSON.pair (Compat.toKey propertyName) (JSON.toEncoding discriminatorValue) <> goObject a c-      ApCodec oc1 oc2 -> goObject a oc1 <> goObject a oc2  instance HasCodec a => JSON.ToJSON (Autodocodec a) where   toJSON = toJSONViaCodec . unAutodocodec
src/Autodocodec/Codec.hs view
@@ -44,7 +44,7 @@ import GHC.Generics (Generic)  -- $setup--- >>> import Autodocodec.Aeson (toJSONVia, toJSONViaCodec, parseJSONVia, parseJSONViaCodec)+-- >>> import Autodocodec.Aeson (toJSONVia, toJSONViaCodec, toJSONObjectVia, toJSONObjectViaCodec, parseJSONVia, parseJSONViaCodec, parseJSONObjectVia, parseJSONObjectViaCodec) -- >>> import qualified Autodocodec.Aeson.Compat as Compat -- >>> import Autodocodec.Class (HasCodec(codec), requiredField) -- >>> import qualified Data.Aeson as JSON@@ -1555,6 +1555,8 @@ -- Just Apple -- >>> JSON.parseMaybe (parseJSONVia c) (String "Apple") :: Maybe Fruit -- Just Apple+-- >>> JSON.parseMaybe (parseJSONVia c) (String "Tomato") :: Maybe Fruit+-- Nothing parseAlternatives ::   -- | Main codec, for parsing and rendering   Codec context input output ->@@ -1573,6 +1575,7 @@ -- -- -- === Example usage+-- ==== Values -- -- >>> data Fruit = Apple | Orange deriving (Show, Eq, Bounded, Enum) -- >>> let c = parseAlternative shownBoundedEnumCodec (stringConstCodec [(Apple, "foo"), (Orange, "bar")])@@ -1582,6 +1585,46 @@ -- Just Apple -- >>> JSON.parseMaybe (parseJSONVia c) (String "Apple") :: Maybe Fruit -- Just Apple+--+-- ==== Required object fields+--+-- >>> data Fruit = Apple | Orange deriving (Show, Eq, Bounded, Enum)+-- >>> let c = shownBoundedEnumCodec+-- >>> let o = parseAlternative (requiredFieldWith "current" c "current key for this field") (requiredFieldWith "legacy" c "legacy key for this field")+-- >>> toJSONObjectVia o Apple+-- fromList [("current",String "Apple")]+-- >>> JSON.parseMaybe (parseJSONObjectVia o) (KM.fromList [("current",String "Apple")]) :: Maybe Fruit+-- Just Apple+-- >>> JSON.parseMaybe (parseJSONObjectVia o) (KM.fromList [("legacy",String "Apple")]) :: Maybe Fruit+-- Just Apple+-- >>> JSON.parseMaybe (parseJSONObjectVia o) (KM.fromList [("current",String "Tomato")]) :: Maybe Fruit+-- Nothing+--+-- ==== Required object fields+--+-- While 'parseAlternative' works exactly like you would expect it would with 'requiredField', using 'parseAlterternative' with optional fields has some pitfalls.+--+-- >>> data Fruit = Apple | Orange deriving (Show, Eq, Bounded, Enum)+-- >>> let c = shownBoundedEnumCodec+-- >>> let o = parseAlternative (optionalFieldWith "current" c "current key for this field") (optionalFieldWith "legacy" c "legacy key for this field")+-- >>> toJSONObjectVia o (Just Apple)+-- fromList [("current",String "Apple")]+-- >>> toJSONObjectVia o Nothing+-- fromList []+-- >>> JSON.parseMaybe (parseJSONObjectVia o) (KM.fromList [("current",String "Apple")]) :: Maybe (Maybe Fruit)+-- Just (Just Apple)+--+--+-- ! This is the important result !+-- The second 'optionalFieldWith' is not tried because the first one _succeeds_ in parsing 'Nothing'+--+-- >>> JSON.parseMaybe (parseJSONObjectVia o) (KM.fromList [("legacy",String "Apple")]) :: Maybe (Maybe Fruit)+-- Just Nothing+--+-- Here the parser succeeds as well, because it fails to parse the @current@ field, so it tries to parse the @legacy@ field, which is missing.+--+-- >>> JSON.parseMaybe (parseJSONObjectVia o) (KM.fromList [("current",String "Tomato")]) :: Maybe (Maybe Fruit)+-- Just Nothing parseAlternative ::   -- | Main codec, for parsing and rendering   Codec context input output ->