diff --git a/lib/src/core/core.dart b/lib/src/core/core.dart
index d01462b..d8a9d08 100644
--- a/lib/src/core/core.dart
+++ b/lib/src/core/core.dart
@@ -1,9 +1,15 @@
+import 'dart:collection';
+
 export 'package:rive/src/animation_list.dart';
 export 'package:rive/src/state_machine_components.dart';
 export 'package:rive/src/state_transition_conditions.dart';
 export 'package:rive/src/container_children.dart';
 export 'package:rive/src/runtime_artboard.dart';
 export 'package:rive/src/generated/rive_core_context.dart';
+export 'package:rive/src/core/importers/artboard_importer.dart';
+export 'package:rive/src/core/importers/linear_animation_importer.dart';
+export 'package:rive/src/core/importers/keyed_object_importer.dart';
+export 'package:rive/src/core/importers/keyed_property_importer.dart';
 
 typedef PropertyChangeCallback = void Function(dynamic from, dynamic to);
 typedef BatchAddCallback = void Function();
@@ -18,6 +24,7 @@ abstract class Core<T extends CoreContext> {
   void onAdded();
   void onRemoved() {}
   void remove() => context?.removeObject(this);
+  bool import(ImportStack stack) => true;
 }
 
 abstract class CoreContext {
@@ -30,3 +37,41 @@ abstract class CoreContext {
   void markNeedsAdvance();
   void dirty(void Function() dirt);
 }
+
+// ignore: one_member_abstracts
+abstract class ImportStackObject {
+  void resolve();
+}
+
+/// Interface to help objects that need to parent themselves to some other
+/// object previously imported.
+abstract class ImportHelper<T extends Core<CoreContext>> {
+  bool import(T object, ImportStack stack) => true;
+}
+
+/// Stack to help the RiveFile locate latest ImportStackObject created of a
+/// certain type.
+class ImportStack {
+  final _latests = HashMap<int, ImportStackObject>();
+  T latest<T extends ImportStackObject>(int coreType) {
+    var latest = _latests[coreType];
+    if (latest is T) {
+      return latest;
+    }
+    return null;
+  }
+
+  void makeLatest(int coreType, ImportStackObject importObject) {
+    if (importObject != null) {
+      _latests[coreType] = importObject;
+    } else {
+      _latests.remove(coreType);
+    }
+  }
+
+  void resolve() {
+    for (final object in _latests.values) {
+      object.resolve();
+    }
+  }
+}
diff --git a/lib/src/core/importers/artboard_importer.dart b/lib/src/core/importers/artboard_importer.dart
new file mode 100644
index 0000000..171960b
--- /dev/null
+++ b/lib/src/core/importers/artboard_importer.dart
@@ -0,0 +1,34 @@
+import 'package:rive/rive.dart';
+import 'package:rive/src/core/core.dart';
+import 'package:rive/src/rive_core/component.dart';
+
+class ArtboardImporter extends ImportStackObject {
+  final RuntimeArtboard artboard;
+  ArtboardImporter(this.artboard);
+
+  void addComponent(Core<CoreContext> object) => artboard.addObject(object);
+
+  void addAnimation(LinearAnimation animation) {
+    artboard.addObject(animation);
+    animation.artboard = artboard;
+  }
+
+  @override
+  void resolve() {
+    for (final object in artboard.objects.skip(1)) {
+      if (object is Component && object.parentId == null) {
+        object.parent = artboard;
+      }
+      object?.onAddedDirty();
+    }
+    assert(!artboard.children.contains(artboard),
+        'artboard should never contain itself as a child');
+    for (final object in artboard.objects.toList(growable: false)) {
+      if (object == null) {
+        continue;
+      }
+      object.onAdded();
+    }
+    artboard.clean();
+  }
+}
diff --git a/lib/src/core/importers/keyed_object_importer.dart b/lib/src/core/importers/keyed_object_importer.dart
new file mode 100644
index 0000000..06f97e6
--- /dev/null
+++ b/lib/src/core/importers/keyed_object_importer.dart
@@ -0,0 +1,17 @@
+import 'package:rive/src/core/core.dart';
+import 'package:rive/src/rive_core/animation/keyed_object.dart';
+import 'package:rive/src/rive_core/animation/keyed_property.dart';
+
+class KeyedObjectImporter extends ImportStackObject {
+  final KeyedObject keyedObject;
+
+  KeyedObjectImporter(this.keyedObject);
+
+  void addKeyedProperty(KeyedProperty property) {
+    keyedObject.context.addObject(property);
+    keyedObject.internalAddKeyedProperty(property);
+  }
+
+  @override
+  void resolve() {}
+}
diff --git a/lib/src/core/importers/keyed_property_importer.dart b/lib/src/core/importers/keyed_property_importer.dart
new file mode 100644
index 0000000..515343d
--- /dev/null
+++ b/lib/src/core/importers/keyed_property_importer.dart
@@ -0,0 +1,20 @@
+import 'package:rive/src/core/core.dart';
+import 'package:rive/src/rive_core/animation/keyed_property.dart';
+import 'package:rive/src/rive_core/animation/keyframe.dart';
+import 'package:rive/src/rive_core/animation/linear_animation.dart';
+
+class KeyedPropertyImporter extends ImportStackObject {
+  final KeyedProperty keyedProperty;
+  final LinearAnimation animation;
+
+  KeyedPropertyImporter(this.keyedProperty, this.animation);
+
+  void addKeyFrame(KeyFrame keyFrame) {
+    keyedProperty.context.addObject(keyFrame);
+    keyedProperty.internalAddKeyFrame(keyFrame);
+    keyFrame.computeSeconds(animation);
+  }
+
+  @override
+  void resolve() {}
+}
diff --git a/lib/src/core/importers/linear_animation_importer.dart b/lib/src/core/importers/linear_animation_importer.dart
new file mode 100644
index 0000000..fc56910
--- /dev/null
+++ b/lib/src/core/importers/linear_animation_importer.dart
@@ -0,0 +1,24 @@
+import 'package:rive/rive.dart';
+import 'package:rive/src/core/core.dart';
+import 'package:rive/src/rive_core/animation/keyed_object.dart';
+
+class LinearAnimationImporter extends ImportStackObject {
+  final LinearAnimation linearAnimation;
+  final keyedObjects = <KeyedObject>[];
+
+  LinearAnimationImporter(this.linearAnimation);
+
+  void addKeyedObject(KeyedObject object) {
+    linearAnimation.context.addObject(object);
+    
+    keyedObjects.add(object);
+    linearAnimation.internalAddKeyedObject(object);
+  }
+
+  @override
+  void resolve() {
+    for (final keyedObject in keyedObjects) {
+      keyedObject?.objectId ??= linearAnimation.artboard.id;
+    }
+  }
+}
diff --git a/lib/src/rive_core/animation/cubic_interpolator.dart b/lib/src/rive_core/animation/cubic_interpolator.dart
index 430fb6b..85935a5 100644
--- a/lib/src/rive_core/animation/cubic_interpolator.dart
+++ b/lib/src/rive_core/animation/cubic_interpolator.dart
@@ -1,5 +1,7 @@
 import 'dart:typed_data';
+import 'package:rive/src/core/core.dart';
 import 'package:rive/src/rive_core/animation/interpolator.dart';
+import 'package:rive/src/rive_core/artboard.dart';
 import 'package:rive/src/generated/animation/cubic_interpolator_base.dart';
 
 const int newtonIterations = 4;
@@ -41,8 +43,6 @@ class CubicInterpolator extends CubicInterpolatorBase implements Interpolator {
   @override
   void onAddedDirty() {}
   @override
-  void onRemoved() {}
-  @override
   double transform(double value) => _ease.transform(value);
   @override
   void x1Changed(double from, double to) => _updateStoredCubic();
@@ -55,6 +55,16 @@ class CubicInterpolator extends CubicInterpolatorBase implements Interpolator {
   void _updateStoredCubic() {
     _ease = _CubicEase.make(x1, y1, x2, y2);
   }
+
+  @override
+  bool import(ImportStack stack) {
+    var artboardHelper = stack.latest<ArtboardImporter>(ArtboardBase.typeKey);
+    if (artboardHelper == null) {
+      return false;
+    }
+    artboardHelper.addComponent(this);
+    return super.import(stack);
+  }
 }
 
 class _Cubic extends _CubicEase {
diff --git a/lib/src/rive_core/animation/keyed_object.dart b/lib/src/rive_core/animation/keyed_object.dart
index 3f48f3a..b2d0dd4 100644
--- a/lib/src/rive_core/animation/keyed_object.dart
+++ b/lib/src/rive_core/animation/keyed_object.dart
@@ -2,6 +2,7 @@ import 'dart:collection';
 import 'package:rive/src/core/core.dart';
 import 'package:rive/src/rive_core/animation/keyed_property.dart';
 import 'package:rive/src/generated/animation/keyed_object_base.dart';
+import 'linear_animation.dart';
 export 'package:rive/src/generated/animation/keyed_object_base.dart';
 
 class KeyedObject extends KeyedObjectBase<RuntimeArtboard> {
@@ -46,4 +47,14 @@ class KeyedObject extends KeyedObjectBase<RuntimeArtboard> {
 
   @override
   void objectIdChanged(int from, int to) {}
+  @override
+  bool import(ImportStack stack) {
+    var animationHelper =
+        stack.latest<LinearAnimationImporter>(LinearAnimationBase.typeKey);
+    if (animationHelper == null) {
+      return false;
+    }
+    animationHelper.addKeyedObject(this);
+    return super.import(stack);
+  }
 }
diff --git a/lib/src/rive_core/animation/keyed_property.dart b/lib/src/rive_core/animation/keyed_property.dart
index 8b9e58b..a8a8060 100644
--- a/lib/src/rive_core/animation/keyed_property.dart
+++ b/lib/src/rive_core/animation/keyed_property.dart
@@ -1,4 +1,5 @@
 import 'package:rive/src/core/core.dart';
+import 'package:rive/src/rive_core/animation/keyed_object.dart';
 import 'package:rive/src/rive_core/animation/keyframe.dart';
 import 'package:rive/src/generated/animation/keyed_property_base.dart';
 export 'package:rive/src/generated/animation/keyed_property_base.dart';
@@ -149,4 +150,13 @@ class KeyedProperty extends KeyedPropertyBase<RuntimeArtboard>
 
   @override
   void propertyKeyChanged(int from, int to) {}
+  @override
+  bool import(ImportStack stack) {
+    var importer = stack.latest<KeyedObjectImporter>(KeyedObjectBase.typeKey);
+    if (importer == null) {
+      return false;
+    }
+    importer.addKeyedProperty(this);
+    return super.import(stack);
+  }
 }
diff --git a/lib/src/rive_core/animation/keyframe.dart b/lib/src/rive_core/animation/keyframe.dart
index 84aedd3..391bb9c 100644
--- a/lib/src/rive_core/animation/keyframe.dart
+++ b/lib/src/rive_core/animation/keyframe.dart
@@ -57,4 +57,15 @@ abstract class KeyFrame extends KeyFrameBase<RuntimeArtboard>
     _interpolator = value;
     interpolatorId = value?.id;
   }
+
+  @override
+  bool import(ImportStack importStack) {
+    var keyedPropertyHelper =
+        importStack.latest<KeyedPropertyImporter>(KeyedPropertyBase.typeKey);
+    if (keyedPropertyHelper == null) {
+      return false;
+    }
+    keyedPropertyHelper.addKeyFrame(this);
+    return super.import(importStack);
+  }
 }
diff --git a/lib/src/rive_core/animation/linear_animation.dart b/lib/src/rive_core/animation/linear_animation.dart
index 92c1162..c79e12e 100644
--- a/lib/src/rive_core/animation/linear_animation.dart
+++ b/lib/src/rive_core/animation/linear_animation.dart
@@ -2,6 +2,7 @@ import 'dart:collection';
 import 'package:rive/src/core/core.dart';
 import 'package:rive/src/rive_core/animation/keyed_object.dart';
 import 'package:rive/src/rive_core/animation/loop.dart';
+import 'package:rive/src/rive_core/artboard.dart';
 import 'package:rive/src/generated/animation/linear_animation_base.dart';
 export 'package:rive/src/generated/animation/linear_animation_base.dart';
 
@@ -44,4 +45,13 @@ class LinearAnimation extends LinearAnimationBase {
   void workEndChanged(int from, int to) {}
   @override
   void workStartChanged(int from, int to) {}
+  @override
+  bool import(ImportStack stack) {
+    var artboardImporter = stack.latest<ArtboardImporter>(ArtboardBase.typeKey);
+    if (artboardImporter == null) {
+      return false;
+    }
+    artboardImporter.addAnimation(this);
+    return super.import(stack);
+  }
 }
diff --git a/lib/src/rive_core/component.dart b/lib/src/rive_core/component.dart
index a9d4682..9858f94 100644
--- a/lib/src/rive_core/component.dart
+++ b/lib/src/rive_core/component.dart
@@ -171,4 +171,13 @@ abstract class Component extends ComponentBase<RuntimeArtboard>
 
   @override
   void nameChanged(String from, String to) {}
+  @override
+  bool import(ImportStack stack) {
+    var artboardImporter = stack.latest<ArtboardImporter>(ArtboardBase.typeKey);
+    if (artboardImporter == null) {
+      return false;
+    }
+    artboardImporter.addComponent(this);
+    return super.import(stack);
+  }
 }
