[skottie] Switch to SkJSON

Convert to SkJSON APIs and remove RapidJSON dependency.

TBR=
Change-Id: I5b4fd78821a443326e3a5b370748d840b80ef279
Reviewed-on: https://skia-review.googlesource.com/134612
Reviewed-by: Mike Klein <mtklein@google.com>
Commit-Queue: Florin Malita <fmalita@chromium.org>
diff --git a/DEPS b/DEPS
index 4cac345..a0925bc 100644
--- a/DEPS
+++ b/DEPS
@@ -12,7 +12,7 @@
   "third_party/externals/harfbuzz"        : "https://skia.googlesource.com/third_party/harfbuzz.git@1.4.2",
   "third_party/externals/icu"             : "https://chromium.googlesource.com/chromium/deps/icu.git@ec9c1133693148470ffe2e5e53576998e3650c1d",
   "third_party/externals/imgui"           : "https://skia.googlesource.com/external/github.com/ocornut/imgui.git@6384eee34f08cb7eab8d835043e1738e4adcdf75",
-  # TODO: remove jsoncpp after migrating clients to rapidjson
+  # TODO: remove jsoncpp after migrating clients to SkJSON
   "third_party/externals/jsoncpp"         : "https://chromium.googlesource.com/external/github.com/open-source-parsers/jsoncpp.git@1.0.0",
   "third_party/externals/libjpeg-turbo"   : "https://skia.googlesource.com/external/github.com/libjpeg-turbo/libjpeg-turbo.git@1.5.3",
   "third_party/externals/libpng"          : "https://skia.googlesource.com/third_party/libpng.git@v1.6.33",
@@ -21,7 +21,6 @@
   "third_party/externals/microhttpd"      : "https://android.googlesource.com/platform/external/libmicrohttpd@748945ec6f1c67b7efc934ab0808e1d32f2fb98d",
   "third_party/externals/opengl-registry" : "https://skia.googlesource.com/external/github.com/KhronosGroup/OpenGL-Registry@14b80ebeab022b2c78f84a573f01028c96075553",
   "third_party/externals/piex"            : "https://android.googlesource.com/platform/external/piex.git@bb217acdca1cc0c16b704669dd6f91a1b509c406",
-  "third_party/externals/rapidjson"       : "https://skia.googlesource.com/external/github.com/Tencent/rapidjson.git@af223d44f4e8d3772cb1ac0ce8bc2a132b51717f",
   "third_party/externals/sdl"             : "https://skia.googlesource.com/third_party/sdl@5d7cfcca344034aff9327f77fc181ae3754e7a90",
   "third_party/externals/sfntly"          : "https://chromium.googlesource.com/external/github.com/googlei18n/sfntly.git@b18b09b6114b9b7fe6fc2f96d8b15e8a72f66916",
   "third_party/externals/spirv-headers"   : "https://skia.googlesource.com/external/github.com/KhronosGroup/SPIRV-Headers.git@661ad91124e6af2272afd00f804d8aa276e17107",
diff --git a/modules/skjson/BUILD.gn b/modules/skjson/BUILD.gn
index 531f0e0..dd8ca65 100644
--- a/modules/skjson/BUILD.gn
+++ b/modules/skjson/BUILD.gn
@@ -65,7 +65,6 @@
     deps = [
       ":skjson",
       "../..:skia",
-      "../../third_party/rapidjson",
     ]
   }
 }
diff --git a/modules/skjson/src/SkJSONBench.cpp b/modules/skjson/src/SkJSONBench.cpp
index b9c3664..4ef1d6c 100644
--- a/modules/skjson/src/SkJSONBench.cpp
+++ b/modules/skjson/src/SkJSONBench.cpp
@@ -55,7 +55,7 @@
 
 DEF_BENCH( return new JsonBench; )
 
-#if !defined(SK_BUILD_FOR_ANDROID_FRAMEWORK)
+#if (0)
 
 #include "rapidjson/document.h"
 
diff --git a/modules/skottie/BUILD.gn b/modules/skottie/BUILD.gn
index 26b48bc..dff355c 100644
--- a/modules/skottie/BUILD.gn
+++ b/modules/skottie/BUILD.gn
@@ -34,8 +34,8 @@
     configs += [ "../../:skia_private" ]
     deps = [
       "../..:skia",
-      "../../third_party/rapidjson",
-      "../sksg:sksg",
+      "../skjson",
+      "../sksg",
     ]
   }
 }
diff --git a/modules/skottie/include/Skottie.h b/modules/skottie/include/Skottie.h
index a199383..54f293c 100644
--- a/modules/skottie/include/Skottie.h
+++ b/modules/skottie/include/Skottie.h
@@ -19,12 +19,11 @@
 struct SkRect;
 class SkStream;
 
