diff --git a/country.cabal b/country.cabal
--- a/country.cabal
+++ b/country.cabal
@@ -1,5 +1,5 @@
 name: country
-version: 0.1.1
+version: 0.1.2
 synopsis: Country data type and functions
 description:
   The `country` library provides a data type for dealing with
@@ -46,6 +46,8 @@
     , unordered-containers >= 0.2 && < 0.3
     , ghc-prim >= 0.5 && < 0.6
     , hashable >= 1.2 && < 1.3
+    , aeson >= 0.11 && < 1.3
+    , scientific >= 0.3 && < 0.4
   default-language: Haskell2010
 
 test-suite test
diff --git a/src/Country.hs b/src/Country.hs
--- a/src/Country.hs
+++ b/src/Country.hs
@@ -1,6 +1,8 @@
 {-# LANGUAGE BangPatterns #-}
 {-# LANGUAGE MagicHash #-}
 
+{-# OPTIONS_GHC -Wall #-}
+
 module Country
   ( Country
     -- * Three digit code
@@ -20,8 +22,7 @@
 
 import Country.Unsafe (Country(..))
 import Country.Unexposed.Encode.English (countryNameQuads)
-import Country.Unexposed.ExtraNames (extraNames)
-import Country.Unexposed.Names (englishCountryNamesText,numberOfPossibleCodes)
+import Country.Unexposed.Names (englishCountryNamesText,numberOfPossibleCodes,alphaTwoHashMap,alphaThreeHashMap,decodeMap,decodeNumeric,encodeEnglish)
 import Country.Unexposed.Enumerate (enumeratedCountries)
 import Data.Text (Text)
 import Data.ByteString (ByteString)
@@ -39,7 +40,6 @@
 import Data.Bits (unsafeShiftL,unsafeShiftR)
 import qualified Data.List as L
 import qualified Data.HashMap.Strict as HM
-import qualified Data.Text as T
 import qualified Data.Text.Array as TA
 import qualified Data.Text.Internal as TI
 
@@ -48,18 +48,7 @@
 encodeNumeric :: Country -> Word16
 encodeNumeric (Country n) = n
 
--- | Get a country from a numeric code. Any code greater than
---   999 will not have a country associated with it. Additionally,
---   many codes are unassigned.
-decodeNumeric :: Word16 -> Maybe Country
-decodeNumeric n = if n < 1000 && indexByteArray numericValidities (word16ToInt n) == (1 :: Word8)
-  then Just (Country n)
-  else Nothing
 
--- | The name of a country given in English
-encodeEnglish :: Country -> Text
-encodeEnglish (Country n) = indexArray englishCountryNamesText (word16ToInt n)
-
 -- | The alpha-2 country code, uppercase
 alphaTwoUpper :: Country -> Text
 alphaTwoUpper c = TI.text allAlphaTwoUpper (timesTwo (indexOfCountry c)) 2
@@ -82,26 +71,6 @@
 decodeAlphaThree :: Text -> Maybe Country
 decodeAlphaThree = flip HM.lookup alphaThreeHashMap
 
-alphaTwoHashMap :: HashMap Text Country
-alphaTwoHashMap = L.foldl'
-  (\hm (countryNum,_,(c1,c2),_) ->
-      HM.insert (T.pack [c1,c2]) (Country countryNum)
-    $ HM.insert (T.pack [toLower c1, toLower c2]) (Country countryNum)
-    $ hm
-  )
-  HM.empty countryNameQuads
-{-# NOINLINE alphaTwoHashMap #-}
-
-alphaThreeHashMap :: HashMap Text Country
-alphaThreeHashMap = L.foldl'
-  (\hm (countryNum,_,_,(c1,c2,c3)) -> 
-      HM.insert (T.pack [c1,c2,c3]) (Country countryNum)
-    $ HM.insert (T.pack [toLower c1, toLower c2, toLower c3]) (Country countryNum)
-    $ hm
-  )
-  HM.empty countryNameQuads
-{-# NOINLINE alphaThreeHashMap #-}
-
 half :: Int -> Int
 half x = unsafeShiftR x 1
 
@@ -129,15 +98,6 @@
 word16ToChar :: Word16 -> Char
 word16ToChar = chr . fromIntegral
 
-
-decodeMap :: HashMap Text Country
-decodeMap = 
-  let baseMap = HM.union alphaTwoHashMap alphaThreeHashMap
-      hm1 = L.foldl' (\hm (country,name) -> HM.insert name country hm) baseMap extraNames
-      hm2 = L.foldl' (\hm (countryNum,name,_,_) -> HM.insert name (Country countryNum) hm) hm1 countryNameQuads
-   in hm2
-{-# NOINLINE decodeMap #-}
-
 arrayFoldl' :: (a -> b -> a) -> a -> Array b -> a
 arrayFoldl' f z a = go 0 z
   where
@@ -150,19 +110,6 @@
 
 numberOfCountries :: Int
 numberOfCountries = length countryNameQuads
-
--- | The elements in this array are Word8 (basically boolean)
-numericValidities :: ByteArray
-numericValidities = runST $ do
-  m <- newByteArray numberOfPossibleCodes
-  let clear !ix = if ix < numberOfPossibleCodes
-        then writeByteArray m ix (0 :: Word8)
-        else return ()
-  clear 0
-  forM_ countryNameQuads $ \(n,_,_,_) -> do
-    writeByteArray m (word16ToInt n) (1 :: Word8)
-  unsafeFreezeByteArray m
-{-# NOINLINE numericValidities #-}
 
 -- | The elements in this array are Word16
 positions :: ByteArray
diff --git a/src/Country/Unexposed/ExtraNames.hs b/src/Country/Unexposed/ExtraNames.hs
--- a/src/Country/Unexposed/ExtraNames.hs
+++ b/src/Country/Unexposed/ExtraNames.hs
@@ -1,22 +1,11 @@
 {-# LANGUAGE OverloadedStrings #-}
 
 module Country.Unexposed.ExtraNames
-  ( extraNames
-  ) where
+  ( ) where
 
 import Country.Unsafe (Country)
 import Country.Identifier
 import Data.Text (Text)
 import Data.Primitive (indexArray,newArray,unsafeFreezeArray,writeArray,
   writeByteArray,indexByteArray,unsafeFreezeByteArray,newByteArray)
-
-extraNames :: [(Country,Text)]
-extraNames =
-  [ (unitedStatesOfAmerica,"United States")
-  , (unitedStatesOfAmerica,"USA")
-  , (unitedStatesOfAmerica,"U.S.A.")
-  , (mexico,"Estados Unidos Mexicanos")
-  , (mexico,"México")
-  , (mexico,"Méjico")
-  ]
 
diff --git a/src/Country/Unexposed/Names.hs b/src/Country/Unexposed/Names.hs
--- a/src/Country/Unexposed/Names.hs
+++ b/src/Country/Unexposed/Names.hs
@@ -1,9 +1,36 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE MagicHash #-}
+{-# LANGUAGE UnboxedTuples #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+
+{-# OPTIONS_HADDOCK not-home #-}
+
 module Country.Unexposed.Names
   ( englishCountryNamesText
   , englishIdentifierNamesText
   , numberOfPossibleCodes
+  , decodeMap
+  , alphaTwoHashMap
+  , alphaThreeHashMap
+  , decodeNumeric
+  , encodeEnglish
+  , Country(..)
   ) where
 
+import Data.Word (Word16)
+import Data.Hashable (Hashable)
+import Data.Primitive.Types (Prim)
+import Data.HashMap.Strict (HashMap)
+import Data.Primitive (indexArray)
+import Data.Primitive.Array (Array(..))
+import Data.Primitive.ByteArray (ByteArray(..))
+import Control.Monad
+import qualified Data.Text as T
+import qualified Data.Aeson as AE
+import qualified Data.Aeson.Types as AET
+import qualified Data.HashMap.Strict as HM
+import qualified Data.List as L
 import Control.Monad.ST
 import Data.Text (Text)
 import Data.Word
@@ -12,7 +39,44 @@
 import Data.Primitive (Array,indexArray,newArray,unsafeFreezeArray,writeArray,
   writeByteArray,indexByteArray,unsafeFreezeByteArray,newByteArray)
 import qualified Data.Text as T
+import qualified Data.Scientific as SCI
 
+-- | The name of a country given in English
+encodeEnglish :: Country -> Text
+encodeEnglish (Country n) = indexArray englishCountryNamesText (word16ToInt n)
+
+mexico :: Country
+mexico = Country 484
+
+unitedStatesOfAmerica :: Country
+unitedStatesOfAmerica = Country 840
+
+ålandIslands :: Country
+ålandIslands = Country 248
+
+venezuelaBolivarianRepublicOf :: Country
+venezuelaBolivarianRepublicOf = Country 862
+
+boliviaPlurinationalStateOf :: Country
+boliviaPlurinationalStateOf = Country 68
+
+extraNames :: [(Country,Text)]
+extraNames =
+  [ (unitedStatesOfAmerica,"United States")
+  , (unitedStatesOfAmerica,"The United States")
+  , (unitedStatesOfAmerica,"USA")
+  , (unitedStatesOfAmerica,"U.S.A.")
+  , (unitedStatesOfAmerica,"Estados Unidos de América")
+  , (mexico,"Estados Unidos Mexicanos")
+  , (mexico,"México")
+  , (mexico,"Méjico")
+  , (ålandIslands,"Aland Islands")
+  , (venezuelaBolivarianRepublicOf,"Venezuela")
+  , (venezuelaBolivarianRepublicOf,"Bolivarian Republic of Venezuela")
+  , (boliviaPlurinationalStateOf,"Bolivia")
+  , (boliviaPlurinationalStateOf,"Plurinational State of Bolivia")
+  ]
+
 englishCountryNamesText :: Array Text
 englishCountryNamesText = runST $ do
   m <- newArray numberOfPossibleCodes unnamed
@@ -42,4 +106,79 @@
 
 word16ToInt :: Word16 -> Int
 word16ToInt = fromIntegral
+
+decodeMap :: HashMap Text Country
+decodeMap = 
+  let baseMap = HM.union alphaTwoHashMap alphaThreeHashMap
+      hm1 = L.foldl' (\hm (country,name) -> HM.insert name country hm) baseMap extraNames
+      hm2 = L.foldl' (\hm (countryNum,name,_,_) -> HM.insert name (Country countryNum) hm) hm1 countryNameQuads
+   in hm2
+{-# NOINLINE decodeMap #-}
+
+-- | A country recognized by ISO 3166.
+newtype Country = Country Word16
+  deriving (Eq,Ord,Prim,Hashable)
+
+instance Show Country where
+  show (Country n) = T.unpack (indexArray englishIdentifierNamesText (word16ToInt n))
+
+-- todo: add support for encoding directly to bytestring.
+-- Also, add suport for ToJSONKey and FromJSONKey once everything
+-- finally gets off of aeson-0.11 (looking at you, reflex-platform)
+instance AE.ToJSON Country where
+  toJSON = AET.String . encodeEnglish
+
+instance AE.FromJSON Country where
+  parseJSON x = case x of
+    AET.String t -> case HM.lookup t decodeMap of
+      Nothing -> fail $ "invalid country name " ++ T.unpack t
+      Just country -> return country
+    AET.Number n -> case SCI.toBoundedInteger n of
+      Nothing -> fail errMsg
+      Just w -> case decodeNumeric w of
+        Just c -> return c
+        Nothing -> fail errMsg
+      where errMsg = fail $ "invalid country code " ++ show n
+    _ -> AET.typeMismatch "Country" x
+
+-- | Get a country from a numeric code. Any code greater than
+--   999 will not have a country associated with it. Additionally,
+--   many codes are unassigned.
+decodeNumeric :: Word16 -> Maybe Country
+decodeNumeric n = if n < 1000 && indexByteArray numericValidities (word16ToInt n) == (1 :: Word8)
+  then Just (Country n)
+  else Nothing
+
+-- | The elements in this array are Word8 (basically boolean)
+numericValidities :: ByteArray
+numericValidities = runST $ do
+  m <- newByteArray numberOfPossibleCodes
+  let clear !ix = if ix < numberOfPossibleCodes
+        then writeByteArray m ix (0 :: Word8)
+        else return ()
+  clear 0
+  forM_ countryNameQuads $ \(n,_,_,_) -> do
+    writeByteArray m (word16ToInt n) (1 :: Word8)
+  unsafeFreezeByteArray m
+{-# NOINLINE numericValidities #-}
+
+alphaTwoHashMap :: HashMap Text Country
+alphaTwoHashMap = L.foldl'
+  (\hm (countryNum,_,(c1,c2),_) ->
+      HM.insert (T.pack [c1,c2]) (Country countryNum)
+    $ HM.insert (T.pack [toLower c1, toLower c2]) (Country countryNum)
+    $ hm
+  )
+  HM.empty countryNameQuads
+{-# NOINLINE alphaTwoHashMap #-}
+
+alphaThreeHashMap :: HashMap Text Country
+alphaThreeHashMap = L.foldl'
+  (\hm (countryNum,_,_,(c1,c2,c3)) -> 
+      HM.insert (T.pack [c1,c2,c3]) (Country countryNum)
+    $ HM.insert (T.pack [toLower c1, toLower c2, toLower c3]) (Country countryNum)
+    $ hm
+  )
+  HM.empty countryNameQuads
+{-# NOINLINE alphaThreeHashMap #-}
 
diff --git a/src/Country/Unsafe.hs b/src/Country/Unsafe.hs
--- a/src/Country/Unsafe.hs
+++ b/src/Country/Unsafe.hs
@@ -14,20 +14,5 @@
   ( Country(..)
   ) where
 
-import Data.Word (Word16)
-import Data.Hashable (Hashable)
-import Data.Primitive.Types (Prim)
-import Data.Primitive (indexArray)
-import Country.Unexposed.Names (englishIdentifierNamesText)
-import qualified Data.Text as T
-
--- | A country recognized by ISO 3166.
-newtype Country = Country Word16
-  deriving (Eq,Ord,Prim,Hashable)
-
-instance Show Country where
-  show (Country n) = T.unpack (indexArray englishIdentifierNamesText (word16ToInt n))
-
-word16ToInt :: Word16 -> Int
-word16ToInt = fromIntegral
+import Country.Unexposed.Names (Country(..))
 
