Replace json dependency with llvm::json

PiperOrigin-RevId: 434896655
diff --git a/rs_bindings_from_cc/cmdline.cc b/rs_bindings_from_cc/cmdline.cc
index eaf9357..26d75cb 100644
--- a/rs_bindings_from_cc/cmdline.cc
+++ b/rs_bindings_from_cc/cmdline.cc
@@ -11,8 +11,9 @@
 #include <vector>
 
 #include "third_party/absl/flags/flag.h"
+#include "third_party/absl/strings/str_cat.h"
 #include "third_party/absl/strings/substitute.h"
-#include "third_party/json/include/nlohmann/json.hpp"
+#include "third_party/llvm/llvm-project/llvm/include/llvm/Support/JSON.h"
 #include "util/task/status_macros.h"
 
 ABSL_FLAG(bool, do_nothing, false,
@@ -42,6 +43,21 @@
 
 namespace rs_bindings_from_cc {
 
+namespace {
+
+struct TargetAndHeaders {
+  std::string target;
+  std::vector<std::string> headers;
+};
+
+bool fromJSON(const llvm::json::Value& json, TargetAndHeaders& out,
+              llvm::json::Path path) {
+  llvm::json::ObjectMapper mapper(json, path);
+  return mapper && mapper.map("t", out.target) && mapper.map("h", out.headers);
+}
+
+}  // namespace
+
 absl::StatusOr<Cmdline> Cmdline::Create() {
   return CreateFromArgs(
       absl::GetFlag(FLAGS_cc_out), absl::GetFlag(FLAGS_rs_out),
@@ -79,56 +95,33 @@
   if (targets_and_headers_str.empty()) {
     return absl::InvalidArgumentError("please specify --targets_and_headers");
   }
-  nlohmann::json targets_and_headers =
-      nlohmann::json::parse(std::move(targets_and_headers_str),
-                            /* cb= */ nullptr,
-                            /* allow_exceptions= */ false);
-  if (!targets_and_headers.is_array()) {
+  auto targets_and_headers = llvm::json::parse<std::vector<TargetAndHeaders>>(
+      std::move(targets_and_headers_str));
+  if (auto err = targets_and_headers.takeError()) {
     return absl::InvalidArgumentError(
-        "Expected `--targets_and_headers` to be a JSON array of objects");
+        absl::StrCat("Malformed `--targets_and_headers` argument: ",
+                     toString(std::move(err))));
   }
-  for (const auto& target_and_headers : targets_and_headers) {
-    if (!target_and_headers.contains("t")) {
-      return absl::InvalidArgumentError(
-          "Missing `t` field in an `--targets_and_headers` object");
-    }
-    if (!target_and_headers["t"].is_string()) {
-      return absl::InvalidArgumentError(
-          "Expected `t` fields of `--targets_and_headers` to be a string");
-    }
-    if (!target_and_headers.contains("h")) {
-      return absl::InvalidArgumentError(
-          "Missing `h` field in an `--targets_and_headers` object");
-    }
-    if (!target_and_headers["h"].is_array()) {
-      return absl::InvalidArgumentError(
-          "Expected `h` fields of `--targets_and_headers` to be an array");
-    }
-    BlazeLabel target{std::string(target_and_headers["t"])};
-    if (target.value().empty()) {
+  for (const TargetAndHeaders& it : *targets_and_headers) {
+    const std::string& target = it.target;
+    if (target.empty()) {
       return absl::InvalidArgumentError(
           "Expected `t` fields of `--targets_and_headers` to be a non-empty "
           "string");
     }
-    for (const auto& header : target_and_headers["h"]) {
-      if (!header.is_string()) {
-        return absl::InvalidArgumentError(
-            "Expected `h` fields of `--targets_and_headers` to be an array of "
-            "strings");
-      }
-      std::string header_str(header);
-      if (header_str.empty()) {
+    for (const std::string& header : it.headers) {
+      if (header.empty()) {
         return absl::InvalidArgumentError(
             "Expected `h` fields of `--targets_and_headers` to be an array of "
             "non-empty strings");
       }
       const auto [it, inserted] = cmdline.headers_to_targets_.insert(
-          std::make_pair(HeaderName(header_str), target));
+          std::make_pair(HeaderName(header), BlazeLabel(target)));
       if (!inserted) {
         return absl::InvalidArgumentError(absl::Substitute(
             "The `--targets_and_headers` cmdline argument assigns "
             "`$0` header to two conflicting targets: `$1` vs `$2`",
-            header_str, target.value(), it->second.value()));
+            header, target, it->second.value()));
       }
     }
   }
diff --git a/rs_bindings_from_cc/cmdline_test.cc b/rs_bindings_from_cc/cmdline_test.cc
index a05dbca..8dd5fdc 100644
--- a/rs_bindings_from_cc/cmdline_test.cc
+++ b/rs_bindings_from_cc/cmdline_test.cc
@@ -57,10 +57,10 @@
 }
 
 TEST(CmdlineTest, TargetsAndHeadersInvalidJson) {
-  ASSERT_THAT(
-      TestCmdline({"h1"}, "#!$%"),
-      StatusIs(absl::StatusCode::kInvalidArgument,
-               AllOf(HasSubstr("--targets_and_headers"), HasSubstr("array"))));
+  ASSERT_THAT(TestCmdline({"h1"}, "#!$%"),
+              StatusIs(absl::StatusCode::kInvalidArgument,
+                       AllOf(HasSubstr("--targets_and_headers"),
+                             HasSubstr("Invalid JSON"))));
 }
 
 TEST(CmdlineTest, TargetsAndHeadersIntInsteadOfTopLevelArray) {
@@ -80,21 +80,21 @@
   ASSERT_THAT(TestCmdline({"h1"}, R"([{"t": "t1", "h": 123}])"),
               StatusIs(absl::StatusCode::kInvalidArgument,
                        AllOf(HasSubstr("--targets_and_headers"),
-                             HasSubstr("`h`"), HasSubstr("array"))));
+                             HasSubstr(".h"), HasSubstr("array"))));
 }
 
 TEST(CmdlineTest, TargetsAndHeadersMissingTarget) {
   ASSERT_THAT(TestCmdline({"h1"}, R"([{"h": ["h1", "h2"]}])"),
               StatusIs(absl::StatusCode::kInvalidArgument,
                        AllOf(HasSubstr("--targets_and_headers"),
-                             HasSubstr("`t`"), HasSubstr("Missing"))));
+                             HasSubstr(".t"), HasSubstr("missing"))));
 }
 
 TEST(CmdlineTest, TargetsAndHeadersMissingHeader) {
   ASSERT_THAT(TestCmdline({"h1"}, R"([{"t": "t1"}])"),
               StatusIs(absl::StatusCode::kInvalidArgument,
                        AllOf(HasSubstr("--targets_and_headers"),
-                             HasSubstr("`h`"), HasSubstr("Missing"))));
+                             HasSubstr(".h"), HasSubstr("missing"))));
 }
 
 TEST(CmdlineTest, TargetsAndHeadersEmptyHeader) {
@@ -115,14 +115,14 @@
   ASSERT_THAT(TestCmdline({"h1"}, R"([{"t": 123, "h": ["h1", "h2"]}])"),
               StatusIs(absl::StatusCode::kInvalidArgument,
                        AllOf(HasSubstr("--targets_and_headers"),
-                             HasSubstr("`t`"), HasSubstr("string"))));
+                             HasSubstr(".t"), HasSubstr("string"))));
 }
 
 TEST(CmdlineTest, TargetsAndHeadersIntInsteadOfHeader) {
   ASSERT_THAT(TestCmdline({"h1"}, R"([{"t": "t1", "h": [123, "h2"]}])"),
               StatusIs(absl::StatusCode::kInvalidArgument,
                        AllOf(HasSubstr("--targets_and_headers"),
-                             HasSubstr("`h`"), HasSubstr("string"))));
+                             HasSubstr(".h"), HasSubstr("string"))));
 }
 
 TEST(CmdlineTest, TargetsAndHeadersDuplicateHeader) {
diff --git a/rs_bindings_from_cc/importer.cc b/rs_bindings_from_cc/importer.cc
index 05bb5dd..aad9d40 100644
--- a/rs_bindings_from_cc/importer.cc
+++ b/rs_bindings_from_cc/importer.cc
@@ -47,6 +47,7 @@
 #include "third_party/llvm/llvm-project/llvm/include/llvm/ADT/Optional.h"
 #include "third_party/llvm/llvm-project/llvm/include/llvm/ADT/SmallPtrSet.h"
 #include "third_party/llvm/llvm-project/llvm/include/llvm/Support/Casting.h"
+#include "third_party/llvm/llvm-project/llvm/include/llvm/Support/ErrorHandling.h"
 #include "third_party/llvm/llvm-project/llvm/include/llvm/Support/Regex.h"
 #include "util/task/status_macros.h"
 
@@ -154,10 +155,10 @@
       }
       const clang::CXXRecordDecl* base_record_decl =
           ABSL_DIE_IF_NULL(base_specifier.getType()->getAsCXXRecordDecl());