diff --git a/lib/src/rive_file.dart b/lib/src/rive_file.dart
index cef8b95..3946e93 100644
--- a/lib/src/rive_file.dart
+++ b/lib/src/rive_file.dart
@@ -1,7 +1,11 @@
 import 'dart:collection';
 import 'dart:typed_data';
 
+import 'package:meta/meta.dart';
 import 'package:rive/src/core/field_types/core_field_type.dart';
+import 'package:rive/src/generated/animation/keyed_property_base.dart';
+import 'package:rive/src/generated/animation/state_machine_base.dart';
+import 'package:rive/src/rive_core/animation/keyed_property.dart';
 import 'package:rive/src/rive_core/component.dart';
 import 'package:rive/src/rive_core/runtime/runtime_header.dart';
 import 'package:rive/src/rive_core/backboard.dart';
@@ -10,8 +14,6 @@ import 'package:rive/src/utilities/binary_buffer/binary_reader.dart';
 import 'package:rive/src/rive_core/runtime/exceptions/rive_format_error_exception.dart';
 import 'package:rive/src/rive_core/animation/animation.dart';
 import 'package:rive/src/rive_core/animation/keyed_object.dart';
-import 'package:rive/src/rive_core/animation/keyed_property.dart';
-import 'package:rive/src/rive_core/animation/keyframe.dart';
 import 'package:rive/src/rive_core/animation/linear_animation.dart';
 import 'package:rive/src/rive_core/artboard.dart';
 
