bool TinyGLTF::LoadFromString(Model *model, std::string *err, std::string *warn,
                              const char *json_str,
                              unsigned int json_str_length,
                              const std::string &base_dir,
                              unsigned int check_sections) {
  if (json_str_length < 4) {
    if (err) {
      (*err) = "JSON string too short.\n";
    }
    return false;
  }

  JsonDocument v;

#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || \
     defined(_CPPUNWIND)) &&                               \
    !defined(TINYGLTF_NOEXCEPTION)
  try {
    JsonParse(v, json_str, json_str_length, true);

  } catch (const std::exception &e) {
    if (err) {
      (*err) = e.what();
    }
    return false;
  }
#else
  {
    JsonParse(v, json_str, json_str_length);

    if (!IsObject(v)) {
      // Assume parsing was failed.
      if (err) {
        (*err) = "Failed to parse JSON object\n";
      }
      return false;
    }
  }
#endif

  if (!IsObject(v)) {
    // root is not an object.
    if (err) {
      (*err) = "Root element is not a JSON object\n";
    }
    return false;
  }

  {
    bool version_found = false;
    json_const_iterator it;
    if (FindMember(v, "asset", it) && IsObject(GetValue(it))) {
      auto &itObj = GetValue(it);
      json_const_iterator version_it;
      std::string versionStr;
      if (FindMember(itObj, "version", version_it) &&
          GetString(GetValue(version_it), versionStr)) {
        version_found = true;
      }
    }
    if (version_found) {
      // OK
    } else if (check_sections & REQUIRE_VERSION) {
      if (err) {
        (*err) += "\"asset\" object not found in .gltf or not an object type\n";
      }
      return false;
    }
  }

  // scene is not mandatory.
  // FIXME Maybe a better way to handle it than removing the code

  auto IsArrayMemberPresent = [](const json &_v, const char *name) -> bool {
    json_const_iterator it;
    return FindMember(_v, name, it) && IsArray(GetValue(it));
  };

  {
    if ((check_sections & REQUIRE_SCENES) &&
        !IsArrayMemberPresent(v, "scenes")) {
      if (err) {
        (*err) += "\"scenes\" object not found in .gltf or not an array type\n";
      }
      return false;
    }
  }

  {
    if ((check_sections & REQUIRE_NODES) && !IsArrayMemberPresent(v, "nodes")) {
      if (err) {
        (*err) += "\"nodes\" object not found in .gltf\n";
      }
      return false;
    }
  }

  {
    if ((check_sections & REQUIRE_ACCESSORS) &&
        !IsArrayMemberPresent(v, "accessors")) {
      if (err) {
        (*err) += "\"accessors\" object not found in .gltf\n";
      }
      return false;
    }
  }

  {
    if ((check_sections & REQUIRE_BUFFERS) &&
        !IsArrayMemberPresent(v, "buffers")) {
      if (err) {
        (*err) += "\"buffers\" object not found in .gltf\n";
      }
      return false;
    }
  }

  {
    if ((check_sections & REQUIRE_BUFFER_VIEWS) &&
        !IsArrayMemberPresent(v, "bufferViews")) {
      if (err) {
        (*err) += "\"bufferViews\" object not found in .gltf\n";
      }
      return false;
    }
  }

  model->buffers.clear();
  model->bufferViews.clear();
  model->accessors.clear();
  model->meshes.clear();
  model->cameras.clear();
  model->nodes.clear();
  model->extensionsUsed.clear();
  model->extensionsRequired.clear();
  model->extensions.clear();
  model->defaultScene = -1;

  // 1. Parse Asset
  {
    json_const_iterator it;
    if (FindMember(v, "asset", it) && IsObject(GetValue(it))) {
      const json &root = GetValue(it);

      ParseAsset(&model->asset, err, root,
                 store_original_json_for_extras_and_extensions_);
    }
  }

#ifdef TINYGLTF_USE_CPP14
  auto ForEachInArray = [](const json &_v, const char *member,
                           const auto &cb) -> bool
#else
  // The std::function<> implementation can be less efficient because it will
  // allocate heap when the size of the captured lambda is above 16 bytes with
  // clang and gcc, but it does not require C++14.
  auto ForEachInArray = [](const json &_v, const char *member,
                           const std::function<bool(const json &)> &cb) -> bool
