diff --git a/htest/Test/Ganeti/Objects.hs b/htest/Test/Ganeti/Objects.hs
index 6dc0af93cffb22cfb7b45c388f41e1c946728ff7..de21d0c15d4450186493465c41231dd6eaa3a624 100644
--- a/htest/Test/Ganeti/Objects.hs
+++ b/htest/Test/Ganeti/Objects.hs
@@ -102,9 +102,9 @@ instance Arbitrary Instance where
       <$> getFQDN <*> getFQDN <*> getFQDN -- OS name, but...
       <*> arbitrary
       -- FIXME: add non-empty hvparams when they're a proper type
-      <*> pure (Container Map.empty) <*> arbitrary
+      <*> pure (GenericContainer Map.empty) <*> arbitrary
       -- ... and for OSParams
-      <*> pure (Container Map.empty) <*> arbitrary <*> arbitrary
+      <*> pure (GenericContainer Map.empty) <*> arbitrary <*> arbitrary
       <*> arbitrary <*> arbitrary <*> arbitrary
       -- ts
       <*> arbitrary <*> arbitrary
@@ -127,7 +127,7 @@ $(genArbitrary ''PartialIPolicy)
 -- validation rules.
 instance Arbitrary NodeGroup where
   arbitrary = NodeGroup <$> getFQDN <*> pure [] <*> arbitrary <*> arbitrary
-                        <*> arbitrary <*> pure (Container Map.empty)
+                        <*> arbitrary <*> pure (GenericContainer Map.empty)
                         -- ts
                         <*> arbitrary <*> arbitrary
                         -- uuid
@@ -146,23 +146,23 @@ $(genArbitrary ''FilledBeParams)
 
 -- | No real arbitrary instance for 'ClusterHvParams' yet.
 instance Arbitrary ClusterHvParams where
-  arbitrary = return $ Container Map.empty
+  arbitrary = return $ GenericContainer Map.empty
 
 -- | No real arbitrary instance for 'OsHvParams' yet.
 instance Arbitrary OsHvParams where
-  arbitrary = return $ Container Map.empty
+  arbitrary = return $ GenericContainer Map.empty
 
 instance Arbitrary ClusterNicParams where
-  arbitrary = (Container . Map.singleton C.ppDefault) <$> arbitrary
+  arbitrary = (GenericContainer . Map.singleton C.ppDefault) <$> arbitrary
 
 instance Arbitrary OsParams where
-  arbitrary = (Container . Map.fromList) <$> arbitrary
+  arbitrary = (GenericContainer . Map.fromList) <$> arbitrary
 
 instance Arbitrary ClusterOsParams where
-  arbitrary = (Container . Map.fromList) <$> arbitrary
+  arbitrary = (GenericContainer . Map.fromList) <$> arbitrary
 
 instance Arbitrary ClusterBeParams where
-  arbitrary = (Container . Map.fromList) <$> arbitrary
+  arbitrary = (GenericContainer . Map.fromList) <$> arbitrary
 
 instance Arbitrary TagSet where
   arbitrary = Set.fromList <$> genTags
@@ -179,10 +179,11 @@ genEmptyCluster ncount = do
       nodes' = zipWith (\n idx -> n { nodeGroup = guuid,
                                       nodeName = nodeName n ++ show idx })
                nodes [(1::Int)..]
-      contnodes = Container . Map.fromList $ map (\n -> (nodeName n, n)) nodes'
-      continsts = Container Map.empty
+      contnodes = GenericContainer . Map.fromList $
+                  map (\n -> (nodeName n, n)) nodes'
+      continsts = GenericContainer Map.empty
   grp <- arbitrary
-  let contgroups = Container $ Map.singleton guuid grp
+  let contgroups = GenericContainer $ Map.singleton guuid grp
   serial <- arbitrary
   cluster <- resize 8 arbitrary
   let c = ConfigData version cluster contnodes contgroups continsts serial
diff --git a/htools/Ganeti/Config.hs b/htools/Ganeti/Config.hs
index 62582735c453f6b033fa386adb011ff596f1486a..c2c6929057513839c0aac7330511960834c0bddd 100644
--- a/htools/Ganeti/Config.hs
+++ b/htools/Ganeti/Config.hs
@@ -192,7 +192,7 @@ getGroupIpolicy cfg ng =
 -- | Computes a group\'s (merged) disk params.
 getGroupDiskParams :: ConfigData -> NodeGroup -> DiskParams
 getGroupDiskParams cfg ng =