@@ -59,108 +61,168 @@ class RiveFile {
 
       propertyToField[key] = indexToField[fieldIndex];
     });
-
-    _backboard = _readRuntimeObject<Backboard>(reader, propertyToField);
-    if (_backboard == null) {
-      throw const RiveFormatErrorException(
-          'expected first object to be a Backboard');
-    }
-
-    int numArtboards = reader.readVarUint();
-    for (int i = 0; i < numArtboards; i++) {
-      var numObjects = reader.readVarUint();
-      if (numObjects == 0) {
-        throw const RiveFormatErrorException(
-            'artboards must contain at least one object (themselves)');
+    var importStack = ImportStack();
+    while (!reader.isEOF) {
+      var object = _readRuntimeObject(reader, propertyToField);
+      if (object == null) {
+        continue;
       }
-      var artboard =
-          _readRuntimeObject(reader, propertyToField, RuntimeArtboard());
-      // Kind of weird, but the artboard is the core context at runtime, so we
-      // want other objects to be able to resolve it. It's always at the 0
-      // index.
-      artboard?.addObject(artboard);
-      _artboards.add(artboard);
-      // var objects = List<Core<RiveCoreContext>>(numObjects);
-      for (int i = 1; i < numObjects; i++) {
-        Core<CoreContext> object = _readRuntimeObject(reader, propertyToField);
-        // N.B. we add objects that don't load (null) too as we need to look
-        // them up by index.
-        artboard.addObject(object);
+      var previousOfType = importStack.latest(object.coreType);
+      if (previousOfType != null) {
+        previousOfType.resolve();
       }
 
-      // Animations also need to reference objects, so make sure they get read
-      // in before the hierarchy resolves (batch add completes).
-      var numAnimations = reader.readVarUint();
-      for (int i = 0; i < numAnimations; i++) {
-        var animation = _readRuntimeObject<Animation>(reader, propertyToField);
-        if (animation == null) {
-          continue;
-        }
-        artboard.addObject(animation);
-        animation.artboard = artboard;
-        if (animation is LinearAnimation) {
-          var numKeyedObjects = reader.readVarUint();
-          var keyedObjects = List<KeyedObject>.filled(numKeyedObjects, null);
-          for (int j = 0; j < numKeyedObjects; j++) {
-            var keyedObject =
-                _readRuntimeObject<KeyedObject>(reader, propertyToField);
-            if (keyedObject == null) {
-              continue;
-            }
-            keyedObjects[j] = keyedObject;
-            artboard.addObject(keyedObject);
-
-            animation.internalAddKeyedObject(keyedObject);
-
-            var numKeyedProperties = reader.readVarUint();
-            for (int k = 0; k < numKeyedProperties; k++) {
-              var keyedProperty =
-                  _readRuntimeObject<KeyedProperty>(reader, propertyToField);
-              if (keyedProperty == null) {
-                continue;
-              }
-              artboard.addObject(keyedProperty);
-              keyedObject.internalAddKeyedProperty(keyedProperty);
-
-              var numKeyframes = reader.readVarUint();
-              for (int l = 0; l < numKeyframes; l++) {
-                var keyframe =
-                    _readRuntimeObject<KeyFrame>(reader, propertyToField);
-                if (keyframe == null) {
-                  continue;
-                }
-                artboard.addObject(keyframe);
-                keyedProperty.internalAddKeyFrame(keyframe);
-                keyframe.computeSeconds(animation);
-              }
+      ImportStackObject stackObject;
+      switch (object.coreType) {
+        case ArtboardBase.typeKey:
+          stackObject = ArtboardImporter(object as RuntimeArtboard);
+          break;
+        case LinearAnimationBase.typeKey:
+          stackObject = LinearAnimationImporter(object as LinearAnimation);
+          // helper = _AnimationImportHelper();
+          break;
+        case KeyedObjectBase.typeKey:
+          stackObject = KeyedObjectImporter(object as KeyedObject);
+          break;
+        case KeyedPropertyBase.typeKey:
+          {
+            // KeyedProperty importer requires a linear animation importer, so
+            // make sure there's one on the stack.
+            var linearAnimationImporter = importStack
+                .latest<LinearAnimationImporter>(LinearAnimationBase.typeKey);
+            if (linearAnimationImporter != null) {
+              stackObject = KeyedPropertyImporter(object as KeyedProperty,
+                  linearAnimationImporter.linearAnimation);
             }
+            break;
           }
-
-          for (final keyedObject in keyedObjects) {
-            keyedObject?.objectId ??= artboard.id;
+        case StateMachineBase.typeKey:
+          // stackObject = _LinearAnimationStackObject(object as LinearAnimation);
+          // helper = _AnimationImportHelper();
+          break;
+        default:
+          if (object is Component) {
+            // helper = _ArtboardObjectImportHelper();
           }
-        }
+          break;
       }
 
-      // Any component objects with no id map to the artboard. Skip first item
-      // as it's the artboard itself.
-      for (final object in artboard.objects.skip(1)) {
-        if (object is Component && object.parentId == null) {
-          object.parent = artboard;
-        }
-        object?.onAddedDirty();
-      }
+      importStack.makeLatest(object.coreType, stackObject);
 
-      assert(!artboard.children.contains(artboard),
-          'artboard should never contain itself as a child');
-      for (final object in artboard.objects.toList(growable: false)) {
-        if (object == null) {
-          continue;
+      if (object?.import(importStack) ?? true) {
+        switch (object.coreType) {
+          case ArtboardBase.typeKey:
+            _artboards.add(object as Artboard);
+            break;
+          case BackboardBase.typeKey:
+            assert(_backboard == null, 'expect only one backboard in the file');
+            _backboard = object as Backboard;
+            break;
         }
-        object.onAdded();
       }
-      artboard.clean();
     }
+    importStack.resolve();
+
+    // _backboard = _readRuntimeObject<Backboard>(reader, propertyToField);
+    // if (_backboard == null) {
+    //   throw const RiveFormatErrorException(
+    //       'expected first object to be a Backboard');
+    // }
+
+    // int numArtboards = reader.readVarUint();
+    // for (int i = 0; i < numArtboards; i++) {
+    //   var numObjects = reader.readVarUint();
+    //   if (numObjects == 0) {
+    //     throw const RiveFormatErrorException(
+    //         'artboards must contain at least one object (themselves)');
+    //   }
+    //   var artboard =
+    //       _readRuntimeObject(reader, propertyToField, RuntimeArtboard());
+    //   // Kind of weird, but the artboard is the core context at runtime, so we
+    //   // want other objects to be able to resolve it. It's always at the 0
+    //   // index.
+    //   artboard?.addObject(artboard);
+    //   _artboards.add(artboard);
+    //   // var objects = List<Core<RiveCoreContext>>(numObjects);
+    //   for (int i = 1; i < numObjects; i++) {
+    //     Core<CoreContext> object = _readRuntimeObject(reader, propertyToField);
+    //     // N.B. we add objects that don't load (null) too as we need to look
+    //     // them up by index.
+    //     artboard.addObject(object);
+    //   }
+
+    //   // Animations also need to reference objects, so make sure they get read
+    //   // in before the hierarchy resolves (batch add completes).
+    //   var numAnimations = reader.readVarUint();
+    //   for (int i = 0; i < numAnimations; i++) {
+    //     var animation = _readRuntimeObject<Animation>(reader, propertyToField);
+    //     if (animation == null) {
+    //       continue;
+    //     }
+    //     artboard.addObject(animation);
+    //     animation.artboard = artboard;
+    //     if (animation is LinearAnimation) {
+    //       var numKeyedObjects = reader.readVarUint();
+    //       var keyedObjects = List<KeyedObject>.filled(numKeyedObjects, null);
+    //       for (int j = 0; j < numKeyedObjects; j++) {
+    //         var keyedObject =
+    //             _readRuntimeObject<KeyedObject>(reader, propertyToField);
+    //         if (keyedObject == null) {
+    //           continue;
+    //         }
+    //         keyedObjects[j] = keyedObject;
+    //         artboard.addObject(keyedObject);
+
+    //         animation.internalAddKeyedObject(keyedObject);
+
+    //         var numKeyedProperties = reader.readVarUint();
+    //         for (int k = 0; k < numKeyedProperties; k++) {
+    //           var keyedProperty =
+    //               _readRuntimeObject<KeyedProperty>(reader, propertyToField);
+    //           if (keyedProperty == null) {
+    //             continue;
+    //           }
+    //           artboard.addObject(keyedProperty);
+    //           keyedObject.internalAddKeyedProperty(keyedProperty);
+
+    //           var numKeyframes = reader.readVarUint();
+    //           for (int l = 0; l < numKeyframes; l++) {
+    //             var keyframe =
+    //                 _readRuntimeObject<KeyFrame>(reader, propertyToField);
+    //             if (keyframe == null) {
+    //               continue;
+    //             }
+    //             artboard.addObject(keyframe);
+    //             keyedProperty.internalAddKeyFrame(keyframe);
+    //             keyframe.computeSeconds(animation);
+    //           }
+    //         }
+    //       }
+
+    //       for (final keyedObject in keyedObjects) {
+    //         keyedObject?.objectId ??= artboard.id;
+    //       }
+    //     }
+    //   }
+
+    //   // Any component objects with no id map to the artboard. Skip first item
+    //   // as it's the artboard itself.
+    //   for (final object in artboard.objects.skip(1)) {
+    //     if (object is Component && object.parentId == null) {
+    //       object.parent = artboard;
+    //     }
+    //     object?.onAddedDirty();
+    //   }
+
+    //   assert(!artboard.children.contains(artboard),
+    //       'artboard should never contain itself as a child');
+    //   for (final object in artboard.objects.toList(growable: false)) {
+    //     if (object == null) {
+    //       continue;
+    //     }
+    //     object.onAdded();
+    //   }
+    //   artboard.clean();
 
     return true;
   }
@@ -178,11 +240,16 @@ void _skipProperty(BinaryReader reader, int propertyKey,
   field.deserialize(reader);
 }
 
-T _readRuntimeObject<T extends Core<CoreContext>>(
-    BinaryReader reader, HashMap<int, CoreFieldType> propertyToField,
-    [T instance]) {
+Core<CoreContext> _readRuntimeObject(
+    BinaryReader reader, HashMap<int, CoreFieldType> propertyToField) {
   int coreObjectKey = reader.readVarUint();
 
+  Core<CoreContext> instance;
+  switch (coreObjectKey) {
+    case ArtboardBase.typeKey:
+      instance = RuntimeArtboard();
+      break;
+  }
   var object = instance ?? RiveCoreContext.makeCoreInstance(coreObjectKey);
 
   while (true) {
@@ -200,5 +267,94 @@ T _readRuntimeObject<T extends Core<CoreContext>>(
           object, propertyKey, fieldType.deserialize(reader));
     }
   }
-  return object as T;
+  return object;
+}
+
+class _ArtboardImportStackObject extends ImportStackObject {
+  final RuntimeArtboard artboard;
+  _ArtboardImportStackObject(this.artboard);
+
+  void addObject(Core<CoreContext> object) => artboard.addObject(object);
+
+  @override
+  void resolve() {
+    for (final object in artboard.objects.skip(1)) {
+      if (object is Component && object.parentId == null) {
+        object.parent = artboard;
+      }
+      object?.onAddedDirty();
+    }
+    assert(!artboard.children.contains(artboard),
+        'artboard should never contain itself as a child');
+    for (final object in artboard.objects.toList(growable: false)) {
+      if (object == null) {
+        continue;
+      }
+      object.onAdded();
+    }
+    artboard.clean();
+  }
+}
+
+class _ArtboardObjectImportHelper<T extends Core<CoreContext>>
+    extends ImportHelper<T> {
+  @override
+  bool import(T object, ImportStack stack) {
+    _ArtboardImportStackObject artboardStackObject =
+        stack.latest(ArtboardBase.typeKey);
+    if (artboardStackObject == null) {
+      return false;
+    }
+    artboardStackObject.addObject(object);
+    withArtboard(object, artboardStackObject);
+    return true;
+  }
+
+  @protected
+  void withArtboard(T object, _ArtboardImportStackObject artboardStackObject) {}
+}
+
+class _AnimationImportHelper extends _ArtboardObjectImportHelper<Animation> {
+  @override
+  void withArtboard(
+      Animation object, _ArtboardImportStackObject artboardStackObject) {
+    object.artboard = artboardStackObject.artboard;
+  }
+}
+
+class _LinearAnimationStackObject extends ImportStackObject {
+  final LinearAnimation linearAnimation;
+  final keyedObjects = <KeyedObject>[];
+
+  _LinearAnimationStackObject(this.linearAnimation);
+
+  void addKeyedObject(KeyedObject object) {
+    keyedObjects.add(object);
+    linearAnimation.internalAddKeyedObject(object);
+  }
+
+  @override
+  void resolve() {
+    for (final keyedObject in keyedObjects) {
+      keyedObject?.objectId ??= linearAnimation.artboard.id;
+    }
+  }
+}
+
+class _KeyedObjectImportHelper
+    extends _ArtboardObjectImportHelper<KeyedObject> {
+  @override
+  bool import(KeyedObject object, ImportStack stack) {
+    if (!super.import(object, stack)) {
+      return false;
+    }
+    _LinearAnimationStackObject animationStackObject =
+        stack.latest(LinearAnimationBase.typeKey);
+    if (animationStackObject == null) {
+      return false;
+    }
+    animationStackObject.addKeyedObject(object);
+
+    return true;
+  }
 }