#endif
  {
    json_const_iterator itm;
    if (FindMember(_v, member, itm) && IsArray(GetValue(itm))) {
      const json &root = GetValue(itm);
      auto it = ArrayBegin(root);
      auto end = ArrayEnd(root);
      for (; it != end; ++it) {
        if (!cb(*it)) return false;
      }
    }
    return true;
  };

  // 2. Parse extensionUsed
  {
    ForEachInArray(v, "extensionsUsed", [&](const json &o) {
      std::string str;
      GetString(o, str);
      model->extensionsUsed.emplace_back(std::move(str));
      return true;
    });
  }

  {
    ForEachInArray(v, "extensionsRequired", [&](const json &o) {
      std::string str;
      GetString(o, str);
      model->extensionsRequired.emplace_back(std::move(str));
      return true;
    });
  }

  // 3. Parse Buffer
  {
    bool success = ForEachInArray(v, "buffers", [&](const json &o) {
      if (!IsObject(o)) {
        if (err) {
          (*err) += "`buffers' does not contain an JSON object.";
        }
        return false;
      }
      Buffer buffer;
      if (!ParseBuffer(&buffer, err, o,
                       store_original_json_for_extras_and_extensions_, &fs,
                       base_dir, is_binary_, bin_data_, bin_size_)) {
        return false;
      }

      model->buffers.emplace_back(std::move(buffer));
      return true;
    });

    if (!success) {
      return false;
    }
  }
  // 4. Parse BufferView
  {
    bool success = ForEachInArray(v, "bufferViews", [&](const json &o) {
      if (!IsObject(o)) {
        if (err) {
          (*err) += "`bufferViews' does not contain an JSON object.";
        }
        return false;
      }
      BufferView bufferView;
      if (!ParseBufferView(&bufferView, err, o,
                           store_original_json_for_extras_and_extensions_)) {
        return false;
      }

      model->bufferViews.emplace_back(std::move(bufferView));
      return true;
    });

    if (!success) {
      return false;
    }
  }

  // 5. Parse Accessor
  {
    bool success = ForEachInArray(v, "accessors", [&](const json &o) {
      if (!IsObject(o)) {
        if (err) {
          (*err) += "`accessors' does not contain an JSON object.";
        }
        return false;
      }
      Accessor accessor;
      if (!ParseAccessor(&accessor, err, o,
                         store_original_json_for_extras_and_extensions_)) {
        return false;
      }

      model->accessors.emplace_back(std::move(accessor));
      return true;
    });

    if (!success) {
      return false;
    }
  }

  // 6. Parse Mesh
  {
    bool success = ForEachInArray(v, "meshes", [&](const json &o) {
      if (!IsObject(o)) {
        if (err) {
          (*err) += "`meshes' does not contain an JSON object.";
        }
        return false;
      }
      Mesh mesh;
      if (!ParseMesh(&mesh, model, err, o,
                     store_original_json_for_extras_and_extensions_)) {
        return false;
      }

      model->meshes.emplace_back(std::move(mesh));
      return true;
    });

    if (!success) {
      return false;
    }
  }

  // Assign missing bufferView target types
  // - Look for missing Mesh indices
  // - Look for missing Mesh attributes
  for (auto &mesh : model->meshes) {
    for (auto &primitive : mesh.primitives) {
      if (primitive.indices >
          -1)  // has indices from parsing step, must be Element Array Buffer
      {
        if (size_t(primitive.indices) >= model->accessors.size()) {
          if (err) {
            (*err) += "primitive indices accessor out of bounds";
          }
          return false;
        }

        auto bufferView =
            model->accessors[size_t(primitive.indices)].bufferView;
        if (bufferView < 0 || size_t(bufferView) >= model->bufferViews.size()) {
          if (err) {
            (*err) += "accessor[" + std::to_string(primitive.indices) +
                      "] invalid bufferView";
          }
          return false;
        }

        model->bufferViews[size_t(bufferView)].target =
            TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER;
        // we could optionally check if acessors' bufferView type is Scalar, as
        // it should be
      }

      for (auto &attribute : primitive.attributes) {
        model
            ->bufferViews[size_t(
                model->accessors[size_t(attribute.second)].bufferView)]
            .target = TINYGLTF_TARGET_ARRAY_BUFFER;
      }

      for (auto &target : primitive.targets) {
        for (auto &attribute : target) {
          auto bufferView =
              model->accessors[size_t(attribute.second)].bufferView;
          // bufferView could be null(-1) for sparse morph target
          if (bufferView >= 0) {
            model->bufferViews[size_t(bufferView)].target =
                TINYGLTF_TARGET_ARRAY_BUFFER;
          }
        }
      }
    }
  }

  // 7. Parse Node
  {
    bool success = ForEachInArray(v, "nodes", [&](const json &o) {
      if (!IsObject(o)) {
        if (err) {
          (*err) += "`nodes' does not contain an JSON object.";
        }
        return false;
      }
      Node node;
      if (!ParseNode(&node, err, o,
                     store_original_json_for_extras_and_extensions_)) {
        return false;
      }

      model->nodes.emplace_back(std::move(node));
      return true;
    });

    if (!success) {
      return false;
    }
  }

  // 8. Parse scenes.
  {
    bool success = ForEachInArray(v, "scenes", [&](const json &o) {
      if (!IsObject(o)) {
        if (err) {
          (*err) += "`scenes' does not contain an JSON object.";
        }
        return false;
      }
      std::vector<int> nodes;
      ParseIntegerArrayProperty(&nodes, err, o, "nodes", false);

      Scene scene;
      scene.nodes = std::move(nodes);

      ParseStringProperty(&scene.name, err, o, "name", false);

      ParseExtensionsProperty(&scene.extensions, err, o);
      ParseExtrasProperty(&scene.extras, o);

      if (store_original_json_for_extras_and_extensions_) {
        {
          json_const_iterator it;
          if (FindMember(o, "extensions", it)) {
            scene.extensions_json_string = JsonToString(GetValue(it));
          }
        }
        {
          json_const_iterator it;
          if (FindMember(o, "extras", it)) {
            scene.extras_json_string = JsonToString(GetValue(it));
          }
        }
      }

      model->scenes.emplace_back(std::move(scene));
      return true;
    });

    if (!success) {
      return false;
    }
  }

  // 9. Parse default scenes.
  {
    json_const_iterator rootIt;
    int iVal;
    if (FindMember(v, "scene", rootIt) && GetInt(GetValue(rootIt), iVal)) {
      model->defaultScene = iVal;
    }
  }

  // 10. Parse Material
  {
    bool success = ForEachInArray(v, "materials", [&](const json &o) {
      if (!IsObject(o)) {
        if (err) {
          (*err) += "`materials' does not contain an JSON object.";
        }
        return false;
      }
      Material material;
      ParseStringProperty(&material.name, err, o, "name", false);

      if (!ParseMaterial(&material, err, o,
                         store_original_json_for_extras_and_extensions_)) {
        return false;
      }

      model->materials.emplace_back(std::move(material));
      return true;
    });

    if (!success) {
      return false;
    }
  }

  // 11. Parse Image
  void *load_image_user_data{nullptr};

  LoadImageDataOption load_image_option;

  if (user_image_loader_) {
    // Use user supplied pointer
    load_image_user_data = load_image_user_data_;
  } else {
    load_image_option.preserve_channels = preserve_image_channels_;
    load_image_user_data = reinterpret_cast<void *>(&load_image_option);
  }

  {
    int idx = 0;
    bool success = ForEachInArray(v, "images", [&](const json &o) {
      if (!IsObject(o)) {
        if (err) {
          (*err) += "image[" + std::to_string(idx) + "] is not a JSON object.";
        }
        return false;
      }
      Image image;
      if (!ParseImage(&image, idx, err, warn, o,
                      store_original_json_for_extras_and_extensions_, base_dir,
                      &fs, &this->LoadImageData, load_image_user_data)) {
        return false;
      }

      if (image.bufferView != -1) {
        // Load image from the buffer view.
        if (size_t(image.bufferView) >= model->bufferViews.size()) {
          if (err) {
            std::stringstream ss;
            ss << "image[" << idx << "] bufferView \"" << image.bufferView
               << "\" not found in the scene." << std::endl;
            (*err) += ss.str();
          }
          return false;
        }

        const BufferView &bufferView =
            model->bufferViews[size_t(image.bufferView)];
        if (size_t(bufferView.buffer) >= model->buffers.size()) {
          if (err) {
            std::stringstream ss;
            ss << "image[" << idx << "] buffer \"" << bufferView.buffer
               << "\" not found in the scene." << std::endl;
            (*err) += ss.str();
          }
          return false;
        }
        const Buffer &buffer = model->buffers[size_t(bufferView.buffer)];

        if (*LoadImageData == nullptr) {
          if (err) {
            (*err) += "No LoadImageData callback specified.\n";
          }
          return false;
        }
        bool ret = LoadImageData(
            &image, idx, err, warn, image.width, image.height,
            &buffer.data[bufferView.byteOffset],
            static_cast<int>(bufferView.byteLength), load_image_user_data);
        if (!ret) {
          return false;
        }
      }

      model->images.emplace_back(std::move(image));
      ++idx;
      return true;
    });

    if (!success) {
      return false;
    }
  }

  // 12. Parse Texture
  {
    bool success = ForEachInArray(v, "textures", [&](const json &o) {
      if (!IsObject(o)) {
        if (err) {
          (*err) += "`textures' does not contain an JSON object.";
        }
        return false;
      }
      Texture texture;
      if (!ParseTexture(&texture, err, o,
                        store_original_json_for_extras_and_extensions_,
                        base_dir)) {
        return false;
      }

      model->textures.emplace_back(std::move(texture));
      return true;
    });

    if (!success) {
      return false;
    }
  }

  // 13. Parse Animation
  {
    bool success = ForEachInArray(v, "animations", [&](const json &o) {
      if (!IsObject(o)) {
        if (err) {
          (*err) += "`animations' does not contain an JSON object.";
        }
        return false;
      }
      Animation animation;
      if (!ParseAnimation(&animation, err, o,
                          store_original_json_for_extras_and_extensions_)) {
        return false;
      }

      model->animations.emplace_back(std::move(animation));
      return true;
    });

    if (!success) {
      return false;
    }
  }

  // 14. Parse Skin
  {
    bool success = ForEachInArray(v, "skins", [&](const json &o) {
      if (!IsObject(o)) {
        if (err) {
          (*err) += "`skins' does not contain an JSON object.";
        }
        return false;
      }
      Skin skin;
      if (!ParseSkin(&skin, err, o,
                     store_original_json_for_extras_and_extensions_)) {
        return false;
      }

      model->skins.emplace_back(std::move(skin));
      return true;
    });

    if (!success) {
      return false;
    }
  }

  // 15. Parse Sampler
  {
    bool success = ForEachInArray(v, "samplers", [&](const json &o) {
      if (!IsObject(o)) {
        if (err) {
          (*err) += "`samplers' does not contain an JSON object.";
        }
        return false;
      }
      Sampler sampler;
      if (!ParseSampler(&sampler, err, o,
                        store_original_json_for_extras_and_extensions_)) {
        return false;
      }

      model->samplers.emplace_back(std::move(sampler));
      return true;
    });

    if (!success) {
      return false;
    }
  }

  // 16. Parse Camera
  {
    bool success = ForEachInArray(v, "cameras", [&](const json &o) {
      if (!IsObject(o)) {
        if (err) {
          (*err) += "`cameras' does not contain an JSON object.";
        }
        return false;
      }
      Camera camera;
      if (!ParseCamera(&camera, err, o,
                       store_original_json_for_extras_and_extensions_)) {
        return false;
      }

      model->cameras.emplace_back(std::move(camera));
      return true;
    });

    if (!success) {
      return false;
    }
  }

  // 17. Parse Extensions
  ParseExtensionsProperty(&model->extensions, err, v);

  // 18. Specific extension implementations
  {
    json_const_iterator rootIt;
    if (FindMember(v, "extensions", rootIt) && IsObject(GetValue(rootIt))) {
      const json &root = GetValue(rootIt);

      json_const_iterator it(ObjectBegin(root));
      json_const_iterator itEnd(ObjectEnd(root));
      for (; it != itEnd; ++it) {
        // parse KHR_lights_punctual extension
        std::string key(GetKey(it));
        if ((key == "KHR_lights_punctual") && IsObject(GetValue(it))) {
          const json &object = GetValue(it);
          json_const_iterator itLight;
          if (FindMember(object, "lights", itLight)) {
            const json &lights = GetValue(itLight);
            if (!IsArray(lights)) {
              continue;
            }

            auto arrayIt(ArrayBegin(lights));
            auto arrayItEnd(ArrayEnd(lights));
            for (; arrayIt != arrayItEnd; ++arrayIt) {
              Light light;
              if (!ParseLight(&light, err, *arrayIt,
                              store_original_json_for_extras_and_extensions_)) {
                return false;
              }
              model->lights.emplace_back(std::move(light));
            }
          }
        }
      }
    }
  }

  // 19. Parse Extras
  ParseExtrasProperty(&model->extras, v);

  if (store_original_json_for_extras_and_extensions_) {
    model->extras_json_string = JsonToString(v["extras"]);
    model->extensions_json_string = JsonToString(v["extensions"]);
  }

  return true;
}