diff --git a/data/parent-classes.json b/data/parent-classes.json
new file mode 100644
--- /dev/null
+++ b/data/parent-classes.json
@@ -0,0 +1,11 @@
+{
+  "TAGame.CarComponent_Boost_TA": "TAGame.CarComponent_TA",
+  "TAGame.CarComponent_Dodge_TA": "TAGame.CarComponent_TA",
+  "TAGame.CarComponent_DoubleJump_TA": "TAGame.CarComponent_TA",
+  "TAGame.CarComponent_FlipCar_TA": "TAGame.CarComponent_TA",
+  "TAGame.CarComponent_Jump_TA": "TAGame.CarComponent_TA",
+  "TAGame.SpecialPickup_BallGravity_TA": "TAGame.SpecialPickup_TA",
+  "TAGame.SpecialPickup_BallVelcro_TA": "TAGame.SpecialPickup_TA",
+  "TAGame.SpecialPickup_TA": "TAGame.CarComponent_TA",
+  "TAGame.SpecialPickup_Tornado_TA": "TAGame.SpecialPickup_TA"
+}
diff --git a/library/Octane/Data.hs b/library/Octane/Data.hs
--- a/library/Octane/Data.hs
+++ b/library/Octane/Data.hs
@@ -27,6 +27,13 @@
 gameModes :: Bimap.Bimap Int StrictText.Text
 gameModes = Embed.decodeBimap $(FileEmbed.embedFile "data/game-modes.json")
 
+-- | A mapping between classes and their parent classes. Note that not every
+-- class is present in this map. Only classes that are sometimes misrepresented
+-- in the class property map are in this mapping. See #37 for details.
+parentClasses :: Map.Map StrictText.Text StrictText.Text
+parentClasses =
+  Embed.decodeMap $(FileEmbed.embedFile "data/parent-classes.json")
+
 -- | A one-to-one mapping between product IDs and their names.
 products :: Bimap.Bimap Word StrictText.Text
 products = Embed.decodeBimap $(FileEmbed.embedFile "data/products.json")
diff --git a/library/Octane/Utility/ClassPropertyMap.hs b/library/Octane/Utility/ClassPropertyMap.hs
--- a/library/Octane/Utility/ClassPropertyMap.hs
+++ b/library/Octane/Utility/ClassPropertyMap.hs
@@ -13,11 +13,13 @@
 
 import Data.Function ((&))
 
+import qualified Data.Bimap as Bimap
 import qualified Data.IntMap.Strict as IntMap
 import qualified Data.List as List
 import qualified Data.Map.Strict as Map
 import qualified Data.Maybe as Maybe
 import qualified Data.Text as StrictText
+import qualified Octane.Data as Data
 import qualified Octane.Type.ReplayWithoutFrames as Replay
 import qualified Octane.Type.Word32 as Word32
 import qualified "regex-compat" Text.Regex as Regex
@@ -51,32 +53,47 @@
           in (classId, properties)) &
      IntMap.fromList
 
--- | The class cache is a list of 3-tuples where the first element is a class
--- ID, the second is its cache ID, and the third is its parent's cache ID.
-getClassCache :: Replay.ReplayWithoutFrames -> [(Int, Int, Int)]
-getClassCache replay =
+-- | The class cache is a list of 4-tuples where the first element is a class
+-- ID, the second is its name, the third is its cache ID, the fourth is its
+-- parent's cache ID.
+getClassCache :: Replay.ReplayWithoutFrames
+              -> [(Int, StrictText.Text, Int, Int)]
+getClassCache replay = do
+  let classNames = replay & getActorMap & Bimap.toMapR
   replay & #cache & #unpack &
