diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,5 +1,21 @@
-# Revision history for version
+# Revision history
 
-## 0.1.0.0  -- 2018-06-06
+## 0.2.0.0
 
-* First version. Released on an unsuspecting world.
+- Get the version number of a versioned value
+
+- Decouple the decoding logic from the actual encoding
+
+- Provide two families of functions for decoding JSON:
+  `fromJsonAnyVersionX` and `withJsonAnyVersionX`
+
+### Breaking changes
+
+- `decodeAnyVersion` is now called `fromJsonAnyVersion`
+
+- `withAnyVersion` is now called `withJsonAnyVersion`
+
+- `withAnyVersionM` is now called `withJsonAnyVersionM`
+## 0.1.0.0
+
+- First version
diff --git a/src/Versioning/Base.hs b/src/Versioning/Base.hs
--- a/src/Versioning/Base.hs
+++ b/src/Versioning/Base.hs
@@ -4,32 +4,39 @@
 --   'Since' and 'Until' type families.
 --
 --   Example:
---   data Rec v = Rec
---       { foo :: Int               -- this field exists in all versions
---       , bar :: Since V2 v Bool   -- this field has been introduced in V2
---       , baz :: Until V2 v Double -- this field has been removed in V3
---       }
 --
+-- > data Rec v = Rec
+-- >     { foo :: Int               -- this field exists in all versions
+-- >     , bar :: Since V2 v Bool   -- this field has been introduced in V2
+-- >     , baz :: Until V2 v Double -- this field has been removed in V3
+-- >     }
+--
 --   Besides reducing the number of data declarations,
 --   this approach also has other advantages:
+--
 --   * It makes migrations declarative and self-documenting.
+--
 --   * It allows for less verbose version-upcasting functions,
 --     since the fields that have a non-parametric type do not need to be copied.
+--
 --   * It is a foundation on which other useful abstractions can be built.