+namespace skjson { class ObjectValue; }
 namespace sksg { class Scene;  }
 
 namespace skottie {
 
-namespace json { class ValueRef; }
-
 class SK_API ResourceProvider : public SkNoncopyable {
 public:
     virtual ~ResourceProvider() = default;
@@ -78,7 +77,7 @@
 
 private:
     Animation(const ResourceProvider&, SkString ver, const SkSize& size, SkScalar fps,
-              const json::ValueRef&, Stats*);
+              const skjson::ObjectValue&, Stats*);
 
     SkString                     fVersion;
     SkSize                       fSize;
diff --git a/modules/skottie/src/Skottie.cpp b/modules/skottie/src/Skottie.cpp
index 229b5c6..71521c6 100644
--- a/modules/skottie/src/Skottie.cpp
+++ b/modules/skottie/src/Skottie.cpp
@@ -54,8 +54,8 @@
 namespace {
 
 struct AssetInfo {
-    json::ValueRef fAsset;
-    mutable bool   fIsAttaching; // Used for cycle detection
+    const skjson::ObjectValue* fAsset;
+    mutable bool               fIsAttaching; // Used for cycle detection
 };
 
 using AssetMap = SkTHashMap<SkString, AssetInfo>;
@@ -67,17 +67,14 @@
     sksg::AnimatorList&     fAnimators;
 };
 
-bool LogFail(const json::ValueRef& json, const char* msg) {
+bool LogFail(const skjson::Value& json, const char* msg) {
     const auto dump = json.toString();
     LOG("!! %s: %s\n", msg, dump.c_str());
     return false;
 }
 
-sk_sp<sksg::Matrix> AttachMatrix(const json::ValueRef& t, AttachContext* ctx,
-                                        sk_sp<sksg::Matrix> parentMatrix) {
-    if (!t.isObject())
-        return nullptr;
-
+sk_sp<sksg::Matrix> AttachMatrix(const skjson::ObjectValue& t, AttachContext* ctx,
+                                 sk_sp<sksg::Matrix> parentMatrix) {
     auto matrix = sksg::Matrix::Make(SkMatrix::I(), std::move(parentMatrix));
     auto adapter = sk_make_sp<TransformAdapter>(matrix);
     auto anchor_attached = BindProperty<VectorValue>(t["a"], &ctx->fAnimators,
@@ -93,13 +90,13 @@
                 adapter->setScale(ValueTraits<VectorValue>::As<SkVector>(s));
             });
 
-    auto jrotation = t["r"];
-    if (jrotation.isNull()) {
+    const auto* jrotation = &t["r"];
+    if (jrotation->is<skjson::NullValue>()) {
         // 3d rotations have separate rx,ry,rz components.  While we don't fully support them,
         // we can still make use of rz.
-        jrotation = t["rz"];
+        jrotation = &t["rz"];
     }
-    auto rotation_attached = BindProperty<ScalarValue>(jrotation, &ctx->fAnimators,
+    auto rotation_attached = BindProperty<ScalarValue>(*jrotation, &ctx->fAnimators,
             [adapter](const ScalarValue& r) {
                 adapter->setRotation(r);
             });
@@ -125,10 +122,10 @@
     return matrix;
 }
 
-sk_sp<sksg::RenderNode> AttachOpacity(const json::ValueRef& jtransform, AttachContext* ctx,
+sk_sp<sksg::RenderNode> AttachOpacity(const skjson::ObjectValue& jtransform, AttachContext* ctx,
                                       sk_sp<sksg::RenderNode> childNode) {
-    if (!jtransform.isObject() || !childNode)
-        return childNode;
+    if (!childNode)
+        return nullptr;
 
     static constexpr ScalarValue kNoopOpacity = 100;
     auto opacityNode = sksg::OpacityEffect::Make(childNode);
@@ -145,9 +142,9 @@
     return std::move(opacityNode);
 }
 
-sk_sp<sksg::RenderNode> AttachComposition(const json::ValueRef&, AttachContext* ctx);
+sk_sp<sksg::RenderNode> AttachComposition(const skjson::ObjectValue&, AttachContext* ctx);
 
-sk_sp<sksg::Path> AttachPath(const json::ValueRef& jpath, AttachContext* ctx) {
+sk_sp<sksg::Path> AttachPath(const skjson::Value& jpath, AttachContext* ctx) {
     auto path_node = sksg::Path::Make();
     return BindProperty<ShapeValue>(jpath, &ctx->fAnimators,
         [path_node](const ShapeValue& p) {
@@ -157,15 +154,12 @@
         : nullptr;
 }
 
-sk_sp<sksg::GeometryNode> AttachPathGeometry(const json::ValueRef& jpath, AttachContext* ctx) {
-    SkASSERT(jpath.isObject());
-
+sk_sp<sksg::GeometryNode> AttachPathGeometry(const skjson::ObjectValue& jpath, AttachContext* ctx) {
     return AttachPath(jpath["ks"], ctx);
 }
 
-sk_sp<sksg::GeometryNode> AttachRRectGeometry(const json::ValueRef& jrect, AttachContext* ctx) {
-    SkASSERT(jrect.isObject());
-
+sk_sp<sksg::GeometryNode> AttachRRectGeometry(const skjson::ObjectValue& jrect,
+                                              AttachContext* ctx) {
     auto rect_node = sksg::RRect::Make();
     auto adapter = sk_make_sp<RRectAdapter>(rect_node);
 
@@ -189,9 +183,8 @@
     return std::move(rect_node);
 }
 
-sk_sp<sksg::GeometryNode> AttachEllipseGeometry(const json::ValueRef& jellipse, AttachContext* ctx) {
-    SkASSERT(jellipse.isObject());
-
+sk_sp<sksg::GeometryNode> AttachEllipseGeometry(const skjson::ObjectValue& jellipse,
+                                                AttachContext* ctx) {
     auto rect_node = sksg::RRect::Make();
     auto adapter = sk_make_sp<RRectAdapter>(rect_node);
 
@@ -213,15 +206,14 @@
     return std::move(rect_node);
 }
 
-sk_sp<sksg::GeometryNode> AttachPolystarGeometry(const json::ValueRef& jstar, AttachContext* ctx) {
-    SkASSERT(jstar.isObject());
-
+sk_sp<sksg::GeometryNode> AttachPolystarGeometry(const skjson::ObjectValue& jstar,
+                                                 AttachContext* ctx) {
     static constexpr PolyStarAdapter::Type gTypes[] = {
         PolyStarAdapter::Type::kStar, // "sy": 1
         PolyStarAdapter::Type::kPoly, // "sy": 2
     };
 
-    const auto type = jstar["sy"].toDefault<int>(0) - 1;
+    const auto type = ParseDefault<int>(jstar["sy"], 0) - 1;
     if (type < 0 || type >= SkTo<int>(SK_ARRAY_COUNT(gTypes))) {
         LogFail(jstar, "Unknown polystar type");
         return nullptr;
@@ -262,11 +254,9 @@
     return std::move(path_node);
 }
 
-sk_sp<sksg::Color> AttachColor(const json::ValueRef& obj, AttachContext* ctx) {
-    SkASSERT(obj.isObject());
-
+sk_sp<sksg::Color> AttachColor(const skjson::ObjectValue& jcolor, AttachContext* ctx) {
     auto color_node = sksg::Color::Make(SK_ColorBLACK);
-    auto color_attached = BindProperty<VectorValue>(obj["c"], &ctx->fAnimators,
+    auto color_attached = BindProperty<VectorValue>(jcolor["c"], &ctx->fAnimators,
         [color_node](const VectorValue& c) {
             color_node->setColor(ValueTraits<VectorValue>::As<SkColor>(c));
         });
@@ -274,21 +264,19 @@
     return color_attached ? color_node : nullptr;
 }
 
-sk_sp<sksg::Gradient> AttachGradient(const json::ValueRef& obj, AttachContext* ctx) {
-    SkASSERT(obj.isObject());
-
-    const auto stops = obj["g"];
-    if (!stops.isObject())
+sk_sp<sksg::Gradient> AttachGradient(const skjson::ObjectValue& jgrad, AttachContext* ctx) {
+    const skjson::ObjectValue* stops = jgrad["g"];
+    if (!stops)
         return nullptr;
 
-    const auto stopCount = stops["p"].toDefault<int>(-1);
+    const auto stopCount = ParseDefault<int>((*stops)["p"], -1);
     if (stopCount < 0)
         return nullptr;
 
     sk_sp<sksg::Gradient> gradient_node;
     sk_sp<GradientAdapter> adapter;
 
-    if (obj["t"].toDefault<int>(1) == 1) {
+    if (ParseDefault<int>(jgrad["t"], 1) == 1) {
         auto linear_node = sksg::LinearGradient::Make();
         adapter = sk_make_sp<LinearGradientAdapter>(linear_node, stopCount);
         gradient_node = std::move(linear_node);
@@ -300,15 +288,15 @@
         gradient_node = std::move(radial_node);
     }
 
-    BindProperty<VectorValue>(stops["k"], &ctx->fAnimators,
+    BindProperty<VectorValue>((*stops)["k"], &ctx->fAnimators,
         [adapter](const VectorValue& stops) {
             adapter->setColorStops(stops);
         });
-    BindProperty<VectorValue>(obj["s"], &ctx->fAnimators,
+    BindProperty<VectorValue>(jgrad["s"], &ctx->fAnimators,
         [adapter](const VectorValue& s) {
             adapter->setStartPoint(ValueTraits<VectorValue>::As<SkPoint>(s));
         });
-    BindProperty<VectorValue>(obj["e"], &ctx->fAnimators,
+    BindProperty<VectorValue>(jgrad["e"], &ctx->fAnimators,
         [adapter](const VectorValue& e) {
             adapter->setEndPoint(ValueTraits<VectorValue>::As<SkPoint>(e));
         });
@@ -316,7 +304,7 @@
     return gradient_node;
 }
 
-sk_sp<sksg::PaintNode> AttachPaint(const json::ValueRef& jpaint, AttachContext* ctx,
+sk_sp<sksg::PaintNode> AttachPaint(const skjson::ObjectValue& jpaint, AttachContext* ctx,
                                    sk_sp<sksg::PaintNode> paint_node) {
     if (paint_node) {
         paint_node->setAntiAlias(true);
@@ -331,10 +319,8 @@
     return paint_node;
 }
 
-sk_sp<sksg::PaintNode> AttachStroke(const json::ValueRef& jstroke, AttachContext* ctx,
+sk_sp<sksg::PaintNode> AttachStroke(const skjson::ObjectValue& jstroke, AttachContext* ctx,
                                     sk_sp<sksg::PaintNode> stroke_node) {
-    SkASSERT(jstroke.isObject());
-
     if (!stroke_node)
         return nullptr;
 
@@ -347,14 +333,14 @@
     if (!width_attached)
         return nullptr;
 
-    stroke_node->setStrokeMiter(jstroke["ml"].toDefault(4.0f));
+    stroke_node->setStrokeMiter(ParseDefault<SkScalar>(jstroke["ml"], 4.0f));
 
     static constexpr SkPaint::Join gJoins[] = {
         SkPaint::kMiter_Join,
         SkPaint::kRound_Join,
         SkPaint::kBevel_Join,
     };
-    stroke_node->setStrokeJoin(gJoins[SkTPin<int>(jstroke["lj"].toDefault<int>(1) - 1,
+    stroke_node->setStrokeJoin(gJoins[SkTPin<int>(ParseDefault<int>(jstroke["lj"], 1) - 1,
                                                   0, SK_ARRAY_COUNT(gJoins) - 1)]);
 
     static constexpr SkPaint::Cap gCaps[] = {
@@ -362,38 +348,32 @@
         SkPaint::kRound_Cap,
         SkPaint::kSquare_Cap,
     };
-    stroke_node->setStrokeCap(gCaps[SkTPin<int>(jstroke["lc"].toDefault<int>(1) - 1,
+    stroke_node->setStrokeCap(gCaps[SkTPin<int>(ParseDefault(jstroke["lc"], 1) - 1,
                                                 0, SK_ARRAY_COUNT(gCaps) - 1)]);
 
     return stroke_node;
 }
 
-sk_sp<sksg::PaintNode> AttachColorFill(const json::ValueRef& jfill, AttachContext* ctx) {
-    SkASSERT(jfill.isObject());
-
+sk_sp<sksg::PaintNode> AttachColorFill(const skjson::ObjectValue& jfill, AttachContext* ctx) {
     return AttachPaint(jfill, ctx, AttachColor(jfill, ctx));
 }
 
-sk_sp<sksg::PaintNode> AttachGradientFill(const json::ValueRef& jfill, AttachContext* ctx) {
-    SkASSERT(jfill.isObject());
-
+sk_sp<sksg::PaintNode> AttachGradientFill(const skjson::ObjectValue& jfill, AttachContext* ctx) {
     return AttachPaint(jfill, ctx, AttachGradient(jfill, ctx));
 }
 
-sk_sp<sksg::PaintNode> AttachColorStroke(const json::ValueRef& jstroke, AttachContext* ctx) {
-    SkASSERT(jstroke.isObject());
-
+sk_sp<sksg::PaintNode> AttachColorStroke(const skjson::ObjectValue& jstroke, AttachContext* ctx) {
     return AttachStroke(jstroke, ctx, AttachPaint(jstroke, ctx, AttachColor(jstroke, ctx)));
 }
 
-sk_sp<sksg::PaintNode> AttachGradientStroke(const json::ValueRef& jstroke, AttachContext* ctx) {
-    SkASSERT(jstroke.isObject());
-
+sk_sp<sksg::PaintNode> AttachGradientStroke(const skjson::ObjectValue& jstroke,
+                                            AttachContext* ctx) {
     return AttachStroke(jstroke, ctx, AttachPaint(jstroke, ctx, AttachGradient(jstroke, ctx)));
 }
 
 std::vector<sk_sp<sksg::GeometryNode>> AttachMergeGeometryEffect(
-    const json::ValueRef& jmerge, AttachContext* ctx, std::vector<sk_sp<sksg::GeometryNode>>&& geos) {
+        const skjson::ObjectValue& jmerge, AttachContext*,
+        std::vector<sk_sp<sksg::GeometryNode>>&& geos) {
     std::vector<sk_sp<sksg::GeometryNode>> merged;
 
     static constexpr sksg::Merge::Mode gModes[] = {
@@ -404,7 +384,7 @@
         sksg::Merge::Mode::kXOR      ,  // "mm": 5
     };
 
-    const auto mode = gModes[SkTPin<int>(jmerge["mm"].toDefault(1) - 1,
+    const auto mode = gModes[SkTPin<int>(ParseDefault<int>(jmerge["mm"], 1) - 1,
                                          0, SK_ARRAY_COUNT(gModes) - 1)];
     merged.push_back(sksg::Merge::Make(std::move(geos), mode));
 
@@ -412,14 +392,15 @@
 }
 
 std::vector<sk_sp<sksg::GeometryNode>> AttachTrimGeometryEffect(
-    const json::ValueRef& jtrim, AttachContext* ctx, std::vector<sk_sp<sksg::GeometryNode>>&& geos) {
+        const skjson::ObjectValue& jtrim, AttachContext* ctx,
+        std::vector<sk_sp<sksg::GeometryNode>>&& geos) {
 
     enum class Mode {
         kMerged,   // "m": 1
         kSeparate, // "m": 2
     } gModes[] = { Mode::kMerged, Mode::kSeparate };
 
-    const auto mode = gModes[SkTPin<int>(jtrim["m"].toDefault(1) - 1,
+    const auto mode = gModes[SkTPin<int>(ParseDefault<int>(jtrim["m"], 1) - 1,
                                          0, SK_ARRAY_COUNT(gModes) - 1)];
 
     std::vector<sk_sp<sksg::GeometryNode>> inputs;
@@ -454,7 +435,8 @@
 }
 
 std::vector<sk_sp<sksg::GeometryNode>> AttachRoundGeometryEffect(
-    const json::ValueRef& jtrim, AttachContext* ctx, std::vector<sk_sp<sksg::GeometryNode>>&& geos) {
+        const skjson::ObjectValue& jtrim, AttachContext* ctx,
+        std::vector<sk_sp<sksg::GeometryNode>>&& geos) {
 
     std::vector<sk_sp<sksg::GeometryNode>> rounded;
     rounded.reserve(geos.size());
@@ -472,7 +454,7 @@
     return rounded;
 }
 
-using GeometryAttacherT = sk_sp<sksg::GeometryNode> (*)(const json::ValueRef&, AttachContext*);
+using GeometryAttacherT = sk_sp<sksg::GeometryNode> (*)(const skjson::ObjectValue&, AttachContext*);
 static constexpr GeometryAttacherT gGeometryAttachers[] = {
     AttachPathGeometry,
     AttachRRectGeometry,
@@ -480,7 +462,7 @@
     AttachPolystarGeometry,
 };
 
-using PaintAttacherT = sk_sp<sksg::PaintNode> (*)(const json::ValueRef&, AttachContext*);
+using PaintAttacherT = sk_sp<sksg::PaintNode> (*)(const skjson::ObjectValue&, AttachContext*);
 static constexpr PaintAttacherT gPaintAttachers[] = {
     AttachColorFill,
     AttachColorStroke,
@@ -489,7 +471,7 @@
 };
 
 using GeometryEffectAttacherT =
-    std::vector<sk_sp<sksg::GeometryNode>> (*)(const json::ValueRef&,
+    std::vector<sk_sp<sksg::GeometryNode>> (*)(const skjson::ObjectValue&,
                                                AttachContext*,
                                                std::vector<sk_sp<sksg::GeometryNode>>&&);
 static constexpr GeometryEffectAttacherT gGeometryEffectAttachers[] = {
@@ -512,7 +494,7 @@
     uint32_t    fAttacherIndex; // index into respective attacher tables
 };
 
-const ShapeInfo* FindShapeInfo(const json::ValueRef& shape) {
+const ShapeInfo* FindShapeInfo(const skjson::ObjectValue& jshape) {
     static constexpr ShapeInfo gShapeInfo[] = {
         { "el", ShapeType::kGeometry      , 2 }, // ellipse   -> AttachEllipseGeometry
         { "fl", ShapeType::kPaint         , 0 }, // fill      -> AttachColorFill
@@ -530,7 +512,7 @@
     };
 
     SkString type;
-    if (!shape["ty"].to(&type) || type.isEmpty())
+    if (!Parse<SkString>(jshape["ty"], &type) || type.isEmpty())
         return nullptr;
 
     const auto* info = bsearch(type.c_str(),
@@ -546,8 +528,8 @@
 }
 
 struct GeometryEffectRec {
-    const json::ValueRef      fJson;
-    GeometryEffectAttacherT fAttach;
+    const skjson::ObjectValue& fJson;
+    GeometryEffectAttacherT    fAttach;
 };
 
 struct AttachShapeContext {
@@ -566,8 +548,8 @@
     size_t                                  fCommittedAnimators;
 };
 
-sk_sp<sksg::RenderNode> AttachShape(const json::ValueRef& jshape, AttachShapeContext* shapeCtx) {
-    if (!jshape.isArray())
+sk_sp<sksg::RenderNode> AttachShape(const skjson::ArrayValue* jshape, AttachShapeContext* shapeCtx) {
+    if (!jshape)
         return nullptr;
 
     SkDEBUGCODE(const auto initialGeometryEffects = shapeCtx->fGeometryEffectStack->size();)
@@ -577,8 +559,8 @@
     sk_sp<sksg::Matrix> shape_matrix;
 
     struct ShapeRec {
-        const json::ValueRef fJson;
-        const ShapeInfo&   fInfo;
+        const skjson::ObjectValue& fJson;
+        const ShapeInfo&           fInfo;
     };
 
     // First pass (bottom->top):
@@ -588,27 +570,29 @@
     //   * store recs for next pass
     //
     std::vector<ShapeRec> recs;
-    for (size_t i = 0; i < jshape.size(); ++i) {
-        const auto s = jshape[jshape.size() - 1 - i];
-        const auto* info = FindShapeInfo(s);
+    for (size_t i = 0; i < jshape->size(); ++i) {
+        const skjson::ObjectValue* shape = (*jshape)[jshape->size() - 1 - i];
+        if (!shape) continue;
+
+        const auto* info = FindShapeInfo(*shape);
         if (!info) {
-            LogFail(s["ty"], "Unknown shape");
+            LogFail((*shape)["ty"], "Unknown shape");
             continue;
         }
 
-        recs.push_back({ s, *info });
+        recs.push_back({ *shape, *info });
 
         switch (info->fShapeType) {
         case ShapeType::kTransform:
-            if ((shape_matrix = AttachMatrix(s, shapeCtx->fCtx, nullptr))) {
+            if ((shape_matrix = AttachMatrix(*shape, shapeCtx->fCtx, nullptr))) {
                 shape_wrapper = sksg::Transform::Make(std::move(shape_wrapper), shape_matrix);
             }
-            shape_wrapper = AttachOpacity(s, shapeCtx->fCtx, std::move(shape_wrapper));
+            shape_wrapper = AttachOpacity(*shape, shapeCtx->fCtx, std::move(shape_wrapper));
             break;
         case ShapeType::kGeometryEffect:
             SkASSERT(info->fAttacherIndex < SK_ARRAY_COUNT(gGeometryEffectAttachers));
             shapeCtx->fGeometryEffectStack->push_back(
-                { s, gGeometryEffectAttachers[info->fAttacherIndex] });
+                { *shape, gGeometryEffectAttachers[info->fAttacherIndex] });
             break;
         default:
             break;
@@ -640,7 +624,7 @@
                                                                            std::move(geos));
             }
 
-            SkASSERT(shapeCtx->fGeometryEffectStack->back().fJson == rec->fJson);
+            SkASSERT(&shapeCtx->fGeometryEffectStack->back().fJson == &rec->fJson);
             SkASSERT(shapeCtx->fGeometryEffectStack->back().fAttach ==
                      gGeometryEffectAttachers[rec->fInfo.fAttacherIndex]);
             shapeCtx->fGeometryEffectStack->pop_back();
@@ -762,10 +746,10 @@
     return sk_make_sp<SkottieSGAdapter>(std::move(animation));
 }
 
-sk_sp<sksg::RenderNode> AttachAssetRef(const json::ValueRef& jlayer, AttachContext* ctx,
-    sk_sp<sksg::RenderNode>(*attach_proc)(const json::ValueRef& comp, AttachContext* ctx)) {
+sk_sp<sksg::RenderNode> AttachAssetRef(const skjson::ObjectValue& jlayer, AttachContext* ctx,
+    sk_sp<sksg::RenderNode>(*attach_proc)(const skjson::ObjectValue& comp, AttachContext* ctx)) {
 
-    const auto refId = jlayer["refId"].toDefault(SkString());
+    const auto refId = ParseDefault<SkString>(jlayer["refId"], SkString());
     if (refId.isEmpty()) {
         LOG("!! Layer missing refId\n");
         return nullptr;
@@ -787,18 +771,16 @@
     }
 
     asset_info->fIsAttaching = true;
-    auto asset = attach_proc(asset_info->fAsset, ctx);
+    auto asset = attach_proc(*asset_info->fAsset, ctx);
     asset_info->fIsAttaching = false;
 
     return asset;
 }
 
-sk_sp<sksg::RenderNode> AttachCompLayer(const json::ValueRef& jlayer, AttachContext* ctx,
+sk_sp<sksg::RenderNode> AttachCompLayer(const skjson::ObjectValue& jlayer, AttachContext* ctx,
                                         float* time_bias, float* time_scale) {
-    SkASSERT(jlayer.isObject());
-
-    const auto start_time = jlayer["st"].toDefault(0.0f),
-             stretch_time = jlayer["sr"].toDefault(1.0f);
+    const auto start_time = ParseDefault<float>(jlayer["st"], 0.0f),
+             stretch_time = ParseDefault<float>(jlayer["sr"], 1.0f);
 
     *time_bias = -start_time;
     *time_scale = sk_ieee_float_divide(1, stretch_time);
@@ -809,13 +791,11 @@
     return AttachAssetRef(jlayer, ctx, AttachComposition);
 }
 
-sk_sp<sksg::RenderNode> AttachSolidLayer(const json::ValueRef& jlayer, AttachContext*,
+sk_sp<sksg::RenderNode> AttachSolidLayer(const skjson::ObjectValue& jlayer, AttachContext*,
                                          float*, float*) {
-    SkASSERT(jlayer.isObject());
-
-    const auto size = SkSize::Make(jlayer["sw"].toDefault(0.0f),
-                                   jlayer["sh"].toDefault(0.0f));
-    const auto hex = jlayer["sc"].toDefault(SkString());
+    const auto size = SkSize::Make(ParseDefault<float>(jlayer["sw"], 0.0f),
+                                   ParseDefault<float>(jlayer["sh"], 0.0f));
+    const auto hex = ParseDefault<SkString>(jlayer["sc"], SkString());
     uint32_t c;
     if (size.isEmpty() ||
         !hex.startsWith("#") ||
@@ -830,11 +810,9 @@
                             sksg::Color::Make(color));
 }
 
-sk_sp<sksg::RenderNode> AttachImageAsset(const json::ValueRef& jimage, AttachContext* ctx) {
-    SkASSERT(jimage.isObject());
-
-    const auto name = jimage["p"].toDefault(SkString()),
-               path = jimage["u"].toDefault(SkString());
+sk_sp<sksg::RenderNode> AttachImageAsset(const skjson::ObjectValue& jimage, AttachContext* ctx) {
+    const auto name = ParseDefault<SkString>(jimage["p"], SkString()),
+               path = ParseDefault<SkString>(jimage["u"], SkString());
     if (name.isEmpty())
         return nullptr;
 
@@ -851,25 +829,20 @@
         SkImage::MakeFromEncoded(SkData::MakeFromStream(resStream.get(), resStream->getLength())));
 }
 
-sk_sp<sksg::RenderNode> AttachImageLayer(const json::ValueRef& jlayer, AttachContext* ctx,
+sk_sp<sksg::RenderNode> AttachImageLayer(const skjson::ObjectValue& jlayer, AttachContext* ctx,
                                          float*, float*) {
-    SkASSERT(jlayer.isObject());
-
     return AttachAssetRef(jlayer, ctx, AttachImageAsset);
 }
 
-sk_sp<sksg::RenderNode> AttachNullLayer(const json::ValueRef& layer, AttachContext*, float*, float*) {
-    SkASSERT(layer.isObject());
-
+sk_sp<sksg::RenderNode> AttachNullLayer(const skjson::ObjectValue& layer, AttachContext*,
+                                        float*, float*) {
     // Null layers are used solely to drive dependent transforms,
     // but we use free-floating sksg::Matrices for that purpose.
     return nullptr;
 }
 
-sk_sp<sksg::RenderNode> AttachShapeLayer(const json::ValueRef& layer, AttachContext* ctx,
+sk_sp<sksg::RenderNode> AttachShapeLayer(const skjson::ObjectValue& layer, AttachContext* ctx,
                                          float*, float*) {
-    SkASSERT(layer.isObject());
-
     std::vector<sk_sp<sksg::GeometryNode>> geometryStack;
     std::vector<GeometryEffectRec> geometryEffectStack;
     AttachShapeContext shapeCtx(ctx, &geometryStack, &geometryEffectStack, ctx->fAnimators.size());
@@ -885,28 +858,23 @@
     return shapeNode;
 }
 
-sk_sp<sksg::RenderNode> AttachTextLayer(const json::ValueRef& layer, AttachContext*, float*, float*) {
-    SkASSERT(layer.isObject());
-
+sk_sp<sksg::RenderNode> AttachTextLayer(const skjson::ObjectValue& layer, AttachContext*,
+                                        float*, float*) {
     LOG("?? Text layer stub\n");
     return nullptr;
 }
 
 struct AttachLayerContext {
-    AttachLayerContext(const json::ValueRef& jlayers, AttachContext* ctx)
-        : fLayerList(jlayers), fCtx(ctx) {
-        SkASSERT(fLayerList.isArray());
-    }
+    AttachLayerContext(const skjson::ArrayValue& jlayers, AttachContext* ctx)
+        : fLayerList(jlayers), fCtx(ctx) {}
 
-    const json::ValueRef                 fLayerList;
+    const skjson::ArrayValue&            fLayerList;
     AttachContext*                       fCtx;
     SkTHashMap<int, sk_sp<sksg::Matrix>> fLayerMatrixMap;
     sk_sp<sksg::RenderNode>              fCurrentMatte;
 
-    sk_sp<sksg::Matrix> AttachLayerMatrix(const json::ValueRef& jlayer) {
-        SkASSERT(jlayer.isObject());
-
-        const auto layer_index = jlayer["ind"].toDefault<int>(-1);
+    sk_sp<sksg::Matrix> AttachLayerMatrix(const skjson::ObjectValue& jlayer) {
+        const auto layer_index = ParseDefault<int>(jlayer["ind"], -1);
         if (layer_index < 0)
             return nullptr;
 
@@ -917,26 +885,27 @@
     }
 
 private:
-    sk_sp<sksg::Matrix> AttachParentLayerMatrix(const json::ValueRef& jlayer, int layer_index) {
-        SkASSERT(jlayer.isObject());
-
-        const auto parent_index = jlayer["parent"].toDefault<int>(-1);
+    sk_sp<sksg::Matrix> AttachParentLayerMatrix(const skjson::ObjectValue& jlayer,
+                                                int layer_index) {
+        const auto parent_index = ParseDefault<int>(jlayer["parent"], -1);
         if (parent_index < 0 || parent_index == layer_index)
             return nullptr;
 
         if (auto* m = fLayerMatrixMap.find(parent_index))
             return *m;
 
-        for (const json::ValueRef l : fLayerList) {
-            if (l["ind"].toDefault<int>(-1) == parent_index) {
-                return this->AttachLayerMatrixImpl(l, parent_index);
+        for (const skjson::ObjectValue* l : fLayerList) {
+            if (!l) continue;
+
+            if (ParseDefault<int>((*l)["ind"], -1) == parent_index) {
+                return this->AttachLayerMatrixImpl(*l, parent_index);
             }
         }
 
         return nullptr;
     }
 
-    sk_sp<sksg::Matrix> AttachLayerMatrixImpl(const json::ValueRef& jlayer, int layer_index) {
+    sk_sp<sksg::Matrix> AttachLayerMatrixImpl(const skjson::ObjectValue& jlayer, int layer_index) {
         SkASSERT(!fLayerMatrixMap.find(layer_index));
 
         // Add a stub entry to break recursion cycles.
@@ -944,7 +913,12 @@
 
         auto parent_matrix = this->AttachParentLayerMatrix(jlayer, layer_index);
 
-        return *fLayerMatrixMap.set(layer_index, AttachMatrix(jlayer["ks"], fCtx, parent_matrix));
+        if (const skjson::ObjectValue* jtransform = jlayer["ks"]) {
+            return *fLayerMatrixMap.set(layer_index, AttachMatrix(*jtransform, fCtx,
+                                                                  std::move(parent_matrix)));
+
+        }
+        return nullptr;
     }
 };
 
@@ -962,11 +936,10 @@
     return SkBlendMode::kSrcOver;
 }
 
-sk_sp<sksg::RenderNode> AttachMask(const json::ValueRef& jmask,
+sk_sp<sksg::RenderNode> AttachMask(const skjson::ArrayValue* jmask,
                                    AttachContext* ctx,
                                    sk_sp<sksg::RenderNode> childNode) {
-    if (!jmask.isArray())
-        return childNode;
+    if (!jmask) return childNode;
 
     struct MaskRecord {
         sk_sp<sksg::Path>  mask_path;
@@ -977,22 +950,21 @@
 
     bool opaque_mask = true;
 
-    for (const json::ValueRef m : jmask) {
-        if (!m.isObject())
-            continue;
+    for (const skjson::ObjectValue* m : *jmask) {
+        if (!m) continue;
 
-        auto mask_path = AttachPath(m["pt"], ctx);
+        auto mask_path = AttachPath((*m)["pt"], ctx);
         if (!mask_path) {
-            LogFail(m, "Could not parse mask path");
+            LogFail(*m, "Could not parse mask path");
             continue;
         }
 
-        mask_path->setFillType(m["inv"].toDefault(false)
+        mask_path->setFillType(ParseDefault<bool>((*m)["inv"], false)
             ? SkPath::kInverseWinding_FillType
             : SkPath::kWinding_FillType);
 
         SkString mode;
-        if (!m["mode"].to(&mode) ||
+        if (!Parse<SkString>((*m)["mode"], &mode) ||
             mode.size() != 1 ||
             !strcmp(mode.c_str(), "n")) { // "None" masks have no effect.
             continue;
@@ -1003,7 +975,7 @@
         mask_paint->setBlendMode(MaskBlendMode(mode.c_str()[0]));
 
         const auto animator_count = ctx->fAnimators.size();
-        BindProperty<ScalarValue>(m["o"], &ctx->fAnimators,
+        BindProperty<ScalarValue>((*m)["o"], &ctx->fAnimators,
             [mask_paint](const ScalarValue& o) { mask_paint->setOpacity(o * 0.01f); });
 
         opaque_mask &= (animator_count == ctx->fAnimators.size() && mask_paint->getOpacity() >= 1);
@@ -1031,11 +1003,11 @@
     return sksg::MaskEffect::Make(std::move(childNode), std::move(mask_group));
 }
 
-sk_sp<sksg::RenderNode> AttachLayer(const json::ValueRef& jlayer, AttachLayerContext* layerCtx) {
-    if (!jlayer.isObject())
-        return nullptr;
+sk_sp<sksg::RenderNode> AttachLayer(const skjson::ObjectValue* jlayer,
+                                    AttachLayerContext* layerCtx) {
+    if (!jlayer) return nullptr;
 
-    using LayerAttacher = sk_sp<sksg::RenderNode> (*)(const json::ValueRef&, AttachContext*,
+    using LayerAttacher = sk_sp<sksg::RenderNode> (*)(const skjson::ObjectValue&, AttachContext*,
                                                       float* time_bias, float* time_scale);
     static constexpr LayerAttacher gLayerAttachers[] = {
         AttachCompLayer,  // 'ty': 0
@@ -1046,7 +1018,7 @@
         AttachTextLayer,  // 'ty': 5
     };
 
-    int type = jlayer["ty"].toDefault<int>(-1);
+    int type = ParseDefault<int>((*jlayer)["ty"], -1);
     if (type < 0 || type >= SkTo<int>(SK_ARRAY_COUNT(gLayerAttachers))) {
         return nullptr;
     }
@@ -1062,26 +1034,29 @@
           time_scale = 1;
 
     // Layer content.
-    auto layer = gLayerAttachers[type](jlayer, &local_ctx, &time_bias, &time_scale);
+    auto layer = gLayerAttachers[type](*jlayer, &local_ctx, &time_bias, &time_scale);
 
     // Clip layers with explicit dimensions.
     float w = 0, h = 0;
-    if (jlayer["w"].to(&w) && jlayer["h"].to(&h)) {
+    if (Parse<float>((*jlayer)["w"], &w) && Parse<float>((*jlayer)["h"], &h)) {
         layer = sksg::ClipEffect::Make(std::move(layer),
                                        sksg::Rect::Make(SkRect::MakeWH(w, h)),
                                        true);
     }
 
     // Optional layer mask.
-    layer = AttachMask(jlayer["masksProperties"], &local_ctx, std::move(layer));
+    layer = AttachMask((*jlayer)["masksProperties"], &local_ctx, std::move(layer));
 
     // Optional layer transform.
-    if (auto layerMatrix = layerCtx->AttachLayerMatrix(jlayer)) {
+    if (auto layerMatrix = layerCtx->AttachLayerMatrix(*jlayer)) {
         layer = sksg::Transform::Make(std::move(layer), std::move(layerMatrix));
     }
 
     // Optional layer opacity.
-    layer = AttachOpacity(jlayer["ks"], &local_ctx, std::move(layer));
+    // TODO: de-dupe this "ks" lookup with matrix above.
+    if (const skjson::ObjectValue* jtransform = (*jlayer)["ks"]) {
+        layer = AttachOpacity(*jtransform, &local_ctx, std::move(layer));
+    }
 
     class LayerController final : public sksg::GroupAnimator {
     public:
@@ -1119,11 +1094,11 @@
     };
 
     auto controller_node = sksg::OpacityEffect::Make(std::move(layer));
-    const auto        in = jlayer["ip"].toDefault(0.0f),
-                     out = jlayer["op"].toDefault(in);
+    const auto        in = ParseDefault<float>((*jlayer)["ip"], 0.0f),
+                     out = ParseDefault<float>((*jlayer)["op"], in);
 
-    if (!jlayer["tm"].isNull()) {
-        LogFail(jlayer["tm"], "Unsupported time remapping");
+    if (!(*jlayer)["tm"].is<skjson::NullValue>()) {
+        LogFail((*jlayer)["tm"], "Unsupported time remapping");
     }
 
     if (in >= out || !controller_node)
@@ -1137,7 +1112,7 @@
                                             time_bias,
                                             time_scale));
 
-    if (jlayer["td"].toDefault(false)) {
+    if (ParseDefault<bool>((*jlayer)["td"], false)) {
         // This layer is a matte.  We apply it as a mask to the next layer.
         layerCtx->fCurrentMatte = std::move(controller_node);
         return nullptr;
@@ -1149,7 +1124,7 @@
             sksg::MaskEffect::Mode::kNormal, // tt: 1
             sksg::MaskEffect::Mode::kInvert, // tt: 2
         };
-        const auto matteType = jlayer["tt"].toDefault<int>(1) - 1;
+        const auto matteType = ParseDefault<int>((*jlayer)["tt"], 1) - 1;
 
         if (matteType >= 0 && matteType < SkTo<int>(SK_ARRAY_COUNT(gMaskModes))) {
             return sksg::MaskEffect::Make(std::move(controller_node),
@@ -1162,18 +1137,14 @@
     return std::move(controller_node);
 }
 
-sk_sp<sksg::RenderNode> AttachComposition(const json::ValueRef& comp, AttachContext* ctx) {
-    if (!comp.isObject())
-        return nullptr;
-
-    const auto jlayers = comp["layers"];
-    if (!jlayers.isArray())
-        return nullptr;
+sk_sp<sksg::RenderNode> AttachComposition(const skjson::ObjectValue& comp, AttachContext* ctx) {
+    const skjson::ArrayValue* jlayers = comp["layers"];
+    if (!jlayers) return nullptr;
 
     SkSTArray<16, sk_sp<sksg::RenderNode>, true> layers;
-    AttachLayerContext                           layerCtx(jlayers, ctx);
+    AttachLayerContext                           layerCtx(*jlayers, ctx);
 
-    for (const json::ValueRef l : jlayers) {
+    for (const auto& l : *jlayers) {
         if (auto layer_fragment = AttachLayer(l, &layerCtx)) {
             layers.push_back(std::move(layer_fragment));
         }
@@ -1209,18 +1180,27 @@
     stats->fJsonSize = stream->getLength();
     const auto t0 = SkTime::GetMSecs();
 
-    const json::Document doc(stream);
-    const auto json = doc.root();
-    if (!json.isObject())
+    auto data = SkData::MakeFromStream(stream, stream->getLength());
+    if (!data) {
+        SkDebugf("!! Failed to read the input stream.\n");
         return nullptr;
+    }
+
+    const skjson::DOM dom(static_cast<const char*>(data->data()), data->size());
+    if (!dom.root().is<skjson::ObjectValue>()) {
+        // TODO: more error info.
+        SkDebugf("!! Failed to parse JSON input.\n");
+        return nullptr;
+    }
+    const auto& json = dom.root().as<skjson::ObjectValue>();
 
     const auto t1 = SkTime::GetMSecs();
     stats->fJsonParseTimeMS = t1 - t0;
 
-    const auto version = json["v"].toDefault(SkString());
-    const auto size    = SkSize::Make(json["w"].toDefault(0.0f),
-                                      json["h"].toDefault(0.0f));
-    const auto fps     = json["fr"].toDefault(-1.0f);
+    const auto version = ParseDefault<SkString>(json["v"], SkString());
+    const auto size    = SkSize::Make(ParseDefault<float>(json["w"], 0.0f),
+                                      ParseDefault<float>(json["h"], 0.0f));
+    const auto fps     = ParseDefault<float>(json["fr"], -1.0f);
 
     if (size.isEmpty() || version.isEmpty() || fps <= 0) {
         LOG("!! invalid animation params (version: %s, size: [%f %f], frame rate: %f)",
@@ -1270,18 +1250,20 @@
 }
 
 Animation::Animation(const ResourceProvider& resources,
-                     SkString version, const SkSize& size, SkScalar fps, const json::ValueRef& json,
-                     Stats* stats)
+                     SkString version, const SkSize& size, SkScalar fps,
+                     const skjson::ObjectValue& json, Stats* stats)
     : fVersion(std::move(version))
     , fSize(size)
     , fFrameRate(fps)
-    , fInPoint(json["ip"].toDefault(0.0f))
-    , fOutPoint(SkTMax(json["op"].toDefault(SK_ScalarMax), fInPoint)) {
+    , fInPoint(ParseDefault<float>(json["ip"], 0.0f))
+    , fOutPoint(SkTMax(ParseDefault<float>(json["op"], SK_ScalarMax), fInPoint)) {
 
     AssetMap assets;
-    for (const json::ValueRef asset : json["assets"]) {
-        if (asset.isObject()) {
-            assets.set(asset["id"].toDefault(SkString()), { asset, false });
+    if (const skjson::ArrayValue* jassets = json["assets"]) {
+        for (const skjson::ObjectValue* asset : *jassets) {
+            if (asset) {
+                assets.set(ParseDefault<SkString>((*asset)["id"], SkString()), { asset, false });
+            }
         }
     }
 
diff --git a/modules/skottie/src/SkottieAnimator.cpp b/modules/skottie/src/SkottieAnimator.cpp
index 4554409..a023963 100644
--- a/modules/skottie/src/SkottieAnimator.cpp
+++ b/modules/skottie/src/SkottieAnimator.cpp
@@ -21,7 +21,7 @@
 
 #define LOG SkDebugf
 
-bool LogFail(const json::ValueRef& json, const char* msg) {
+bool LogFail(const skjson::Value& json, const char* msg) {
     const auto dump = json.toString();
     LOG("!! %s: %s\n", msg, dump.c_str());
     return false;
@@ -67,15 +67,14 @@
             : SkTPin(fCubicMaps[rec.cmidx].computeYFromX(lt), 0.0f, 1.0f);
     }
 
-    virtual int parseValue(const json::ValueRef&) = 0;
+    virtual int parseValue(const skjson::Value&) = 0;
 
-    void parseKeyFrames(const json::ValueRef& jframes) {
-        if (!jframes.isArray())
-            return;
+    void parseKeyFrames(const skjson::ArrayValue& jframes) {
+        for (const skjson::ObjectValue* jframe : jframes) {
+            if (!jframe) continue;
 
-        for (const json::ValueRef jframe : jframes) {
             float t0;
-            if (!jframe["t"].to(&t0))
+            if (!Parse<float>((*jframe)["t"], &t0))
                 continue;
 
             if (!fRecs.empty()) {
@@ -88,24 +87,24 @@
                 fRecs.back().t1 = t0;
             }
 
-            const auto vidx0 = this->parseValue(jframe["s"]);
+            const auto vidx0 = this->parseValue((*jframe)["s"]);
             if (vidx0 < 0)
                 continue;
 
             // Defaults for constant frames.
             int vidx1 = vidx0, cmidx = -1;
 
-            if (!jframe["h"].toDefault(false)) {
+            if (!ParseDefault<bool>((*jframe)["h"], false)) {
                 // Regular frame, requires an end value.
-                vidx1 = this->parseValue(jframe["e"]);
+                vidx1 = this->parseValue((*jframe)["e"]);
                 if (vidx1 < 0)
                     continue;
 
                 // default is linear lerp
                 static constexpr SkPoint kDefaultC0 = { 0, 0 },
                                          kDefaultC1 = { 1, 1 };
-                const auto c0 = jframe["i"].toDefault(kDefaultC0),
-                           c1 = jframe["o"].toDefault(kDefaultC1);
+                const auto c0 = ParseDefault<SkPoint>((*jframe)["i"], kDefaultC0),
+                           c1 = ParseDefault<SkPoint>((*jframe)["o"], kDefaultC1);
 
                 if (c0 != kDefaultC0 || c1 != kDefaultC1) {
                     // TODO: is it worth de-duping these?
@@ -175,9 +174,11 @@
 template <typename T>
 class KeyframeAnimator final : public KeyframeAnimatorBase {
 public:
-    static std::unique_ptr<KeyframeAnimator> Make(const json::ValueRef& jframes,
+    static std::unique_ptr<KeyframeAnimator> Make(const skjson::ArrayValue* jv,
                                                   std::function<void(const T&)>&& apply) {
-        std::unique_ptr<KeyframeAnimator> animator(new KeyframeAnimator(jframes, std::move(apply)));
+        if (!jv) return nullptr;
+
+        std::unique_ptr<KeyframeAnimator> animator(new KeyframeAnimator(*jv, std::move(apply)));
         if (!animator->count())
             return nullptr;
 
@@ -193,15 +194,15 @@
     }
 
 private:
-    KeyframeAnimator(const json::ValueRef& jframes,
+    KeyframeAnimator(const skjson::ArrayValue& jframes,
                      std::function<void(const T&)>&& apply)
         : fApplyFunc(std::move(apply)) {
         this->parseKeyFrames(jframes);
     }
 
-    int parseValue(const json::ValueRef& jv) override {
+    int parseValue(const skjson::Value& jv) override {
         T val;
-        if (!jv.to(&val) || (!fVs.empty() &&
+        if (!Parse<T>(jv, &val) || (!fVs.empty() &&
                 ValueTraits<T>::Cardinality(val) != ValueTraits<T>::Cardinality(fVs.back()))) {
             return -1;
         }
@@ -235,21 +236,20 @@
 };
 
 template <typename T>
-static inline bool BindPropertyImpl(const json::ValueRef& jprop,
+static inline bool BindPropertyImpl(const skjson::ObjectValue* jprop,
                                     sksg::AnimatorList* animators,
                                     std::function<void(const T&)>&& apply,
                                     const T* noop = nullptr) {
-    if (!jprop.isObject())
-        return false;
+    if (!jprop) return false;
 
-    const auto jpropA = jprop["a"];
-    const auto jpropK = jprop["k"];
+    const auto& jpropA = (*jprop)["a"];
+    const auto& jpropK = (*jprop)["k"];
 
     // Older Json versions don't have an "a" animation marker.
     // For those, we attempt to parse both ways.
-    if (!jpropA.toDefault(false)) {
+    if (!ParseDefault<bool>(jpropA, false)) {
         T val;
-        if (jpropK.to<T>(&val)) {
+        if (Parse<T>(jpropK, &val)) {
             // Static property.
             if (noop && val == *noop)
                 return false;
@@ -258,8 +258,8 @@
             return true;
         }
 
-        if (!jpropA.isNull()) {
-            return LogFail(jprop, "Could not parse (explicit) static property");
+        if (!jpropA.is<skjson::NullValue>()) {
+            return LogFail(*jprop, "Could not parse (explicit) static property");
         }
     }
 
@@ -267,7 +267,7 @@
     auto animator = KeyframeAnimator<T>::Make(jpropK, std::move(apply));
 
     if (!animator) {
-        return LogFail(jprop, "Could not parse keyframed property");
+        return LogFail(*jprop, "Could not parse keyframed property");
     }
 
     animators->push_back(std::move(animator));
@@ -277,11 +277,10 @@
 
 class SplitPointAnimator final : public sksg::Animator {
 public:
-    static std::unique_ptr<SplitPointAnimator> Make(const json::ValueRef& jprop,
+    static std::unique_ptr<SplitPointAnimator> Make(const skjson::ObjectValue* jprop,
                                                     std::function<void(const VectorValue&)>&& apply,
                                                     const VectorValue*) {
-        if (!jprop.isObject())
-            return nullptr;
+        if (!jprop) return nullptr;
 
         std::unique_ptr<SplitPointAnimator> split_animator(
             new SplitPointAnimator(std::move(apply)));
@@ -290,11 +289,11 @@
         // the object itself, so the scope is bound to the life time of the object.
         auto* split_animator_ptr = split_animator.get();
 
-        if (!BindPropertyImpl<ScalarValue>(jprop["x"], &split_animator->fAnimators,
+        if (!BindPropertyImpl<ScalarValue>((*jprop)["x"], &split_animator->fAnimators,
                 [split_animator_ptr](const ScalarValue& x) { split_animator_ptr->setX(x); }) ||
-            !BindPropertyImpl<ScalarValue>(jprop["y"], &split_animator->fAnimators,
+            !BindPropertyImpl<ScalarValue>((*jprop)["y"], &split_animator->fAnimators,
                 [split_animator_ptr](const ScalarValue& y) { split_animator_ptr->setY(y); })) {
-            LogFail(jprop, "Could not parse split property");
+            LogFail(*jprop, "Could not parse split property");
             return nullptr;
         }
 
@@ -331,11 +330,11 @@
     using INHERITED = sksg::Animator;
 };
 
-bool BindSplitPositionProperty(const json::ValueRef& jprop,
+bool BindSplitPositionProperty(const skjson::Value& jv,
                                sksg::AnimatorList* animators,
                                std::function<void(const VectorValue&)>&& apply,
                                const VectorValue* noop) {
-    if (auto split_animator = SplitPointAnimator::Make(jprop, std::move(apply), noop)) {
+    if (auto split_animator = SplitPointAnimator::Make(jv, std::move(apply), noop)) {
         animators->push_back(std::unique_ptr<sksg::Animator>(split_animator.release()));
         return true;
     }
@@ -346,29 +345,32 @@
 } // namespace
 
 template <>
-bool BindProperty(const json::ValueRef& jprop,
+bool BindProperty(const skjson::Value& jv,
                   sksg::AnimatorList* animators,
                   std::function<void(const ScalarValue&)>&& apply,
                   const ScalarValue* noop) {
-    return BindPropertyImpl(jprop, animators, std::move(apply), noop);
+    return BindPropertyImpl(jv, animators, std::move(apply), noop);
 }
 
 template <>
-bool BindProperty(const json::ValueRef& jprop,
+bool BindProperty(const skjson::Value& jv,
                   sksg::AnimatorList* animators,
                   std::function<void(const VectorValue&)>&& apply,
                   const VectorValue* noop) {
-    return jprop["s"].toDefault<bool>(false)
-        ? BindSplitPositionProperty(jprop, animators, std::move(apply), noop)
-        : BindPropertyImpl(jprop, animators, std::move(apply), noop);
+    if (!jv.is<skjson::ObjectValue>())
+        return false;
+
+    return ParseDefault<bool>(jv.as<skjson::ObjectValue>()["s"], false)
+        ? BindSplitPositionProperty(jv, animators, std::move(apply), noop)
+        : BindPropertyImpl(jv, animators, std::move(apply), noop);
 }
 
 template <>
-bool BindProperty(const json::ValueRef& jprop,
+bool BindProperty(const skjson::Value& jv,
                   sksg::AnimatorList* animators,
                   std::function<void(const ShapeValue&)>&& apply,
                   const ShapeValue* noop) {
-    return BindPropertyImpl(jprop, animators, std::move(apply), noop);
+    return BindPropertyImpl(jv, animators, std::move(apply), noop);
 }
 
 } // namespace skottie
diff --git a/modules/skottie/src/SkottieAnimator.h b/modules/skottie/src/SkottieAnimator.h
index 6dc8f6c..c4464e3 100644
--- a/modules/skottie/src/SkottieAnimator.h
+++ b/modules/skottie/src/SkottieAnimator.h
@@ -12,14 +12,14 @@
 
 #include <functional>
 
-namespace skottie {
+namespace skjson { class Value; }
 
-namespace json { class ValueRef; }
+namespace skottie {
 
 // This is the workhorse for property binding: depending on whether the property is animated,
 // it will either apply immediately or instantiate and attach a keyframe animator.
 template <typename T>
-bool BindProperty(const json::ValueRef&,
+bool BindProperty(const skjson::Value&,
                   sksg::AnimatorList*,
                   std::function<void(const T&)>&&,
                   const T* noop = nullptr);
diff --git a/modules/skottie/src/SkottieJson.cpp b/modules/skottie/src/SkottieJson.cpp
index 23e616d..4f23939 100644
--- a/modules/skottie/src/SkottieJson.cpp
+++ b/modules/skottie/src/SkottieJson.cpp
@@ -14,45 +14,37 @@
 #include "SkStream.h"
 #include "SkString.h"
 #include "SkottieValue.h"
-
-#include "rapidjson/error/en.h"
-#include "rapidjson/prettywriter.h"
-#include "rapidjson/stringbuffer.h"
-
 #include <vector>
 
 namespace skottie {
 
-namespace json {
+using namespace skjson;
 
 template <>
-bool ValueRef::to<SkScalar>(SkScalar* v) const {
-    if (!fValue) return false;
-
+bool Parse<SkScalar>(const Value& v, SkScalar* s) {
     // Some versions wrap values as single-element arrays.
-    if (fValue->IsArray() && fValue->Size() == 1) {
-        return ValueRef(fValue->operator[](0)).to(v);
+    if (const skjson::ArrayValue* array = v) {
+        if (array->size() == 1) {
+            return Parse((*array)[0], s);
+        }
     }
 
-    if (!fValue->IsNumber())
-        return false;
+    if (const skjson::NumberValue* num = v) {
+        *s = static_cast<SkScalar>(**num);
+        return true;
+    }
 
-    *v = static_cast<SkScalar>(fValue->GetDouble());
-
-    return true;
+    return false;
 }
 
 template <>
-bool ValueRef::to<bool>(bool* v) const {
-    if (!fValue) return false;
-
-    switch(fValue->GetType()) {
-    case rapidjson::kNumberType:
-        *v = SkToBool(fValue->GetDouble());
+bool Parse<bool>(const Value& v, bool* b) {
+    switch(v.getType()) {
+    case Value::Type::kNumber:
+        *b = SkToBool(*v.as<NumberValue>());
         return true;
-    case rapidjson::kFalseType:
-    case rapidjson::kTrueType:
-        *v = fValue->GetBool();
+    case Value::Type::kBool:
+        *b = *v.as<BoolValue>();
         return true;
     default:
         break;
@@ -62,46 +54,49 @@
 }
 
 template <>
-bool ValueRef::to<int>(int* v) const {
-    if (!fValue || !fValue->IsInt())
-        return false;
+bool Parse<int>(const Value& v, int* i) {
+    if (const skjson::NumberValue* num = v) {
+        const auto dbl = **num;
+        *i = dbl;
+        return *i == dbl;
+    }
 
-    *v = fValue->GetInt();
-
-    return true;
+    return false;
 }
 
 template <>
-bool ValueRef::to<SkString>(SkString* v) const {
-    if (!fValue || !fValue->IsString())
-        return false;
+bool Parse<SkString>(const Value& v, SkString* s) {
+    if (const skjson::StringValue* sv = v) {
+        s->set(sv->begin(), sv->size());
+        return true;
+    }
 
-    v->set(fValue->GetString());
-
-    return true;
+    return false;
 }
 
 template <>
-bool ValueRef::to<SkPoint>(SkPoint* v) const {
-    if (!fValue || !fValue->IsObject())
+bool Parse<SkPoint>(const Value& v, SkPoint* pt) {
+    if (!v.is<ObjectValue>())
         return false;
+    const auto& ov = v.as<ObjectValue>();
 
-    const auto jvx = ValueRef(this->operator[]("x")),
-               jvy = ValueRef(this->operator[]("y"));
+    const auto& jvx = ov["x"];
+    const auto& jvy = ov["y"];
 
     // Some BM versions seem to store x/y as single-element arrays.
-    return ValueRef(jvx.isArray() ? jvx.operator[](size_t(0)) : jvx).to(&v->fX)
-        && ValueRef(jvy.isArray() ? jvy.operator[](size_t(0)) : jvy).to(&v->fY);
+    return Parse<SkScalar>(jvx.is<ArrayValue>() ? jvx.as<ArrayValue>()[0] : jvx, &pt->fX)
+        && Parse<SkScalar>(jvy.is<ArrayValue>() ? jvy.as<ArrayValue>()[0] : jvy, &pt->fY);
 }
 
 template <>
-bool ValueRef::to<std::vector<float>>(std::vector<float>* v) const {
-    if (!fValue || !fValue->IsArray())
+bool Parse<std::vector<float>>(const Value& v, std::vector<float>* vec) {
+    if (!v.is<ArrayValue>())
         return false;
+    const auto& av = v.as<ArrayValue>();
 
-    v->resize(fValue->Size());
-    for (size_t i = 0; i < fValue->Size(); ++i) {
-        if (!ValueRef(fValue->operator[](i)).to(v->data() + i)) {
+    vec->resize(av.size());
+    for (size_t i = 0; i < av.size(); ++i) {
+        if (!Parse(av[i], vec->data() + i)) {
             return false;
         }
     }
@@ -111,16 +106,17 @@
 
 namespace {
 
-bool ParsePointVec(const ValueRef& jv, std::vector<SkPoint>* pts) {
-    if (!jv.isArray())
+bool ParsePointVec(const Value& v, std::vector<SkPoint>* pts) {
+    if (!v.is<ArrayValue>())
         return false;
+    const auto& av = v.as<ArrayValue>();
 
     pts->clear();
-    pts->reserve(jv.size());
+    pts->reserve(av.size());
 
     std::vector<float> vec;
-    for (size_t i = 0; i < jv.size(); ++i) {
-        if (!jv[i].to(&vec) || vec.size() != 2)
+    for (size_t i = 0; i < av.size(); ++i) {
+        if (!Parse(av[i], &vec) || vec.size() != 2)
             return false;
         pts->push_back(SkPoint::Make(vec[0], vec[1]));
     }
@@ -131,113 +127,40 @@
 } // namespace
 
 template <>
-bool ValueRef::to<ShapeValue>(ShapeValue* v) const {
-    SkASSERT(v->fVertices.empty());
-
-    if (!fValue)
-        return false;
+bool Parse<ShapeValue>(const Value& v, ShapeValue* sh) {
+    SkASSERT(sh->fVertices.empty());
 
     // Some versions wrap values as single-element arrays.
-    if (fValue->IsArray() && fValue->Size() == 1) {
-        return ValueRef(fValue->operator[](0)).to(v);
+    if (const skjson::ArrayValue* av = v) {
+        if (av->size() == 1) {
+            return Parse((*av)[0], sh);
+        }
     }
 
+    if (!v.is<skjson::ObjectValue>())
+        return false;
+    const auto& ov = v.as<ObjectValue>();
+
     std::vector<SkPoint> inPts,  // Cubic Bezier "in" control points, relative to vertices.
                          outPts, // Cubic Bezier "out" control points, relative to vertices.
                          verts;  // Cubic Bezier vertices.
 
-    if (!fValue->IsObject() ||
-        !ParsePointVec(this->operator[]("i"), &inPts) ||
-        !ParsePointVec(this->operator[]("o"), &outPts) ||
-        !ParsePointVec(this->operator[]("v"), &verts) ||
+    if (!ParsePointVec(ov["i"], &inPts) ||
+        !ParsePointVec(ov["o"], &outPts) ||
+        !ParsePointVec(ov["v"], &verts) ||
         inPts.size() != outPts.size() ||
         inPts.size() != verts.size()) {
 
         return false;
     }
 
-    v->fVertices.reserve(inPts.size());
+    sh->fVertices.reserve(inPts.size());
     for (size_t i = 0; i < inPts.size(); ++i) {
-        v->fVertices.push_back(BezierVertex({inPts[i], outPts[i], verts[i]}));
+        sh->fVertices.push_back(BezierVertex({inPts[i], outPts[i], verts[i]}));
     }
-    v->fClosed = this->operator[]("c").toDefault<bool>(false);
+    sh->fClosed = ParseDefault<bool>(ov["c"], false);
 
     return true;
 }
 
-size_t ValueRef::size() const {
-    return this->isArray() ? fValue->Size() : 0;
-}
-
-ValueRef ValueRef::operator[](size_t i) const {
-    return i < this->size() ? ValueRef(fValue->operator[](i)) : ValueRef();
-}
-
-ValueRef ValueRef::operator[](const char* key) const {
-    if (!this->isObject())
-        return ValueRef();
-
-    const auto m = fValue->FindMember(key);
-    return m == fValue->MemberEnd() ? ValueRef() : ValueRef(m->value);
-}
-
-const rapidjson::Value* ValueRef::begin() const {
-    return this->isArray() ? fValue->Begin() : nullptr;
-}
-
-const rapidjson::Value* ValueRef::end() const {
-    return this->isArray() ? fValue->End() : nullptr;
-}
-
-SkString ValueRef::toString() const {
-#ifdef SK_DEBUG
-    rapidjson::StringBuffer buf;
-    if (fValue) {
-        rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(buf);
-        fValue->Accept(writer);
-    }
-
-    return SkString(buf.GetString());
-#else
-    return SkString();
-#endif // SK_DEBUG
-}
-
-Document::Document(SkStream* stream) {
-    if (!stream->hasLength()) {
-        SkDebugf("!! unsupported unseekable json stream\n");
-        return;
-    }
-
-    // RapidJSON provides three DOM-builder approaches:
-    //
-    //   1) in-place   : all data buffered, constructs the DOM in-place -- this is the fastest
-    //   2) from buffer: all data buffered, copies to DOM -- this is slightly slower
-    //   3) from stream: streamed data, reads/copies to DOM -- this is *significantly* slower
-    //
-    // We like fast, so #1 it is.
-
-    // The buffer needs to be C-string.
-    const auto size = stream->getLength();
-    fData = SkData::MakeUninitialized(size + 1);
-    if (stream->read(fData->writable_data(), size) < size) {
-        SkDebugf("!! could not read JSON stream\n");
-        return;
-    }
-
-    auto data = static_cast<char*>(fData->writable_data());
-    data[size] = '\0';
-
-    fDocument.ParseInsitu(data);
-
-#ifdef SK_DEBUG
-        if (fDocument.HasParseError()) {
-            SkDebugf("!! failed to parse json: %s\n",
-                     rapidjson::GetParseError_En(fDocument.GetParseError()));
-        }
-#endif
-}
-
-} // namespace json
-
 } // namespace skottie
diff --git a/modules/skottie/src/SkottieJson.h b/modules/skottie/src/SkottieJson.h
index 76e17c6..98ee25a 100644
--- a/modules/skottie/src/SkottieJson.h
+++ b/modules/skottie/src/SkottieJson.h
@@ -8,68 +8,26 @@
 #ifndef SkottieJson_DEFINED
 #define SkottieJson_DEFINED
 
+#include "SkJSON.h"
 #include "SkRefCnt.h"
 
-#include "rapidjson/document.h"
-
 class SkData;
 class SkStream;
 class SkString;
 
 namespace skottie {
 
-namespace json {
+template <typename T>
+bool Parse(const skjson::Value&, T*);
 
-class ValueRef {
-public:
-    ValueRef() : fValue(nullptr) {}
-    ValueRef(const rapidjson::Value& v) : fValue(v.IsNull() ? nullptr : &v) {}
-
-    bool isNull()   const { return !fValue;   }
-    bool isObject() const { return fValue && fValue->IsObject(); }
-    bool isArray()  const { return fValue && fValue->IsArray();  }
-
-    template <typename T>
-    bool to(T*) const;
-
-    template <typename T>
-    T toDefault(const T& defaultValue) const {
-        T v;
-        if (!this->to<T>(&v)) {
-            v = defaultValue;
-        }
-        return v;
+template <typename T>
+T ParseDefault(const skjson::Value& v, const T& defaultValue) {
+    T res;
+    if (!Parse<T>(v, &res)) {
+        res = defaultValue;
     }
-
-    size_t size() const;
-    ValueRef operator[](size_t i) const;
-    ValueRef operator[](const char* key) const;
-
-    bool operator==(const ValueRef& other) const { return fValue == other.fValue; }
-    bool operator!=(const ValueRef& other) const { return !(*this == other); }
-
-    const rapidjson::Value* begin() const;
-    const rapidjson::Value* end() const;
-
-    SkString toString() const;
-
-private:
-    const rapidjson::Value*       fValue;
-};
-
-// Container for the json DOM
-class Document {
-public:
-    explicit Document(SkStream*);
-
-    ValueRef root() const { return fDocument; }
-
-private:
-    sk_sp<SkData>       fData;     // raw data
-    rapidjson::Document fDocument; // in-place json DOM
-};
-
-} // namespace json
+    return res;
+}
 
 } // namespace skottie
 
diff --git a/third_party/rapidjson/BUILD.gn b/third_party/rapidjson/BUILD.gn
deleted file mode 100644
index f9e50b2..0000000
--- a/third_party/rapidjson/BUILD.gn
+++ /dev/null
@@ -1,12 +0,0 @@
-# Copyright 2018 Google Inc.
-#
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import("../third_party.gni")
-
-third_party("rapidjson") {
-  public_include_dirs = [ "../externals/rapidjson/include" ]
-  defines = [ "RAPIDJSON_HAS_CXX11_RVALUE_REFS=1" ]
-  sources = []
-}