-  map
-    (\x ->
-       ( x & #classId & Word32.fromWord32
-       , x & #cacheId & Word32.fromWord32
-       , x & #parentCacheId & Word32.fromWord32))
+    Maybe.mapMaybe
+      (\cacheItem -> do
+         let classId = cacheItem & #classId & Word32.fromWord32
+         className <- Map.lookup classId classNames
+         let cacheId = cacheItem & #cacheId & Word32.fromWord32
+         let parentCacheId = cacheItem & #parentCacheId & Word32.fromWord32
+         pure (classId, className, cacheId, parentCacheId))
 
 -- | The class IDs in a replay. Comes from the class cache.
 getClassIds :: Replay.ReplayWithoutFrames -> [Int]
-getClassIds replay = replay & getClassCache & map (\(x, _, _) -> x)
+getClassIds replay = replay & getClassCache & map (\(x, _, _, _) -> x)
 
 -- | Gets the parent class ID for the given parent cache ID. This is necessary
 -- because there is not always a class with the given cache ID in the cache.
 -- When that happens, the parent cache ID is decremented and tried again.
-getParentClassId :: Int -> [(Int, Int, Int)] -> Maybe Int
-getParentClassId parentCacheId xs =
-  case dropWhile (\(_, cacheId, _) -> cacheId /= parentCacheId) xs of
-    [] ->
-      if parentCacheId <= 0
-        then Nothing
-        else getParentClassId (parentCacheId - 1) xs
-    (parentClassId, _, _):_ -> Just parentClassId
+getParentClassId :: StrictText.Text
+                 -> Int
+                 -> [(Int, StrictText.Text, Int, Int)]
+                 -> Maybe Int
+getParentClassId className parentCacheId xs =
+  case Map.lookup className Data.parentClasses of
+    Just parentClassName ->
+      xs & filter (\(_, name, _, _) -> name == parentClassName) &
+      filter (\(_, _, cacheId, _) -> cacheId == parentCacheId) &
+      map (\(classId, _, _, _) -> classId) &
+      Maybe.listToMaybe
+    Nothing ->
+      case dropWhile (\(_, _, cacheId, _) -> cacheId /= parentCacheId) xs of
+        [] ->
+          if parentCacheId <= 0
+            then Nothing
+            else getParentClassId className (parentCacheId - 1) xs
+        (parentClassId, _, _, _):_ -> Just parentClassId
 
 -- | The basic class map is a naive mapping from class ID to its parent class
 -- ID. It's naive because it only maps the class ID to its immediate parent.
@@ -88,8 +105,8 @@
     (\xs ->
        case xs of
          [] -> Nothing
-         (classId, _, parentCacheId):ys -> do
-           parentClassId <- getParentClassId parentCacheId ys
+         (classId, className, _, parentCacheId):ys -> do
+           parentClassId <- getParentClassId className parentCacheId ys
            pure (classId, parentClassId)) &
   IntMap.fromList
 
@@ -140,7 +157,7 @@
      IntMap.fromList
 
 -- | The actor map is a mapping from class names to their IDs.
-getActorMap :: Replay.ReplayWithoutFrames -> Map.Map StrictText.Text Int
+getActorMap :: Replay.ReplayWithoutFrames -> Bimap.Bimap StrictText.Text Int
 getActorMap replay =
   replay & #classes & #unpack &
   map
@@ -148,7 +165,7 @@
        let className = x & #name & #unpack
            classId = x & #streamId & Word32.fromWord32
        in (className, classId)) &
-  Map.fromList
+  Bimap.fromList
 
 -- | Gets the class ID and name for a given property ID.
 getClass
diff --git a/library/Octane/Utility/Parser.hs b/library/Octane/Utility/Parser.hs
--- a/library/Octane/Utility/Parser.hs
+++ b/library/Octane/Utility/Parser.hs
@@ -18,6 +18,7 @@
 
 import qualified Control.DeepSeq as DeepSeq
 import qualified Control.Monad as Monad
+import qualified Data.Bimap as Bimap
 import qualified Data.Binary.Bits as BinaryBit
 import qualified Data.Binary.Bits.Get as BinaryBit
 import qualified Data.Binary.Get as Binary
@@ -111,7 +112,7 @@
     (CPM.getPropertyMap replay)
     (CPM.getClassPropertyMap replay)
     IntMap.empty
-    (CPM.getActorMap replay)
+    (replay & CPM.getActorMap & Bimap.toMap)
     keyFrames
     version
 
diff --git a/octane.cabal b/octane.cabal
--- a/octane.cabal
+++ b/octane.cabal
@@ -3,7 +3,7 @@
 -- see: https://github.com/sol/hpack
 
 name:           octane
-version:        0.16.0
+version:        0.16.1
 synopsis:       Parse Rocket League replays.
 description:    Octane parses Rocket League replays.
 category:       Game
@@ -21,6 +21,7 @@
     data/classes-with-rotation.json
     data/classes.json
     data/game-modes.json
+    data/parent-classes.json
     data/products.json
     data/properties.json
     package.yaml
diff --git a/package.yaml b/package.yaml
--- a/package.yaml
+++ b/package.yaml
@@ -80,4 +80,4 @@
     - -with-rtsopts=-N
     main: Main.hs
     source-dirs: test-suite
-version: '0.16.0'
+version: '0.16.1'