---   However there are also some drawbacks to consider:
---   some classes may require a separate standalone deriving clause for each version.
---   Moreover the usage of type-level computations can make the error messages
---   harder to understand.
+--
+--   Please note that some classes may require a separate standalone deriving clause
+--   for each version of a data-type or some kind of inductive deriving mechanism.
 {-# LANGUAGE DataKinds             #-}
-{-# LANGUAGE DeriveGeneric         #-}
+{-# LANGUAGE ExplicitNamespaces    #-}
+{-# LANGUAGE FlexibleContexts      #-}
 {-# LANGUAGE FlexibleInstances     #-}
 {-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE ScopedTypeVariables   #-}
 {-# LANGUAGE StandaloneDeriving    #-}
 {-# LANGUAGE TypeFamilies          #-}
 {-# LANGUAGE TypeOperators         #-}
 {-# LANGUAGE UndecidableInstances  #-}
 module Versioning.Base
   ( V(..)
+  , GetV
+  , versionNumber
   , Since
   , Until
   , NA
@@ -57,13 +64,23 @@
   )
 where
 
-import           Data.Aeson   (FromJSON (..), ToJSON (..), Value (..))
-import           GHC.Generics (Generic)
-import           GHC.TypeNats
+import           Data.Proxy               (Proxy (..))
+import           GHC.TypeNats             (type (-), KnownNat, Nat, natVal)
+import           Numeric.Natural          (Natural)
 
+import           Versioning.Internal.Base (Bare)
+
 -- | The version of a data model
 newtype V = V Nat
 
+-- | Get the type-level natural of a version
+type family GetV (v :: V) :: Nat where
+    GetV ('V n) = n
+
+-- | Get the version number of a versioned value
+versionNumber :: forall a v. KnownNat (GetV v) => a v -> Natural
+versionNumber _ = natVal (Proxy :: Proxy (GetV v))
+
 -- | This allows us to express that a field is only present since
 --   a given version.
 --   The first parameter is the version in which the field has been introduced,
@@ -83,36 +100,20 @@
     Until ('V u) ('V v) a = Until ('V (u - 1)) ('V (v - 1)) a
 
 -- | A type indicating absence.
---   The 'Maybe' is a hack needed to let aeson parse a record even
---   if a field is missing.
+--   The 'Maybe' is a hack needed to let aeson parse a record successfully even
+--   if a field of type 'NA' is missing.
+--
+--   Ideally we would like to define it as
+--
+-- > data NA = NA
+--
+--   but this would not work with 'FromJSON' instances that are derived
+--   with Generic.
 type NA = Maybe Bare
 
 -- | A placeholder for an absent value.
 na :: NA
 na = Nothing
-
--- | An uninhabited type.
---   We define our own type instead of using "Data.Void"
---   because we need additional instances.
---   Moreover this type is hidden to force users to use 'NA',
---   which is needed because of the 'Maybe' hack.
-data Bare
-
-deriving instance Eq Bare
-
-deriving instance Generic Bare
-
-deriving instance Show Bare
-
--- Attempting to supply a value for an absent field must produce a
--- parsing failure
-instance FromJSON Bare where
-    parseJSON _ = fail "An NA field should be absent or null"
-
--- We provide an instance to make the compiler happy.
--- In practice it will never be used.
-instance ToJSON Bare where
-    toJSON _ = Null
 
 -- Handy type synonyms to minimize parenthesis
 type V1 = 'V 1
diff --git a/src/Versioning/Internal/Base.hs b/src/Versioning/Internal/Base.hs
new file mode 100644
--- /dev/null
+++ b/src/Versioning/Internal/Base.hs
@@ -0,0 +1,33 @@
+{-# LANGUAGE DeriveGeneric      #-}
+{-# LANGUAGE StandaloneDeriving #-}
+module Versioning.Internal.Base
+  ( Bare
+  )
+where
+
+
+import           Data.Aeson   (FromJSON (..), ToJSON (..), Value (..))
+import           GHC.Generics (Generic)
+
+-- | An uninhabited type.
+--   We define our own type instead of using "Data.Void"
+--   because we need additional instances.
+--   Moreover this type is internal.
+--   Users are supposed to use 'NA' to express absence.
+data Bare
+
+deriving instance Eq Bare
+
+deriving instance Generic Bare
+
+deriving instance Show Bare
+
+-- Attempting to supply a value for an absent field must produce a
+-- parsing failure
+instance FromJSON Bare where
+    parseJSON _ = fail "An NA field should be absent or null"
+
+-- We provide an instance to make the compiler happy.
+-- In practice it will never be used.
+instance ToJSON Bare where
+    toJSON _ = Null
diff --git a/src/Versioning/Internal/Decoding.hs b/src/Versioning/Internal/Decoding.hs
new file mode 100644
--- /dev/null
+++ b/src/Versioning/Internal/Decoding.hs
@@ -0,0 +1,118 @@
+-- | Encoding-agnosting deserialization utilities.
+{-# LANGUAGE AllowAmbiguousTypes   #-}
+{-# LANGUAGE ApplicativeDo         #-}
+{-# LANGUAGE ConstraintKinds       #-}
+{-# LANGUAGE DataKinds             #-}
+{-# LANGUAGE FlexibleContexts      #-}
+{-# LANGUAGE FlexibleInstances     #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE RankNTypes            #-}
+{-# LANGUAGE ScopedTypeVariables   #-}
+{-# LANGUAGE TypeApplications      #-}
+{-# LANGUAGE TypeFamilies          #-}
+{-# LANGUAGE TypeInType            #-}
+{-# LANGUAGE TypeOperators         #-}
+{-# LANGUAGE UndecidableInstances  #-}
+module Versioning.Internal.Decoding
+  ( Applied
+  , Apply
+  , ApplyM
+  , DecodableTo
+  , DecodeAnyVersion
+  , Decoder (..)
+  , WithAnyVersion
+  , decodeAnyVersion
+  , withAnyVersion
+  , withAnyVersionM
+  )
+where
+
+import           Data.Functor.Alt            (Alt (..))
+import           Data.Functor.Identity       (Identity (..))
+import           Data.Kind                   (Constraint, Type)
+
+import           Versioning.Base
+import           Versioning.Internal.Folding (Decr)
+import           Versioning.Upgrade
+
+-- | The result type of the action that has been applied to the decoded object
+--   with 'withAnyVersion' or 'withAnyVersionM'.
+type family Applied (c :: Type -> Constraint) (a :: V -> Type) :: Type
+
+-- | The pure function to apply to the decoded object with 'withAnyVersion'
+type Apply a c = forall v. c (a v) => a v -> Applied c a
+
+-- | The action to apply to the decoded object with 'withAnyVersionM'
+type ApplyM m a c = forall v. c (a v) => a v -> m (Applied c a)
+
+-- | The function that will perform the actual decoding
+newtype Decoder dec enc t a = Decoder (forall v. dec (a v) => enc -> t (a v))
+
+-- | Handy constraint synonym to be used with 'decodeAnyVersion'
+type DecodableTo dec v a = DecodeAnyVersion v v a dec
+
+-- | Decode by trying all the versions decrementally
+--   and upgrade the decoded object to the newest version.
+decodeAnyVersion
+  :: forall v a dec enc t
+   . (Alt t, Applicative t, DecodableTo dec v a)
+  => Decoder dec enc t a
+  -> enc
+  -> t (a v)
+decodeAnyVersion = decodeAnyVersion' @v @v @a @dec
+
+-- | Decode by trying all the versions decrementally
+--   and apply an action to the decoded object at its original version.
+withAnyVersionM
+  :: forall v c a dec enc m t
+   . (WithAnyVersion v a c dec, Alt t, Applicative t, Traversable t, Applicative m, c (a v))
+  => Decoder dec enc t a
+  -> ApplyM m a c
+  -> enc
+  -> m (t (Applied c a))
+withAnyVersionM = withAnyVersion' @v @a @c @dec
+
+-- | Pure version of 'withAnyVersionM'.
+withAnyVersion
+  :: forall v c a dec enc t
+   . (WithAnyVersion v a c dec, c (a v), Alt t, Applicative t, Traversable t)
+  => Decoder dec enc t a
+  -> Apply a c
+  -> enc
+  -> t (Applied c a)
+withAnyVersion dec action =
+  runIdentity . withAnyVersionM @v @c @a dec (Identity . action)
+
+class DecodeAnyVersion (v :: V) (w :: V) (a :: V -> Type) dec where
+    decodeAnyVersion'
+      :: (Alt t, Applicative t)
+      => Decoder dec enc t a
+      -> enc
+      -> t (a w)
+
+instance {-# OVERLAPPING #-} (dec (a V1), Upgrade V1 w a)
+  => DecodeAnyVersion V1 w a dec where
+    decodeAnyVersion' (Decoder decode) bs = upgrade @V1 @w <$> decode @V1 bs
+
+instance {-# OVERLAPPABLE #-} (DecodeAnyVersion (Decr v V1) w a dec, dec (a v), dec (a (Decr v V1)), Upgrade v w a)
+  => DecodeAnyVersion v w a dec where
+    decodeAnyVersion' decoder@(Decoder decode) bs = upgrade @v @w <$> decode @v bs
+                                                <!> decodeAnyVersion' @(Decr v V1) @w decoder bs
+
+class WithAnyVersion (v :: V) (a :: V -> Type) c dec where
+    withAnyVersion' :: (Applicative m, Alt t, Applicative t, Traversable t, c (a v))
+                    => Decoder dec enc t a
+                    -> ApplyM m a c
+                    -> enc
+                    -> m (t (Applied c a))
+
+instance {-# OVERLAPPING #-} (dec (a V1), c (a V1))
+  => WithAnyVersion V1 a c dec where
+    withAnyVersion' (Decoder decode) action bs = traverse action (decode @V1 bs)
+
+instance {-# OVERLAPPABLE #-} (WithAnyVersion (Decr v V1) a c dec, dec (a v), dec (a (Decr v V1)), c (a v), c (a (Decr v V1)))
+  => WithAnyVersion v a c dec where
+    withAnyVersion' dec@(Decoder decode) action bs = do
+        res  <- traverse action (decode @v bs)
+        next <- withAnyVersion' @(Decr v V1) @a @c dec action bs
+        pure (res <!> next)
diff --git a/src/Versioning/Internal/Folding.hs b/src/Versioning/Internal/Folding.hs
new file mode 100644
--- /dev/null
+++ b/src/Versioning/Internal/Folding.hs
@@ -0,0 +1,41 @@
+{-# LANGUAGE DataKinds            #-}
+{-# LANGUAGE ExplicitNamespaces   #-}
+{-# LANGUAGE TypeFamilies         #-}
+{-# LANGUAGE TypeOperators        #-}
+{-# LANGUAGE UndecidableInstances #-}
+module Versioning.Internal.Folding
+  ( Decr
+  , Incr
+  )
+where
+
+import           GHC.TypeLits
+import           GHC.TypeNats    (type (-), type (+))
+
+import           Versioning.Base (V (..))
+
+-- | Decrement version until the target version is reached
+type family Decr (c :: V) (v :: V) :: V where
+    Decr ('V c) ('V v) = Decr' (CmpNat c v) ('V c)
+
+type family Decr' (o :: Ordering) (c :: V) :: V where
+    Decr' 'GT ('V c) = 'V (c - 1)
+    Decr' 'EQ ('V c) = 'V c
+    Decr' 'LT ('V c) = TypeError
+                       ( 'Text "Cannot decrement "
+                   ':<>: 'ShowType ('V c)
+                   ':<>: 'Text " because the target is a higher version"
+                       )
+
+-- | Increment version until the target version is reached
+type family Incr (c :: V) (v :: V) :: V where
+    Incr ('V c) ('V v) = Incr' (CmpNat c v) ('V c)
+
+type family Incr' (o :: Ordering) (c :: V) :: V where
+    Incr' 'LT ('V c) = 'V (c + 1)
+    Incr' 'EQ ('V c) = 'V c
+    Incr' 'GT ('V c) = TypeError
+                       ( 'Text "Cannot increment "
+                   ':<>: 'ShowType ('V c)
+                   ':<>: 'Text " because the target is a lower version"
+                       )
diff --git a/src/Versioning/JSON.hs b/src/Versioning/JSON.hs
--- a/src/Versioning/JSON.hs
+++ b/src/Versioning/JSON.hs
@@ -1,3 +1,4 @@
+-- | JSON-specific deserialization utilities.
 {-# LANGUAGE AllowAmbiguousTypes   #-}
 {-# LANGUAGE ConstraintKinds       #-}
 {-# LANGUAGE DataKinds             #-}
@@ -13,91 +14,145 @@
 {-# LANGUAGE UndecidableInstances  #-}
 module Versioning.JSON
   ( Applied
-  , DecodeAnyVersion
-  , WithAnyVersion
-  , decodeAnyVersion
-  , withAnyVersion
-  , withAnyVersionM
+  , JsonDecodableTo
+  , fromJsonAnyVersion
+  , fromJsonAnyVersionStrict
+  , fromJsonAnyVersionEither
+  , fromJsonAnyVersionEitherStrict
+  , withJsonAnyVersion
+  , withJsonAnyVersionStrict
+  , withJsonAnyVersionEither
+  , withJsonAnyVersionEitherStrict
+  , withJsonAnyVersionM
+  , withJsonAnyVersionStrictM
+  , withJsonAnyVersionEitherM
+  , withJsonAnyVersionEitherStrictM
   )
 where
 
-import           Control.Applicative   ((<|>))
-import           Data.Aeson            (FromJSON, decode)
-import qualified Data.ByteString.Lazy  as LazyBS
-import           Data.Functor.Identity (Identity (..))
-import           Data.Kind             (Constraint, Type)
-import           GHC.TypeLits
+import           Data.Aeson                   (FromJSON, decode, decodeStrict,
+                                               eitherDecode, eitherDecodeStrict)
+import qualified Data.ByteString              as StrictBS
+import qualified Data.ByteString.Lazy         as LazyBS
 
-import           Versioning.Base
-import           Versioning.Upgrade
+import           Versioning.Internal.Decoding
 
 -- | Decode a JSON string by trying all the versions decrementally
 --   and upgrade the decoded object to the newest version.
-decodeAnyVersion
-  :: forall v a . DecodeAnyVersion v v a => LazyBS.ByteString -> Maybe (a v)
-decodeAnyVersion = decodeAnyVersion' @v @v
+fromJsonAnyVersion
+  :: forall v a . JsonDecodableTo v a => LazyBS.ByteString -> Maybe (a v)
+fromJsonAnyVersion = decodeAnyVersion jsonDecode
 
+-- | Like 'fromJsonAnyVersion' but it reads from a strict 'ByteString'
+fromJsonAnyVersionStrict
+  :: forall v a . JsonDecodableTo v a => StrictBS.ByteString -> Maybe (a v)
+fromJsonAnyVersionStrict = decodeAnyVersion jsonDecodeStrict
+
+-- | Like 'fromJsonAnyVersion' but returns a message when decoding fails
+fromJsonAnyVersionEither
+  :: forall v a
+   . JsonDecodableTo v a
+  => LazyBS.ByteString
+  -> Either String (a v)
+fromJsonAnyVersionEither = decodeAnyVersion jsonEitherDecode
+
+-- | Like 'fromJsonAnyVersionStrict' but returns a message when decoding fails
+fromJsonAnyVersionEitherStrict
+  :: forall v a
+   . JsonDecodableTo v a
+  => StrictBS.ByteString
+  -> Either String (a v)
+fromJsonAnyVersionEitherStrict = decodeAnyVersion jsonEitherDecodeStrict
+
 -- | Decode a JSON string by trying all the versions decrementally
 --   and apply an action to the decoded object at its original version.
-withAnyVersionM
-  :: forall c v a m
-   . (WithAnyVersion v a c, Applicative m, c a v)
+withJsonAnyVersionM
+  :: forall c a v m
+   . (WithAnyVersion v a c FromJSON, Applicative m, c (a v))
   => ApplyM m a c
   -> LazyBS.ByteString
   -> m (Maybe (Applied c a))
-withAnyVersionM = withAnyVersion' @v @a @c
+withJsonAnyVersionM = withAnyVersionM @v @c @a jsonDecode
 
--- | Pure version of 'withAnyVersionM'.
-withAnyVersion
-  :: forall c v a
-   . (WithAnyVersion v a c, c a v)
-  => Apply a c
-  -> LazyBS.ByteString
-  -> Maybe (Applied c a)
-withAnyVersion action = runIdentity . withAnyVersionM @c @v @a (Identity . action)
+-- | Like 'withJsonAnyVersionM' but it reads from a strict 'ByteString'
+withJsonAnyVersionStrictM
+  :: forall c a v m
+   . (WithAnyVersion v a c FromJSON, Applicative m, c (a v))
+  => ApplyM m a c
+  -> StrictBS.ByteString
+  -> m (Maybe (Applied c a))
+withJsonAnyVersionStrictM = withAnyVersionM @v @c @a jsonDecodeStrict
 
--- | The result type of the action that has been applied to the decoded object
---   with 'withAnyVersion'.
-type family Applied (c :: (V -> Type) -> V -> Constraint) (a :: V -> Type) :: Type
+-- | Like 'withJsonAnyVersionM' but returns a message when decoding fails
+withJsonAnyVersionEitherM
+  :: forall c a v m
+   . (WithAnyVersion v a c FromJSON, Applicative m, c (a v))
+  => ApplyM m a c
+  -> LazyBS.ByteString
+  -> m (Either String (Applied c a))
+withJsonAnyVersionEitherM = withAnyVersionM @v @c @a jsonEitherDecode
 
-type ApplyM m a c = forall v. c a v => a v -> m (Applied c a)
+-- | Like 'withJsonAnyVersionStrictM' but returns a message when decoding fails
+withJsonAnyVersionEitherStrictM
+  :: forall c a v m
+   . (WithAnyVersion v a c FromJSON, Applicative m, c (a v))
+  => ApplyM m a c
+  -> StrictBS.ByteString
+  -> m (Either String (Applied c a))
+withJsonAnyVersionEitherStrictM = withAnyVersionM @v @c @a jsonEitherDecodeStrict
 
-type Apply a c = forall v. c a v => a v -> Applied c a
+-- | Decode a JSON string by trying all the versions decrementally
+--   and apply a pure function to the decoded object at its original version.
+withJsonAnyVersion
+  :: forall c a v
+   . (WithAnyVersion v a c FromJSON, c (a v))
+  => Apply a c
+  -> LazyBS.ByteString
+  -> Maybe (Applied c a)
+withJsonAnyVersion = withAnyVersion @v @c @a jsonDecode
 
-class DecodeAnyVersion (v :: V) (w :: V) (a :: V -> Type) where
-    decodeAnyVersion' :: LazyBS.ByteString -> Maybe (a w)
+-- | Like 'withJsonAnyVersion' but it reads from a strict 'ByteString'
+withJsonAnyVersionStrict
+  :: forall c a v
+   . (WithAnyVersion v a c FromJSON, c (a v))
+  => Apply a c
+  -> StrictBS.ByteString
+  -> Maybe (Applied c a)
+withJsonAnyVersionStrict = withAnyVersion @v @c @a jsonDecodeStrict
 
-instance {-# OVERLAPPING #-} (FromJSON (a V1), Upgrade V1 w a) => DecodeAnyVersion V1 w a where
-    decodeAnyVersion' bs = upgrade @V1 @w <$> decode @(a V1) bs
+-- | Like 'withJsonAnyVersion' but returns a message when decoding fails
+withJsonAnyVersionEither
+  :: forall c a v
+   . (WithAnyVersion v a c FromJSON, c (a v))
+  => Apply a c
+  -> LazyBS.ByteString
+  -> Either String (Applied c a)
+withJsonAnyVersionEither = withAnyVersion @v @c @a jsonEitherDecode
 
-instance {-# OVERLAPPABLE #-} (DecodeAnyVersion (Decr v V1) w a, FromJSON (a v), FromJSON (a (Decr v V1)), Upgrade v w a)
-  => DecodeAnyVersion v w a where
-    decodeAnyVersion' bs = upgrade @v @w <$> decode @(a v) bs
-                       <|> decodeAnyVersion' @(Decr v V1) @w bs
+-- | Like 'withJsonAnyVersionStrict' but returns a message when decoding fails
+withJsonAnyVersionEitherStrict
+  :: forall c a v
+   . (WithAnyVersion v a c FromJSON, c (a v))
+  => Apply a c
+  -> StrictBS.ByteString
+  -> Either String (Applied c a)
+withJsonAnyVersionEitherStrict = withAnyVersion @v @c @a jsonEitherDecodeStrict
 
-class WithAnyVersion (v :: V) (a :: V -> Type) c where
-    withAnyVersion' :: (Applicative m, c a v) => ApplyM m a c -> LazyBS.ByteString -> m (Maybe (Applied c a))
+-- | Decode with the aeson 'decode' function
+jsonDecode :: Decoder FromJSON LazyBS.ByteString Maybe a
+jsonDecode = Decoder decode
 
-instance {-# OVERLAPPING #-} (FromJSON (a V1), c a V1) => WithAnyVersion V1 a c where
-    withAnyVersion' action bs = case decode @(a V1) bs of
-        Just doc -> Just <$> action doc
-        Nothing  -> pure Nothing
+-- | Decode with the aeson 'decodeStrict' function
+jsonDecodeStrict :: Decoder FromJSON StrictBS.ByteString Maybe a
+jsonDecodeStrict = Decoder decodeStrict
 
-instance {-# OVERLAPPABLE #-} (WithAnyVersion (Decr v V1) a c, FromJSON (a v), FromJSON (a (Decr v V1)), c a v, c a (Decr v V1))
-  => WithAnyVersion v a c where
-    withAnyVersion' action bs = case decode @(a v) bs of
-        Just doc -> Just <$> action doc
-        Nothing  -> withAnyVersion' @(Decr v V1) @a @c action bs
+-- | Decode with the aeson 'eitherDecode' function
+jsonEitherDecode :: Decoder FromJSON LazyBS.ByteString (Either String) a
+jsonEitherDecode = Decoder eitherDecode
 
--- | Decrement version until the target version is reached
-type family Decr (c :: V) (v :: V) :: V where
-    Decr ('V c) ('V v) = Decr' (CmpNat c v) ('V c)
+-- | Decode with the aeson 'eitherDecodeStrict' function
+jsonEitherDecodeStrict :: Decoder FromJSON StrictBS.ByteString (Either String) a
+jsonEitherDecodeStrict = Decoder eitherDecodeStrict
 
-type family Decr' (o :: Ordering) (c :: V) :: V where
-    Decr' 'GT ('V c) = 'V (c - 1)
-    Decr' 'EQ ('V c) = 'V c
-    Decr' 'LT ('V c) = TypeError
-                       ( 'Text "Cannot decrement "
-                   ':<>: 'ShowType ('V c)
-                   ':<>: 'Text " because the target is a higher version"
-                       )
+-- | Handy constraint synonym to be used with 'fromJsonAnyVersion'
+type JsonDecodableTo v a = DecodableTo FromJSON v a
diff --git a/src/Versioning/Upgrade.hs b/src/Versioning/Upgrade.hs
--- a/src/Versioning/Upgrade.hs
+++ b/src/Versioning/Upgrade.hs
@@ -15,9 +15,9 @@
 where
 
 import           Data.Kind       (Type)
-import           GHC.TypeLits
 
 import           Versioning.Base
+import           Versioning.Internal.Folding (Incr)
 
 -- | Adapt from a version to another
 class Adapt (v :: V) (w :: V) (a :: V -> Type) where
@@ -42,16 +42,3 @@
 instance {-# OVERLAPPABLE #-} (Adapt v (Incr v w) a, Upgrade (Incr v w) w a)
   => Upgrade v w a where
     upgrade' x = upgrade' @(Incr v w) @w (adapt @v @(Incr v w) x)
-
--- | Increment version until the target version is reached
-type family Incr (c :: V) (v :: V) :: V where
-    Incr ('V c) ('V v) = Incr' (CmpNat c v) ('V c)
-
-type family Incr' (o :: Ordering) (c :: V) :: V where
-    Incr' 'LT ('V c) = 'V (c + 1)
-    Incr' 'EQ ('V c) = 'V c
-    Incr' 'GT ('V c) = TypeError
-                       ( 'Text "Cannot increment "
-                   ':<>: 'ShowType ('V c)
-                   ':<>: 'Text " because the target is a lower version"
-                       )
diff --git a/tests/Main.hs b/tests/Main.hs
--- a/tests/Main.hs
+++ b/tests/Main.hs
@@ -16,28 +16,32 @@
 import qualified Data.ByteString.Lazy  as LazyBS
 import           GHC.Generics          (Generic)
 import           Test.Hspec
+
 import           Versioning.Base
 import           Versioning.JSON
 import           Versioning.Upgrade
 
 main :: IO ()
 main = hspec $ do
+    describe "Versioning" $ do
+        it "Can get the version number of a record" $
+            versionNumber foo1 `shouldBe` 1
+
     describe "Upgrade" $ do
         it "Can upgrade across two versions" $
             upgrade @V1 foo1 `shouldBe` foo3
 
     describe "DecodeAnyVersion" $ do
         it "Can decode from V1" $
-            decodeAnyVersion @V3 fooJsonV1 `shouldBe` Just foo3
+            fromJsonAnyVersion @V3 fooJsonV1 `shouldBe` Just foo3
 
-    describe "DecodeAnyVersion" $ do
         it "Can decode from V3" $
-            decodeAnyVersion @V3 fooJsonV3 `shouldBe` Just foo3
+            fromJsonAnyVersion @V3 fooJsonV3 `shouldBe` Just foo3
 
     describe "WithAnyVersion" $ do
         -- Decode a Foo and return its string representation without upgrading it
         it "Can apply a function on the decoded object" $ do
-            let Just res = withAnyVersion @ShowAnyVersion @V3 @Foo showAnyVersion fooJsonV1
+            let Just res = withJsonAnyVersion @Show @Foo @V3 show fooJsonV1
             res `shouldBe` show foo1
 
 data Foo v = Foo
@@ -79,14 +83,7 @@
                     , untilV2 = na
                     }
 
-type instance Applied ShowAnyVersion a = String
-
--- | Show an object, whatever its version
-class ShowAnyVersion (a :: V -> *) (v :: V) where
-    showAnyVersion :: a v -> String
-
-instance Show (Foo v) => ShowAnyVersion Foo v where
-    showAnyVersion = show
+type instance Applied Show a = String
 
 -- | A 'Foo' at version V1
 foo1 :: Foo V1
diff --git a/versioning.cabal b/versioning.cabal
--- a/versioning.cabal
+++ b/versioning.cabal
@@ -1,5 +1,5 @@
 name:                versioning
-version:             0.1.0.0
+version:             0.2.0.0
 synopsis:            Type-safe data versioning.
 description:         This package provides various tools to deal with
                      data versioning in a type-safe way.
@@ -16,11 +16,15 @@
 
 library
   exposed-modules:     Versioning.Base
+                     , Versioning.Internal.Base
+                     , Versioning.Internal.Decoding
+                     , Versioning.Internal.Folding
                      , Versioning.Upgrade
                      , Versioning.JSON
-  build-depends:       base >=4.11 && <4.12
+  build-depends:       base >=4.10 && <4.12
                      , aeson >=1.0 && <1.5
                      , bytestring >=0.10 && <0.11
+                     , semigroupoids >=5 && <6
   hs-source-dirs:      src
   default-language:    Haskell2010
   ghc-options:       -Wall
@@ -28,7 +32,7 @@
 test-suite tests
   type:                exitcode-stdio-1.0
   main-is:             Main.hs
-  build-depends:       base >=4.11 && <4.12
+  build-depends:       base >=4.10 && <4.12
                      , aeson >=1.0 && <1.5
                      , bytestring >=0.10 && <0.11
                      , hspec >=2.0 && <2.6