-      std::optional<int64_t> offset = {0};
+      llvm::Optional<int64_t> offset = {0};
       for (const clang::CXXBasePathElement& base_path_element : path) {
         if (base_path_element.Base->isVirtual()) {
-          offset = std::nullopt;
+          offset.reset();
           break;
         }
         *offset +=
@@ -166,7 +167,7 @@
                      base_path_element.Base->getType()->getAsCXXRecordDecl()))
                  .getQuantity()};
       }
-      DCHECK(!offset.has_value() || *offset >= 0)
+      DCHECK(!offset.hasValue() || *offset >= 0)
           << "Concrete base classes should have non-negative offsets.";
       bases.push_back(
           BaseClass{.base_record_id = GenerateDeclId(base_record_decl),
@@ -414,20 +415,18 @@
     if (i > 0) {
       const auto& prev = items[i - 1];
       if (are_equal(item, prev)) {
-        std::string prev_json =
-            std::visit([&](auto&& item) { return item.ToJson().dump(); },
-                       std::get<2>(prev));
-        std::string curr_json =
-            std::visit([&](auto&& item) { return item.ToJson().dump(); },
-                       std::get<2>(item));
+        llvm::json::Value prev_json = std::visit(
+            [&](auto&& item) { return item.ToJson(); }, std::get<2>(prev));
+        llvm::json::Value curr_json = std::visit(
+            [&](auto&& item) { return item.ToJson(); }, std::get<2>(item));
         if (prev_json != curr_json) {
-          LOG(FATAL) << "Non-deterministic order of IR items: " << prev_json
-                     << " -VS- " << curr_json;
+          llvm::report_fatal_error(
+              llvm::formatv("Non-deterministic order of IR items: {0} -VS- {1}",
+                            prev_json, curr_json));
         } else {
           // TODO(lukasza): Avoid generating duplicate IR items.  Currently
           // known example: UnsupportedItem: name=std::signbit; message=
           // Items contained in namespaces are not supported yet.
-          LOG(WARNING) << "Duplicated IR item: " << curr_json;
           continue;
         }
       }
@@ -612,7 +611,7 @@
       lifetime_params.begin(), lifetime_params.end(),
       [](const Lifetime& l1, const Lifetime& l2) { return l1.name < l2.name; });
 
-  std::optional<MemberFuncMetadata> member_func_metadata;
+  llvm::Optional<MemberFuncMetadata> member_func_metadata;
   if (auto* method_decl =
           clang::dyn_cast<clang::CXXMethodDecl>(function_decl)) {
     switch (method_decl->getAccess()) {
@@ -625,7 +624,8 @@
         // TODO(lukasza): Revisit this for protected methods.
         return LookupResult();
     }
-    std::optional<MemberFuncMetadata::InstanceMethodMetadata> instance_metadata;
+    llvm::Optional<MemberFuncMetadata::InstanceMethodMetadata>
+        instance_metadata;
     if (method_decl->isInstance()) {
       MemberFuncMetadata::ReferenceQualification reference;
       switch (method_decl->getRefQualifier()) {
@@ -749,7 +749,7 @@
 
   const clang::ASTRecordLayout& layout = ctx_.getASTRecordLayout(record_decl);
 
-  std::optional<size_t> base_size = std::nullopt;
+  llvm::Optional<size_t> base_size;
   bool override_alignment = record_decl->hasAttr<clang::AlignedAttr>();
   if (record_decl->getNumBases() != 0) {
     // The size of the base class subobjects is easy to compute, so long as we
@@ -907,7 +907,8 @@
   return !patterns_to_ignore.match(line);
 }
 
-std::optional<std::string> Importer::GetComment(const clang::Decl* decl) const {
+llvm::Optional<std::string> Importer::GetComment(
+    const clang::Decl* decl) const {
   // This does currently not distinguish between different types of comments.
   // In general it is not possible in C++ to reliably only extract doc comments.
   // This is going to be a heuristic that needs to be tuned over time.
@@ -923,9 +924,8 @@
       raw_comment->getFormattedText(sm, sm.getDiagnostics());
   std::string cleaned_comment_text = absl::StrJoin(
       absl::StrSplit(raw_comment_text, '\n', ShouldKeepCommentLine), "\n");
-  return cleaned_comment_text.empty()
-             ? std::nullopt
-             : std::optional<std::string>(std::move(cleaned_comment_text));
+  if (cleaned_comment_text.empty()) return {};
+  return cleaned_comment_text;
 }
 
 SourceLoc Importer::ConvertSourceLocation(clang::SourceLocation loc) const {
diff --git a/rs_bindings_from_cc/importer.h b/rs_bindings_from_cc/importer.h
index e5b9a91..ac6d2f8 100644
--- a/rs_bindings_from_cc/importer.h
+++ b/rs_bindings_from_cc/importer.h
@@ -159,7 +159,7 @@
   }
 
   // Gets the doc comment of the declaration.
-  std::optional<std::string> GetComment(const clang::Decl* decl) const;
+  llvm::Optional<std::string> GetComment(const clang::Decl* decl) const;
 
   // Converts the Clang type `qual_type` into an equivalent `MappedType`.
   // Lifetimes for the type can optionally be specified using `lifetimes`.
diff --git a/rs_bindings_from_cc/importer_test.cc b/rs_bindings_from_cc/importer_test.cc
index fba5014..04fbdf4 100644
--- a/rs_bindings_from_cc/importer_test.cc
+++ b/rs_bindings_from_cc/importer_test.cc
@@ -114,10 +114,10 @@
 
 // Matches an RsType or CcType that has the given decl_id.
 MATCHER_P(DeclIdIs, decl_id, "") {
-  if (arg.decl_id.has_value() && *arg.decl_id == decl_id) return true;
+  if (arg.decl_id.hasValue() && *arg.decl_id == decl_id) return true;
 
   *result_listener << "actual decl_id: ";
-  if (arg.decl_id.has_value()) {
+  if (arg.decl_id.hasValue()) {
     *result_listener << *arg.decl_id;
   } else {
     *result_listener << "std::nullopt";
diff --git a/rs_bindings_from_cc/ir.cc b/rs_bindings_from_cc/ir.cc
index 060f316..a949ad5 100644
--- a/rs_bindings_from_cc/ir.cc
+++ b/rs_bindings_from_cc/ir.cc
@@ -16,65 +16,57 @@
 #include "base/integral_types.h"
 #include "third_party/absl/strings/string_view.h"
 #include "rs_bindings_from_cc/bazel_types.h"
-#include "third_party/json/src/json.hpp"
+#include "third_party/llvm/llvm-project/llvm/include/llvm/Support/JSON.h"
 #include "util/intops/strong_int.h"
 
 namespace rs_bindings_from_cc {
 
 template <class T>
-static std::vector<nlohmann::json> VectorToJson(const std::vector<T>& v) {
-  std::vector<nlohmann::json> result;
-  result.reserve(v.size());
-  for (const T& t : v) {
-    result.push_back(t.ToJson());
-  }
-  return result;
+llvm::json::Value toJSON(const T& t) {
+  return t.ToJson();
 }
 
-nlohmann::json HeaderName::ToJson() const {
-  nlohmann::json result;
-  result["name"] = name_;
-  return result;
+template <typename TTag, typename TInt>
+llvm::json::Value toJSON(const util_intops::StrongInt<TTag, TInt> strong_int) {
+  return llvm::json::Value(strong_int.value());
 }
 
-nlohmann::json Lifetime::ToJson() const {
-  nlohmann::json result;
-  result["name"] = name;
-  result["id"] = id.value();
-  return result;
+template <typename TTag>
+llvm::json::Value toJSON(const gtl::labs::StringType<TTag> string_type) {
+  return llvm::json::Value(string_type.value());
 }
 
-nlohmann::json RsType::ToJson() const {
-  nlohmann::json result;
-
-  if (decl_id.has_value()) {
-    result["decl_id"] = decl_id->value();
-  } else {
-    result["name"] = name;
-  }
-  std::vector<nlohmann::json> json_lifetime_args;
-  json_lifetime_args.reserve(lifetime_args.size());
-  for (const LifetimeId& lifetime_id : lifetime_args) {
-    json_lifetime_args.push_back(lifetime_id.value());
-  }
-  result["lifetime_args"] = json_lifetime_args;
-  result["type_args"] = VectorToJson(type_args);
-
-  return result;
+llvm::json::Value HeaderName::ToJson() const {
+  return llvm::json::Object{
+      {"name", name_},
+  };
 }
 
-nlohmann::json CcType::ToJson() const {
-  nlohmann::json result;
+llvm::json::Value Lifetime::ToJson() const {
+  return llvm::json::Object{
+      {"name", name},
+      {"id", id},
+  };
+}
 
-  if (decl_id.has_value()) {
-    result["decl_id"] = decl_id->value();
-  } else {
-    result["name"] = name;
-  }
-  result["is_const"] = is_const;
-  result["type_args"] = VectorToJson(type_args);
+llvm::json::Value RsType::ToJson() const {
+  return llvm::json::Object{
+      {"name", decl_id.hasValue() ? llvm::json::Value(nullptr)
+                                  : llvm::json::Value(name)},
+      {"lifetime_args", lifetime_args},
+      {"type_args", type_args},
+      {"decl_id", decl_id},
+  };
+}
 
-  return result;
+llvm::json::Value CcType::ToJson() const {
+  return llvm::json::Object{
+      {"name", decl_id.hasValue() ? llvm::json::Value(nullptr)
+                                  : llvm::json::Value(name)},
+      {"is_const", is_const},
+      {"type_args", type_args},
+      {"decl_id", decl_id},
+  };
 }
 
 static MappedType PointerOrReferenceTo(MappedType pointee_type,
@@ -175,32 +167,30 @@
   };
 }
 
-nlohmann::json MappedType::ToJson() const {
-  nlohmann::json result;
-
-  result["rs_type"] = rs_type.ToJson();
-  result["cc_type"] = cc_type.ToJson();
-
-  return result;
+llvm::json::Value MappedType::ToJson() const {
+  return llvm::json::Object{
+      {"rs_type", rs_type},
+      {"cc_type", cc_type},
+  };
 }
 
-nlohmann::json Identifier::ToJson() const {
-  nlohmann::json result;
-  result["identifier"] = identifier_;
-  return result;
+llvm::json::Value Identifier::ToJson() const {
+  return llvm::json::Object{
+      {"identifier", identifier_},
+  };
 }
 
-nlohmann::json IntegerConstant::ToJson() const {
-  nlohmann::json result;
-  result["is_negative"] = is_negative_;
-  result["wrapped_value"] = wrapped_value_;
-  return result;
+llvm::json::Value IntegerConstant::ToJson() const {
+  return llvm::json::Object{
+      {"is_negative", is_negative_},
+      {"wrapped_value", wrapped_value_},
+  };
 }
 
-nlohmann::json Operator::ToJson() const {
-  nlohmann::json result;
-  result["name"] = name_;
-  return result;
+llvm::json::Value Operator::ToJson() const {
+  return llvm::json::Object{
+      {"name", name_},
+  };
 }
 
 static std::string SpecialNameToString(SpecialName special_name) {
@@ -212,81 +202,80 @@
   }
 }
 
-nlohmann::json ToJson(const UnqualifiedIdentifier& unqualified_identifier) {
-  nlohmann::json result;
+llvm::json::Value toJSON(const UnqualifiedIdentifier& unqualified_identifier) {
   if (auto* id = std::get_if<Identifier>(&unqualified_identifier)) {
-    result["Identifier"] = id->ToJson();
+    return llvm::json::Object{
+        {"Identifier", *id},
+    };
   } else if (auto* op = std::get_if<Operator>(&unqualified_identifier)) {
-    result["Operator"] = op->ToJson();
+    return llvm::json::Object{
+        {"Operator", *op},
+    };
   } else {
     SpecialName special_name = std::get<SpecialName>(unqualified_identifier);
-    result[SpecialNameToString(special_name)] = nullptr;
+    return llvm::json::Object{
+        {SpecialNameToString(special_name), nullptr},
+    };
   }
-  return result;
 }
 
-nlohmann::json FuncParam::ToJson() const {
-  nlohmann::json result;
-  result["type"] = type.ToJson();
-  result["identifier"] = identifier.ToJson();
-  return result;
+llvm::json::Value FuncParam::ToJson() const {
+  return llvm::json::Object{
+      {"type", type},
+      {"identifier", identifier},
+  };
 }
 
 std::ostream& operator<<(std::ostream& o, const SpecialName& special_name) {
   return o << SpecialNameToString(special_name);
 }
 
-nlohmann::json MemberFuncMetadata::ToJson() const {
-  nlohmann::json meta;
-
-  meta["record_id"] = record_id.value();
-
-  if (instance_method_metadata.has_value()) {
-    nlohmann::json instance;
-
-    absl::string_view reference;
-    switch (instance_method_metadata->reference) {
-      case MemberFuncMetadata::kLValue:
-        reference = "LValue";
-        break;
-      case MemberFuncMetadata::kRValue:
-        reference = "RValue";
-        break;
-      case MemberFuncMetadata::kUnqualified:
-        reference = "Unqualified";
-        break;
-    }
-    instance["reference"] = reference;
-    instance["is_const"] = instance_method_metadata->is_const;
-    instance["is_virtual"] = instance_method_metadata->is_virtual;
-    instance["is_explicit_ctor"] = instance_method_metadata->is_explicit_ctor;
-
-    meta["instance_method_metadata"] = std::move(instance);
+llvm::json::Value MemberFuncMetadata::InstanceMethodMetadata::ToJson() const {
+  const char* reference_str = nullptr;
+  switch (reference) {
+    case MemberFuncMetadata::kLValue:
+      reference_str = "LValue";
+      break;
+    case MemberFuncMetadata::kRValue:
+      reference_str = "RValue";
+      break;
+    case MemberFuncMetadata::kUnqualified:
+      reference_str = "Unqualified";
+      break;
   }
 
-  return meta;
+  return llvm::json::Object{
+      {"reference", reference_str},
+      {"is_const", is_const},
+      {"is_virtual", is_virtual},
+      {"is_explicit_ctor", is_explicit_ctor},
+  };
 }
 
-nlohmann::json Func::ToJson() const {
-  nlohmann::json func;
-  func["name"] = rs_bindings_from_cc::ToJson(name);
-  func["owning_target"] = owning_target.value();
-  if (doc_comment) {
-    func["doc_comment"] = *doc_comment;
-  }
-  func["mangled_name"] = mangled_name;
-  func["return_type"] = return_type.ToJson();
-  func["params"] = VectorToJson(params);
-  func["lifetime_params"] = VectorToJson(lifetime_params);
-  func["is_inline"] = is_inline;
-  if (member_func_metadata.has_value()) {
-    func["member_func_metadata"] = member_func_metadata->ToJson();
-  }
-  func["source_loc"] = source_loc.ToJson();
+llvm::json::Value MemberFuncMetadata::ToJson() const {
+  return llvm::json::Object{
+      {"record_id", record_id},
+      {"instance_method_metadata", instance_method_metadata},
+  };
+}
 
-  nlohmann::json item;
-  item["Func"] = std::move(func);
-  return item;
+llvm::json::Value Func::ToJson() const {
+  llvm::json::Object func{
+      {"name", name},
+      {"owning_target", owning_target},
+      {"doc_comment", doc_comment},
+      {"mangled_name", mangled_name},
+      {"return_type", return_type},
+      {"params", params},
+      {"lifetime_params", lifetime_params},
+      {"is_inline", is_inline},
+      {"member_func_metadata", member_func_metadata},
+      {"source_loc", source_loc},
+  };
+
+  return llvm::json::Object{
+      {"Func", std::move(func)},
+  };
 }
 
 static std::string AccessToString(AccessSpecifier access) {
@@ -304,18 +293,15 @@
   return o << AccessToString(access);
 }
 
-nlohmann::json Field::ToJson() const {
-  nlohmann::json result;
-
-  result["identifier"] = identifier.ToJson();
-  if (doc_comment) {
-    result["doc_comment"] = *doc_comment;
-  }
-  result["type"] = type.ToJson();
-  result["access"] = AccessToString(access);
-  result["offset"] = offset;
-  result["is_no_unique_address"] = is_no_unique_address;
-  return result;
+llvm::json::Value Field::ToJson() const {
+  return llvm::json::Object{
+      {"identifier", identifier},
+      {"doc_comment", doc_comment},
+      {"type", type},
+      {"access", AccessToString(access)},
+      {"offset", offset},
+      {"is_no_unique_address", is_no_unique_address},
+  };
 }
 
 static std::string SpecialMemberDefinitionToString(
@@ -337,125 +323,119 @@
   return o << SpecialMemberDefinitionToString(definition);
 }
 
-nlohmann::json SpecialMemberFunc::ToJson() const {
-  nlohmann::json result;
-  result["definition"] = SpecialMemberDefinitionToString(definition);
-  result["access"] = AccessToString(access);
-  return result;
+llvm::json::Value SpecialMemberFunc::ToJson() const {
+  return llvm::json::Object{
+      {"definition", SpecialMemberDefinitionToString(definition)},
+      {"access", AccessToString(access)},
+  };
 }
 
-nlohmann::json BaseClass::ToJson() const {
-  nlohmann::json base;
-  base["base_record_id"] = base_record_id.value();
-  if (offset.has_value()) {
-    base["offset"] = *offset;
-  }
-  return base;
-}
-nlohmann::json Record::ToJson() const {
-  nlohmann::json record;
-  record["rs_name"] = rs_name;
-  record["cc_name"] = cc_name;
-  record["id"] = id.value();
-  record["owning_target"] = owning_target.value();
-  if (doc_comment) {
-    record["doc_comment"] = *doc_comment;
-  }
-  record["unambiguous_public_bases"] = VectorToJson(unambiguous_public_bases);
-  record["fields"] = VectorToJson(fields);
-  record["lifetime_params"] = VectorToJson(lifetime_params);
-  record["size"] = size;
-  record["alignment"] = alignment;
-  if (base_size) {
-    record["base_size"] = *base_size;
-  }
-  record["override_alignment"] = override_alignment;
-  record["copy_constructor"] = copy_constructor.ToJson();
-  record["move_constructor"] = move_constructor.ToJson();
-  record["destructor"] = destructor.ToJson();
-  record["is_trivial_abi"] = is_trivial_abi;
-  record["is_final"] = is_final;
-
-  nlohmann::json item;
-  item["Record"] = std::move(record);
-  return item;
+llvm::json::Value BaseClass::ToJson() const {
+  return llvm::json::Object{
+      {"base_record_id", base_record_id},
+      {"offset", offset},
+  };
 }
 
-nlohmann::json Enumerator::ToJson() const {
-  nlohmann::json result;
-  result["identifier"] = identifier.ToJson();
-  result["value"] = value.ToJson();
-  return result;
+llvm::json::Value Record::ToJson() const {
+  llvm::json::Object record{
+      {"rs_name", rs_name},
+      {"cc_name", cc_name},
+      {"id", id},
+      {"owning_target", owning_target},
+      {"doc_comment", doc_comment},
+      {"unambiguous_public_bases", unambiguous_public_bases},
+      {"fields", fields},
+      {"lifetime_params", lifetime_params},
+      {"size", size},
+      {"alignment", alignment},
+      {"base_size", base_size},
+      {"override_alignment", override_alignment},
+      {"copy_constructor", copy_constructor},
+      {"move_constructor", move_constructor},
+      {"destructor", destructor},
+      {"is_trivial_abi", is_trivial_abi},
+      {"is_final", is_final},
+  };
+
+  return llvm::json::Object{
+      {"Record", std::move(record)},
+  };
 }
 
-nlohmann::json Enum::ToJson() const {
-  nlohmann::json enum_ir;
-  enum_ir["identifier"] = identifier.ToJson();
-  enum_ir["id"] = id.value();
-  enum_ir["owning_target"] = owning_target.value();
-  enum_ir["underlying_type"] = underlying_type.ToJson();
-  enum_ir["enumerators"] = VectorToJson(enumerators);
-
-  nlohmann::json item;
-  item["Enum"] = std::move(enum_ir);
-  return item;
+llvm::json::Value Enumerator::ToJson() const {
+  return llvm::json::Object{
+      {"identifier", identifier},
+      {"value", value},
+  };
 }
 
-nlohmann::json TypeAlias::ToJson() const {
-  nlohmann::json type_alias;
-  type_alias["identifier"] = identifier.ToJson();
-  type_alias["id"] = id.value();
-  type_alias["owning_target"] = owning_target.value();
-  if (doc_comment) {
-    type_alias["doc_comment"] = *doc_comment;
-  }
-  type_alias["underlying_type"] = underlying_type.ToJson();
+llvm::json::Value Enum::ToJson() const {
+  llvm::json::Object enum_ir{
+      {"identifier", identifier},       {"id", id},
+      {"owning_target", owning_target}, {"underlying_type", underlying_type},
+      {"enumerators", enumerators},
+  };
 
-  nlohmann::json item;
-  item["TypeAlias"] = std::move(type_alias);
-  return item;
+  return llvm::json::Object{
+      {"Enum", std::move(enum_ir)},
+  };
 }
 
-nlohmann::json SourceLoc::ToJson() const {
-  nlohmann::json source_loc;
-  source_loc["filename"] = filename;
-  source_loc["line"] = line;
-  source_loc["column"] = column;
-  return source_loc;
+llvm::json::Value TypeAlias::ToJson() const {
+  llvm::json::Object type_alias{
+      {"identifier", identifier},           {"id", id},
+      {"owning_target", owning_target},     {"doc_comment", doc_comment},
+      {"underlying_type", underlying_type},
+  };
+
+  return llvm::json::Object{
+      {"TypeAlias", std::move(type_alias)},
+  };
 }
 
-nlohmann::json UnsupportedItem::ToJson() const {
-  nlohmann::json unsupported;
-  unsupported["name"] = name;
-  unsupported["message"] = message;
-  unsupported["source_loc"] = source_loc.ToJson();
-
-  nlohmann::json item;
-  item["UnsupportedItem"] = std::move(unsupported);
-  return item;
+llvm::json::Value SourceLoc::ToJson() const {
+  return llvm::json::Object{
+      {"filename", filename},
+      {"line", line},
+      {"column", column},
+  };
 }
 
-nlohmann::json Comment::ToJson() const {
-  nlohmann::json comment;
-  comment["text"] = text;
+llvm::json::Value UnsupportedItem::ToJson() const {
+  llvm::json::Object unsupported{
+      {"name", name},
+      {"message", message},
+      {"source_loc", source_loc},
+  };
 
-  nlohmann::json item;
-  item["Comment"] = std::move(comment);
-  return item;
+  return llvm::json::Object{
+      {"UnsupportedItem", std::move(unsupported)},
+  };
 }
 
-nlohmann::json IR::ToJson() const {
-  std::vector<nlohmann::json> json_items;
+llvm::json::Value Comment::ToJson() const {
+  llvm::json::Object comment{
+      {"text", text},
+  };
+
+  return llvm::json::Object{
+      {"Comment", std::move(comment)},
+  };
+}
+
+llvm::json::Value IR::ToJson() const {
+  std::vector<llvm::json::Value> json_items;
   json_items.reserve(items.size());
   for (const auto& item : items) {
     std::visit([&](auto&& item) { json_items.push_back(item.ToJson()); }, item);
   }
 
-  nlohmann::json result;
-  result["used_headers"] = VectorToJson(used_headers);
-  result["current_target"] = current_target.value();
-  result["items"] = std::move(json_items);
-  return result;
+  return llvm::json::Object{
+      {"used_headers", used_headers},
+      {"current_target", current_target},
+      {"items", std::move(json_items)},
+  };
 }
 
 }  // namespace rs_bindings_from_cc
diff --git a/rs_bindings_from_cc/ir.h b/rs_bindings_from_cc/ir.h
index 9f43088..159d9d2 100644
--- a/rs_bindings_from_cc/ir.h
+++ b/rs_bindings_from_cc/ir.h
@@ -25,8 +25,10 @@
 #include "base/logging.h"
 #include "third_party/absl/strings/string_view.h"
 #include "rs_bindings_from_cc/bazel_types.h"
-#include "third_party/json/src/json.hpp"
 #include "third_party/llvm/llvm-project/llvm/include/llvm/ADT/APSInt.h"
+#include "third_party/llvm/llvm-project/llvm/include/llvm/ADT/Optional.h"
+#include "third_party/llvm/llvm-project/llvm/include/llvm/Support/FormatVariadic.h"
+#include "third_party/llvm/llvm-project/llvm/include/llvm/Support/JSON.h"
 #include "util/intops/strong_int.h"
 
 namespace rs_bindings_from_cc {
@@ -50,7 +52,7 @@
 
   absl::string_view IncludePath() const { return name_; }
 
-  nlohmann::json ToJson() const;
+  llvm::json::Value ToJson() const;
 
   template <typename H>
   friend H AbslHashValue(H h, const HeaderName& header_name) {
@@ -68,7 +70,7 @@
 }
 
 inline std::ostream& operator<<(std::ostream& o, const HeaderName& h) {
-  return o << std::setw(internal::kJsonIndent) << h.ToJson();
+  return o << std::string(llvm::formatv("{0:2}", h.ToJson()));
 }
 
 // An int uniquely representing a Decl. Since our IR goes through the JSON
@@ -82,7 +84,7 @@
 
 // A lifetime.
 struct Lifetime {
-  nlohmann::json ToJson() const;
+  llvm::json::Value ToJson() const;
 
   // Lifetime name. Unlike syn::Lifetime, this does not include the apostrophe.
   //
@@ -95,13 +97,13 @@
 };
 
 inline std::ostream& operator<<(std::ostream& o, const Lifetime& l) {
-  return o << std::setw(internal::kJsonIndent) << l.ToJson();
+  return o << std::string(llvm::formatv("{0:2}", l.ToJson()));
 }
 
 // A C++ type involved in the bindings. It has the knowledge of how the type
 // is spelled in C++.
 struct CcType {
-  nlohmann::json ToJson() const;
+  llvm::json::Value ToJson() const;
 
   // The name of the type. Examples:
   // - "int32_t", "std::ptrdiff_t", "long long", "bool"
@@ -114,8 +116,9 @@
   std::string name;
 
   // Id of a decl that this type corresponds to. `nullopt` when `name` is
-  // non-empty.
-  std::optional<DeclId> decl_id = std::nullopt;
+  // non-empty. `llvm::Optional` is used because it integrates better with
+  // `llvm::json` library than `std::optional`.
+  llvm::Optional<DeclId> decl_id;
 
   // The C++ const-qualification for the type.
   //
@@ -135,7 +138,7 @@
 // A Rust type involved in the bindings. It has the knowledge of how the type
 // is spelled in Rust.
 struct RsType {
-  nlohmann::json ToJson() const;
+  llvm::json::Value ToJson() const;
 
   // The name of the type. Examples:
   // - "i32" or "bool"
@@ -152,8 +155,9 @@
   std::string name;
 
   // Id of a decl that this type corresponds to. `nullopt` when `name` is
-  // non-empty.
-  std::optional<DeclId> decl_id = std::nullopt;
+  // non-empty. `llvm::Optional` is used because it integrates better with
+  // `llvm::json` library than `std::optional`.
+  llvm::Optional<DeclId> decl_id;
 
   // Lifetime arguments for a generic type. Examples:
   //   *mut i32 has no lifetime arguments
@@ -173,7 +177,7 @@
 };
 
 inline std::ostream& operator<<(std::ostream& o, const RsType& type) {
-  return o << std::setw(internal::kJsonIndent) << type.ToJson();
+  return o << std::string(llvm::formatv("{0:2}", type.ToJson()));
 }
 
 // A type involved in the bindings. The rs_type and cc_type will be treated
@@ -215,14 +219,14 @@
 
   bool IsVoid() const { return rs_type.name == "()"; }
 
-  nlohmann::json ToJson() const;
+  llvm::json::Value ToJson() const;
 
   RsType rs_type;
   CcType cc_type;
 };
 
 inline std::ostream& operator<<(std::ostream& o, const MappedType& type) {
-  return o << std::setw(internal::kJsonIndent) << type.ToJson();
+  return o << std::string(llvm::formatv("{0:2}", type.ToJson()));
 }
 
 // An identifier involved in bindings.
@@ -248,7 +252,7 @@
 
   absl::string_view Ident() const { return identifier_; }
 
-  nlohmann::json ToJson() const;
+  llvm::json::Value ToJson() const;
 
  private:
   std::string identifier_;
@@ -272,7 +276,7 @@
   IntegerConstant(const IntegerConstant& other) = default;
   IntegerConstant& operator=(const IntegerConstant& other) = default;
 
-  nlohmann::json ToJson() const;
+  llvm::json::Value ToJson() const;
 
  private:
   // value < 0
@@ -290,7 +294,7 @@
 
   absl::string_view Name() const { return name_; }
 
-  nlohmann::json ToJson() const;
+  llvm::json::Value ToJson() const;
 
  private:
   std::string name_;
@@ -309,14 +313,14 @@
 //    FuncParam of a C++ function `void Foo(int32_t a);` will be
 //    `FuncParam{.type=Type{"i32", "int32_t"}, .identifier=Identifier("foo"))`.
 struct FuncParam {
-  nlohmann::json ToJson() const;
+  llvm::json::Value ToJson() const;
 
   MappedType type;
   Identifier identifier;
 };
 
 inline std::ostream& operator<<(std::ostream& o, const FuncParam& param) {
-  return o << std::setw(internal::kJsonIndent) << param.ToJson();
+  return o << std::string(llvm::formatv("{0:2}", param.ToJson()));
 }
 
 enum SpecialName {
@@ -333,7 +337,7 @@
 // them differently. After all, they are not invoked or defined like normal
 // functions.
 using UnqualifiedIdentifier = std::variant<Identifier, Operator, SpecialName>;
-nlohmann::json ToJson(const UnqualifiedIdentifier& unqualified_identifier);
+llvm::json::Value toJSON(const UnqualifiedIdentifier& unqualified_identifier);
 
 struct MemberFuncMetadata {
   enum ReferenceQualification : char {
@@ -347,6 +351,8 @@
   // constructors and 2) `is_const` and `is_virtual` never applies to
   // constructors.
   struct InstanceMethodMetadata {
+    llvm::json::Value ToJson() const;
+
     ReferenceQualification reference = kUnqualified;
     bool is_const = false;
     bool is_virtual = false;
@@ -355,7 +361,7 @@
     bool is_explicit_ctor = false;
   };
 
-  nlohmann::json ToJson() const;
+  llvm::json::Value ToJson() const;
 
   // The type that this is a member function for.
   DeclId record_id;
@@ -363,12 +369,15 @@
   // Qualifiers for the instance method.
   //
   // If null, this is a static method.
-  std::optional<InstanceMethodMetadata> instance_method_metadata;
+  //
+  // `llvm::Optional` is used because it integrates better with `llvm::json`
+  // library than `std::optional`.
+  llvm::Optional<InstanceMethodMetadata> instance_method_metadata;
 };
 
 // Source code location
 struct SourceLoc {
-  nlohmann::json ToJson() const;
+  llvm::json::Value ToJson() const;
 
   std::string filename;
   uint64 line;
@@ -376,28 +385,28 @@
 };
 
 inline std::ostream& operator<<(std::ostream& o, const SourceLoc& r) {
-  return o << std::setw(internal::kJsonIndent) << r.ToJson();
+  return o << std::string(llvm::formatv("{0:2}", r.ToJson()));
 }
 
 // A function involved in the bindings.
 struct Func {
-  nlohmann::json ToJson() const;
+  llvm::json::Value ToJson() const;
 
   UnqualifiedIdentifier name;
   BlazeLabel owning_target;
-  std::optional<std::string> doc_comment;
+  llvm::Optional<std::string> doc_comment;
   std::string mangled_name;
   MappedType return_type;
   std::vector<FuncParam> params;
   std::vector<Lifetime> lifetime_params;
   bool is_inline;
   // If null, this is not a member function.
-  std::optional<MemberFuncMetadata> member_func_metadata;
+  llvm::Optional<MemberFuncMetadata> member_func_metadata;
   SourceLoc source_loc;
 };
 
 inline std::ostream& operator<<(std::ostream& o, const Func& f) {
-  return o << std::setw(internal::kJsonIndent) << f.ToJson();
+  return o << std::string(llvm::formatv("{0:2}", f.ToJson()));
 }
 
 // Access specifier for a member or base class.
@@ -411,10 +420,10 @@
 
 // A field (non-static member variable) of a record.
 struct Field {
-  nlohmann::json ToJson() const;
+  llvm::json::Value ToJson() const;
 
   Identifier identifier;
-  std::optional<std::string> doc_comment;
+  llvm::Optional<std::string> doc_comment;
   MappedType type;
   AccessSpecifier access;
   // Field offset in bits.
@@ -424,7 +433,7 @@
 };
 
 inline std::ostream& operator<<(std::ostream& o, const Field& f) {
-  return o << std::setw(internal::kJsonIndent) << f.ToJson();
+  return o << std::string(llvm::formatv("{0:2}", f.ToJson()));
 }
 
 // Information about special member functions.
@@ -450,7 +459,7 @@
     kDeleted,
   };
 
-  nlohmann::json ToJson() const;
+  llvm::json::Value ToJson() const;
 
   Definition definition = Definition::kTrivial;
   AccessSpecifier access = AccessSpecifier::kPublic;
@@ -460,23 +469,26 @@
                          const SpecialMemberFunc::Definition& definition);
 
 inline std::ostream& operator<<(std::ostream& o, const SpecialMemberFunc& f) {
-  return o << std::setw(internal::kJsonIndent) << f.ToJson();
+  return o << std::string(llvm::formatv("{0:2}", f.ToJson()));
 }
 
 // A base class subobject of a struct or class.
 struct BaseClass {
-  nlohmann::json ToJson() const;
+  llvm::json::Value ToJson() const;
   DeclId base_record_id;
 
   // The offset the base class subobject is located at. This is always nonempty
   // for nonvirtual inheritance, and always empty if a virtual base class is
   // anywhere in the inheritance chain.
-  std::optional<int64_t> offset;
+  //
+  // `llvm::Optional` is used because it integrates better with `llvm::json`
+  // library than `std::optional`.
+  llvm::Optional<int64_t> offset;
 };
 
 // A record (struct, class, union).
 struct Record {
-  nlohmann::json ToJson() const;
+  llvm::json::Value ToJson() const;
 
   // `rs_name` and `cc_name` are typically equal, but they may be different for
   // template instantiations (when `cc_name` is similar to `MyStruct<int>` and
@@ -486,7 +498,7 @@
 
   DeclId id;
   BlazeLabel owning_target;
-  std::optional<std::string> doc_comment;
+  llvm::Optional<std::string> doc_comment;
   std::vector<BaseClass> unambiguous_public_bases;
   std::vector<Field> fields;
   std::vector<Lifetime> lifetime_params;
@@ -497,7 +509,10 @@
   // The size of the base class subobjects, or null if there are none.
   //
   // More information: docs/struct_layout
-  std::optional<size_t> base_size = std::nullopt;
+  //
+  // `llvm::Optional` is used because it integrates better with `llvm::json`
+  // library than `std::optional`.
+  llvm::Optional<size_t> base_size;
 
   // True if the alignment may differ from what the fields would imply.
   //
@@ -532,14 +547,14 @@
 };
 
 struct Enumerator {
-  nlohmann::json ToJson() const;
+  llvm::json::Value ToJson() const;
 
   Identifier identifier;
   IntegerConstant value;
 };
 
 struct Enum {
-  nlohmann::json ToJson() const;
+  llvm::json::Value ToJson() const;
 
   Identifier identifier;
   DeclId id;
@@ -549,27 +564,27 @@
 };
 
 inline std::ostream& operator<<(std::ostream& o, const Record& r) {
-  return o << std::setw(internal::kJsonIndent) << r.ToJson();
+  return o << std::string(llvm::formatv("{0:2}", r.ToJson()));
 }
 
 // A type alias (defined either using `typedef` or `using`).
 struct TypeAlias {
-  nlohmann::json ToJson() const;
+  llvm::json::Value ToJson() const;
 
   Identifier identifier;
   DeclId id;
   BlazeLabel owning_target;
-  std::optional<std::string> doc_comment;
+  llvm::Optional<std::string> doc_comment;
   MappedType underlying_type;
 };
 
 inline std::ostream& operator<<(std::ostream& o, const TypeAlias& t) {
-  return o << std::setw(internal::kJsonIndent) << t.ToJson();
+  return o << std::string(llvm::formatv("{0:2}", t.ToJson()));
 }
 
 // A placeholder for an item that we can't generate bindings for (yet)
 struct UnsupportedItem {
-  nlohmann::json ToJson() const;
+  llvm::json::Value ToJson() const;
 
   // TODO(forster): We could show the original declaration in the generated
   // message (potentially also for successfully imported items).
@@ -584,23 +599,23 @@
 };
 
 inline std::ostream& operator<<(std::ostream& o, const UnsupportedItem& r) {
-  return o << std::setw(internal::kJsonIndent) << r.ToJson();
+  return o << std::string(llvm::formatv("{0:2}", r.ToJson()));
 }
 
 struct Comment {
-  nlohmann::json ToJson() const;
+  llvm::json::Value ToJson() const;
 
   std::string text;
 };
 
 inline std::ostream& operator<<(std::ostream& o, const Comment& r) {
-  return o << std::setw(internal::kJsonIndent) << r.ToJson();
+  return o << std::string(llvm::formatv("{0:2}", r.ToJson()));
 }
 
 // A complete intermediate representation of bindings for publicly accessible
 // declarations of a single C++ library.
 struct IR {
-  nlohmann::json ToJson() const;
+  llvm::json::Value ToJson() const;
 
   template <typename T>
   std::vector<const T*> get_items_if() const {
@@ -624,7 +639,7 @@
 };
 
 inline std::ostream& operator<<(std::ostream& o, const IR& ir) {
-  return o << std::setw(internal::kJsonIndent) << ir.ToJson();
+  return o << std::string(llvm::formatv("{0:2}", ir.ToJson()));
 }
 }  // namespace rs_bindings_from_cc
 
diff --git a/rs_bindings_from_cc/json_from_cc.cc b/rs_bindings_from_cc/json_from_cc.cc
index 7909cd3..b1df4a4 100644
--- a/rs_bindings_from_cc/json_from_cc.cc
+++ b/rs_bindings_from_cc/json_from_cc.cc
@@ -10,7 +10,9 @@
 #include "rs_bindings_from_cc/ffi_types.h"
 #include "rs_bindings_from_cc/ir.h"
 #include "rs_bindings_from_cc/ir_from_cc.h"
-#include "third_party/json/src/json.hpp"
+#include "third_party/llvm/llvm-project/llvm/include/llvm/Support/ErrorHandling.h"
+#include "third_party/llvm/llvm-project/llvm/include/llvm/Support/FormatVariadic.h"
+#include "third_party/llvm/llvm-project/llvm/include/llvm/Support/JSON.h"
 
 namespace rs_bindings_from_cc {
 
@@ -37,7 +39,7 @@
   // messages. If we start using this for production, then we should bridge the
   // error code into Rust.
   CHECK(ir.ok()) << "- IrFromCc reported an error: " << ir.status().message();
-  std::string json = ir->ToJson().dump();
+  std::string json = llvm::formatv("{0}", ir->ToJson());
   return AllocFfiU8SliceBox(MakeFfiU8Slice(json));
 }
 
diff --git a/rs_bindings_from_cc/rs_bindings_from_cc.cc b/rs_bindings_from_cc/rs_bindings_from_cc.cc
index 05b2952..1060f6c 100644
--- a/rs_bindings_from_cc/rs_bindings_from_cc.cc
+++ b/rs_bindings_from_cc/rs_bindings_from_cc.cc
@@ -21,7 +21,8 @@
 #include "rs_bindings_from_cc/ir.h"
 #include "rs_bindings_from_cc/ir_from_cc.h"
 #include "rs_bindings_from_cc/src_code_gen.h"
-#include "third_party/json/src/json.hpp"
+#include "third_party/llvm/llvm-project/llvm/include/llvm/Support/FormatVariadic.h"
+#include "third_party/llvm/llvm-project/llvm/include/llvm/Support/JSON.h"
 #include "third_party/llvm/llvm-project/llvm/include/llvm/Support/raw_ostream.h"
 #include "util/task/status_macros.h"
 
@@ -66,8 +67,8 @@
           std::vector<absl::string_view>(argv, argv + argc)));
 
   if (!cmdline.ir_out().empty()) {
-    RETURN_IF_ERROR(
-        SetFileContents(cmdline.ir_out(), ir.ToJson().dump(/*indent=*/2)));
+    RETURN_IF_ERROR(SetFileContents(
+        cmdline.ir_out(), std::string(llvm::formatv("{0:2}", ir.ToJson()))));
   }
 
   rs_bindings_from_cc::Bindings bindings =
diff --git a/rs_bindings_from_cc/src_code_gen.cc b/rs_bindings_from_cc/src_code_gen.cc
index 35cc9ef..893aff3 100644
--- a/rs_bindings_from_cc/src_code_gen.cc
+++ b/rs_bindings_from_cc/src_code_gen.cc
@@ -8,8 +8,9 @@
 
 #include "rs_bindings_from_cc/ffi_types.h"
 #include "rs_bindings_from_cc/ir.h"
-#include "third_party/json/src/json.hpp"
 #include "third_party/llvm/llvm-project/clang/include/clang/Format/Format.h"
+#include "third_party/llvm/llvm-project/llvm/include/llvm/Support/FormatVariadic.h"
+#include "third_party/llvm/llvm-project/llvm/include/llvm/Support/JSON.h"
 
 namespace rs_bindings_from_cc {
 
@@ -48,7 +49,7 @@
 }
 
 Bindings GenerateBindings(const IR& ir) {
-  std::string json = ir.ToJson().dump();
+  std::string json = llvm::formatv("{0}", ir.ToJson());
   FfiBindings ffi_bindings = GenerateBindingsImpl(MakeFfiU8Slice(json));
   Bindings bindings = MakeBindingsFromFfiBindings(ffi_bindings);
   FreeFfiBindings(ffi_bindings);