-  Container $
+  GenericContainer $
   fillDict (fromContainer . clusterDiskparams $ configCluster cfg)
            (fromContainer $ groupDiskparams ng) []
 
diff --git a/htools/Ganeti/JSON.hs b/htools/Ganeti/JSON.hs
index 1480140e8b552ebdd1c960e89d116eb0510cb7c4..1138898344a64c5d4d94407b3f2dce784146f421 100644
--- a/htools/Ganeti/JSON.hs
+++ b/htools/Ganeti/JSON.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE TypeSynonymInstances, FlexibleInstances #-}
 {-| JSON utility functions. -}
 
 {-
@@ -37,11 +38,12 @@ module Ganeti.JSON
   , asObjectList
   , tryFromObj
   , toArray
-  , Container(..)
+  , HasStringRepr(..)
+  , GenericContainer(..)
+  , Container
   )
   where
 
-import Control.Arrow (second)
 import Control.Monad (liftM)
 import Data.Maybe (fromMaybe)
 import qualified Data.Map as Map
@@ -167,24 +169,48 @@ toArray o =
 
 -- * Container type (special type for JSON serialisation)
 
+-- | Class of types that can be converted from Strings. This is
+-- similar to the 'Read' class, but it's using a different
+-- serialisation format, so we have to define a separate class. Mostly
+-- useful for custom key types in JSON dictionaries, which have to be
+-- backed by strings.
+class HasStringRepr a where
+  fromStringRepr :: (Monad m) => String -> m a
+  toStringRepr :: a -> String
+
+-- | Trivial instance 'HasStringRepr' for 'String'.
+instance HasStringRepr String where
+  fromStringRepr = return
+  toStringRepr = id
+
 -- | The container type, a wrapper over Data.Map
-newtype Container a = Container { fromContainer :: Map.Map String a }
+newtype GenericContainer a b =
+  GenericContainer { fromContainer :: Map.Map a b }
   deriving (Show, Read, Eq)
 
+-- | Type alias for string keys.
+type Container = GenericContainer String
+
 -- | Container loader.
-readContainer :: (Monad m, J.JSON a) =>
-                 J.JSObject J.JSValue -> m (Container a)
+readContainer :: (Monad m, HasStringRepr a, Ord a, J.JSON b) =>
+                 J.JSObject J.JSValue -> m (GenericContainer a b)
 readContainer obj = do
   let kjvlist = J.fromJSObject obj
-  kalist <- mapM (\(k, v) -> fromKeyValue k v >>= \a -> return (k, a)) kjvlist
-  return $ Container (Map.fromList kalist)
+  kalist <- mapM (\(k, v) -> do
+                    k' <- fromStringRepr k
+                    v' <- fromKeyValue k v
+                    return (k', v')) kjvlist
+  return $ GenericContainer (Map.fromList kalist)
 
 -- | Container dumper.
-showContainer :: (J.JSON a) => Container a -> J.JSValue
+showContainer :: (HasStringRepr a, J.JSON b) =>
+                 GenericContainer a b -> J.JSValue
 showContainer =
-  J.makeObj . map (second J.showJSON) . Map.toList . fromContainer
+  J.makeObj . map (\(k, v) -> (toStringRepr k, J.showJSON v)) .
+  Map.toList . fromContainer
 
-instance (J.JSON a) => J.JSON (Container a) where
+instance (HasStringRepr a, Ord a, J.JSON b) =>
+         J.JSON (GenericContainer a b) where
   showJSON = showContainer
   readJSON (J.JSObject o) = readContainer o
   readJSON v = fail $ "Failed to load container, expected object but got "
diff --git a/htools/Ganeti/Types.hs b/htools/Ganeti/Types.hs
index a31cf62ade9f9d71286c7223d780d8926c14819a..99eceae14c7f02d56ebfaed7790f4c17bdd08be9 100644
--- a/htools/Ganeti/Types.hs
+++ b/htools/Ganeti/Types.hs
@@ -60,6 +60,7 @@ import qualified Text.JSON as JSON
 
 import qualified Ganeti.Constants as C
 import qualified Ganeti.THH as THH
+import Ganeti.JSON
 
 -- * Generic types
 
@@ -118,6 +119,10 @@ $(THH.declareSADT "DiskTemplate"
        ])
 $(THH.makeJSONInstance ''DiskTemplate)
 
+instance HasStringRepr DiskTemplate where
+  fromStringRepr = diskTemplateFromRaw
+  toStringRepr = diskTemplateToRaw
+
 -- | The Group allocation policy type.
 --
 -- Note that the order of constructors is important as the automatic