diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/rest-stringmap.cabal b/rest-stringmap.cabal
new file mode 100644
--- /dev/null
+++ b/rest-stringmap.cabal
@@ -0,0 +1,32 @@
+name:                rest-stringmap
+version:             0.1.0.0
+license:             BSD3
+synopsis:            Maps with stringy keys that can be transcoded to JSON and XML.
+description:         Maps with stringy keys that can be transcoded to JSON and XML.
+author:              Silk B.V.
+maintainer:          code@silk.co
+copyright:           2014 Silk B.V.
+category:            Silk
+build-type:          Simple
+cabal-version:       >=1.10
+
+library
+  default-language:  Haskell2010
+  ghc-options:       -Wall
+  hs-source-dirs:    src
+  exposed-modules:
+    Rest.StringMap.HashMap.Strict
+    Rest.StringMap.HashMap.Lazy
+    Rest.StringMap.Map.Lazy
+    Rest.StringMap.Map.Strict
+    Rest.StringMap.Util
+  build-depends:
+      base >= 4.6 && < 4.8
+    , aeson == 0.7.*
+    , containers == 0.5.*
+    , hashable == 1.2.*
+    , hxt == 9.3.*
+    , json-schema == 0.4.*
+    , text >= 0.10 && < 1.2
+    , tostring == 0.2.*
+    , unordered-containers == 0.2.*
diff --git a/src/Rest/StringMap/HashMap/Lazy.hs b/src/Rest/StringMap/HashMap/Lazy.hs
new file mode 100644
--- /dev/null
+++ b/src/Rest/StringMap/HashMap/Lazy.hs
@@ -0,0 +1,62 @@
+{-# LANGUAGE
+    DeriveDataTypeable
+  , FlexibleInstances
+  , OverlappingInstances
+  , ScopedTypeVariables
+  #-}
+module Rest.StringMap.HashMap.Lazy
+  ( StringHashMap
+  , fromHashMap
+  , toHashMap
+  , fromList
+  , toList
+  ) where
+
+import Data.Aeson
+import Data.HashMap.Strict (HashMap)
+import Data.Hashable
+import Data.JSON.Schema
+import Data.String
+import Data.String.ToString
+import Data.Typeable
+import Text.XML.HXT.Arrow.Pickle
+import qualified Data.HashMap.Lazy as H
+
+import Rest.StringMap.Util
+
+newtype StringHashMap a b = StringHashMap { unH :: HashMap a b }
+  deriving (Eq, Show, Typeable)
+
+fromHashMap :: HashMap a b -> StringHashMap a b
+fromHashMap = StringHashMap
+
+toHashMap :: StringHashMap a b -> HashMap a b
+toHashMap = unH
+
+fromList :: (Eq a, Hashable a) => [(a, b)] -> StringHashMap a b
+fromList = StringHashMap . H.fromList
+
+toList :: StringHashMap a b -> [(a, b)]
+toList = H.toList . unH
+
+-- | This is used with the assumption that a = b => f a = f b
+mapKeys ::  (Hashable l, Eq l) => (k -> l) -> StringHashMap k v -> StringHashMap l v
+mapKeys f = StringHashMap . H.foldlWithKey' (\h k v -> H.insert (f k) v h) H.empty . unH
+
+-- | Base case since XmlPickler expects strings
+instance XmlPickler b => XmlPickler (StringHashMap String b) where
+  xpickle = pickleStringMap fromList toList
+
+-- | General case
+instance (Eq a, Hashable a, IsString a, ToString a, XmlPickler b) => XmlPickler (StringHashMap a b) where
+  xpickle = pickleMap mapKeys mapKeys
+
+instance (ToString a, ToJSON b) => ToJSON (StringHashMap a b) where
+  toJSON = mapToJSON mapKeys
+
+instance (Hashable a, Eq a, IsString a, FromJSON b) => FromJSON (StringHashMap a b) where
+  parseJSON = mapParseJSON mapKeys
+
+instance JSONSchema b => JSONSchema (StringHashMap a b) where
+  schema _ = mapSchema (Proxy :: Proxy b)
+
diff --git a/src/Rest/StringMap/HashMap/Strict.hs b/src/Rest/StringMap/HashMap/Strict.hs
new file mode 100644
--- /dev/null
+++ b/src/Rest/StringMap/HashMap/Strict.hs
@@ -0,0 +1,61 @@
+{-# LANGUAGE
+    DeriveDataTypeable
+  , FlexibleInstances
+  , OverlappingInstances
+  , ScopedTypeVariables
+  #-}
+module Rest.StringMap.HashMap.Strict
+  ( StringHashMap
+  , fromHashMap
+  , toHashMap
+  , fromList
+  , toList
+  ) where
+
+import Data.Aeson
+import Data.HashMap.Strict (HashMap)
+import Data.Hashable
+import Data.JSON.Schema
+import Data.String
+import Data.String.ToString
+import Data.Typeable
+import Text.XML.HXT.Arrow.Pickle
+import qualified Data.HashMap.Strict as H
+
+import Rest.StringMap.Util
+
+newtype StringHashMap a b = StringHashMap { unH :: HashMap a b }
+  deriving (Eq, Show, Typeable)
+
+fromHashMap :: HashMap a b -> StringHashMap a b
+fromHashMap = StringHashMap
+
+toHashMap :: StringHashMap a b -> HashMap a b
+toHashMap = unH
+
+fromList :: (Eq a, Hashable a) => [(a, b)] -> StringHashMap a b
+fromList = StringHashMap . H.fromList
+
+toList :: StringHashMap a b -> [(a, b)]
+toList = H.toList . unH
+
+-- | This is used with the assumption that a = b => f a = f b
+mapKeys :: (Hashable l, Eq l) => (k -> l) -> StringHashMap k v -> StringHashMap l v
+mapKeys f = StringHashMap . H.foldlWithKey' (\h k v -> H.insert (f k) v h) H.empty . unH
+
+-- | Base case since XmlPickler expects strings
+instance XmlPickler b => XmlPickler (StringHashMap String b) where
+  xpickle = pickleStringMap fromList toList
+
+-- | General case
+instance (Eq a, Hashable a, IsString a, ToString a, XmlPickler b) => XmlPickler (StringHashMap a b) where
+  xpickle = pickleMap mapKeys mapKeys
+
+instance (ToString a, ToJSON b) => ToJSON (StringHashMap a b) where
+  toJSON = mapToJSON mapKeys
+
+instance (Hashable a, Eq a, IsString a, FromJSON b) => FromJSON (StringHashMap a b) where
+  parseJSON = mapParseJSON mapKeys
+
+instance JSONSchema b => JSONSchema (StringHashMap a b) where
+  schema _ = mapSchema (Proxy :: Proxy b)
diff --git a/src/Rest/StringMap/Map/Lazy.hs b/src/Rest/StringMap/Map/Lazy.hs
new file mode 100644
--- /dev/null
+++ b/src/Rest/StringMap/Map/Lazy.hs
@@ -0,0 +1,57 @@
+{-# LANGUAGE
+    DeriveDataTypeable
+  , FlexibleInstances
+  , OverlappingInstances
+  , ScopedTypeVariables
+  #-}
+module Rest.StringMap.Map.Lazy
+  ( StringMap
+  , fromMap
+  , toMap
+  , toList
+  , fromList
+  ) where
+
+import Data.Aeson
+import Data.JSON.Schema
+import Data.Map (Map)
+import Data.String
+import Data.String.ToString
+import Data.Typeable
+import Text.XML.HXT.Arrow.Pickle
+import qualified Data.Map as M
+
+import Rest.StringMap.Util
+
+newtype StringMap a b = StringMap { unM :: Map a b }
+  deriving (Eq, Show, Typeable)
+
+fromMap :: Map a b -> StringMap a b
+fromMap = StringMap
+
+toMap :: StringMap a b -> Map a b
+toMap = unM
+
+fromList :: Ord a => [(a,b)] -> StringMap a b
+fromList = StringMap . M.fromList
+
+toList :: StringMap a b -> [(a, b)]
+toList = M.toList . unM
+
+mapKeys :: Ord l => (k -> l) -> StringMap k v -> StringMap l v
+mapKeys f = StringMap . M.mapKeys f . unM
+
+instance XmlPickler b => XmlPickler (StringMap String b) where
+  xpickle = pickleStringMap fromList toList
+
+instance (Ord a, IsString a, ToString a, XmlPickler b) => XmlPickler (StringMap a b) where
+  xpickle = pickleMap mapKeys mapKeys
+
+instance (ToString a, ToJSON b) => ToJSON (StringMap a b) where
+  toJSON = toJSON . M.mapKeys toString . unM
+
+instance (Ord a, IsString a, FromJSON b) => FromJSON (StringMap a b) where
+  parseJSON = fmap (StringMap . M.mapKeys fromString) . parseJSON
+
+instance JSONSchema b => JSONSchema (StringMap a b) where
+  schema _ = mapSchema (Proxy :: Proxy b)
diff --git a/src/Rest/StringMap/Map/Strict.hs b/src/Rest/StringMap/Map/Strict.hs
new file mode 100644
--- /dev/null
+++ b/src/Rest/StringMap/Map/Strict.hs
@@ -0,0 +1,57 @@
+{-# LANGUAGE
+    DeriveDataTypeable
+  , FlexibleInstances
+  , OverlappingInstances
+  , ScopedTypeVariables
+  #-}
+module Rest.StringMap.Map.Strict
+  ( StringMap
+  , fromMap
+  , toMap
+  , toList
+  , fromList
+  ) where
+
+import Data.Aeson
+import Data.JSON.Schema
+import Data.Map.Strict (Map)
+import Data.String
+import Data.String.ToString
+import Data.Typeable
+import Text.XML.HXT.Arrow.Pickle
+import qualified Data.Map.Strict as M
+
+import Rest.StringMap.Util
+
+newtype StringMap a b = StringMap { unM :: Map a b }
+  deriving (Eq, Show, Typeable)
+
+fromMap :: Map a b -> StringMap a b
+fromMap = StringMap
+
+toMap :: StringMap a b -> Map a b
+toMap = unM
+
+fromList :: Ord a => [(a,b)] -> StringMap a b
+fromList = StringMap . M.fromList
+
+toList :: StringMap a b -> [(a, b)]
+toList = M.toList . unM
+
+mapKeys :: Ord l => (k -> l) -> StringMap k v -> StringMap l v
+mapKeys f = StringMap . M.mapKeys f . unM
+
+instance XmlPickler b => XmlPickler (StringMap String b) where
+  xpickle = pickleStringMap fromList toList
+
+instance (Ord a, IsString a, ToString a, XmlPickler b) => XmlPickler (StringMap a b) where
+  xpickle = pickleMap mapKeys mapKeys
+
+instance (ToString a, ToJSON b) => ToJSON (StringMap a b) where
+  toJSON = toJSON . M.mapKeys toString . unM
+
+instance (Ord a, IsString a, FromJSON b) => FromJSON (StringMap a b) where
+  parseJSON = fmap (StringMap . M.mapKeys fromString) . parseJSON
+
+instance JSONSchema b => JSONSchema (StringMap a b) where
+  schema _ = mapSchema (Proxy :: Proxy b)
diff --git a/src/Rest/StringMap/Util.hs b/src/Rest/StringMap/Util.hs
new file mode 100644
--- /dev/null
+++ b/src/Rest/StringMap/Util.hs
@@ -0,0 +1,33 @@
+module Rest.StringMap.Util
+  ( pickleStringMap
+  , pickleMap
+  , mapSchema
+  , mapToJSON
+  , mapParseJSON
+  ) where
+
+import Data.Aeson.Types
+import Data.JSON.Schema (JSONSchema, Schema, schema)
+import Data.JSON.Schema.Combinators (field)
+import Data.Proxy (Proxy)
+import Data.String (IsString (..))
+import Data.String.ToString (ToString (..))
+import Text.XML.HXT.Arrow.Pickle (PU, XmlPickler, xpElem, xpList, xpPair, xpTextAttr, xpWrap, xpickle)
+
+pickleStringMap :: XmlPickler b => ([(String, b)] -> m) -> (m -> [(String, b)]) -> PU m
+pickleStringMap fromList toList =
+  xpElem "map"
+    $ xpWrap (fromList, toList)
+    $ xpList (xpElem "value" (xpPair (xpTextAttr "key") xpickle))
+
+pickleMap :: (XmlPickler m, ToString k, IsString k) => ((String -> k) -> m -> m') -> ((k -> String) -> m' -> m) -> PU m'
+pickleMap mapKeys mapKeys' = xpWrap (mapKeys fromString, mapKeys' toString) xpickle
+
+mapSchema :: JSONSchema a => Proxy a -> Schema
+mapSchema = field "key" False . schema
+
+mapToJSON :: (ToString a, ToJSON m) => ((a -> String) -> m' -> m) -> m' -> Value
+mapToJSON mapKeys = toJSON . mapKeys toString
+
+mapParseJSON :: (FromJSON m, IsString k) => ((String -> k) -> m -> m') -> Value -> Parser m'
+mapParseJSON mapKeys = fmap (mapKeys fromString) . parseJSON
