https://github.com/evelez7 updated 
https://github.com/llvm/llvm-project/pull/142483

>From fa8b80f9bfe2b7faf765ed4cf60fb8cec30e1d48 Mon Sep 17 00:00:00 2001
From: Erick Velez <erickvel...@gmail.com>
Date: Mon, 2 Jun 2025 12:53:36 -0700
Subject: [PATCH 1/4] [clang-doc] add a JSON generator

---
 clang-tools-extra/clang-doc/CMakeLists.txt    |   1 +
 clang-tools-extra/clang-doc/Generators.cpp    |   2 +
 clang-tools-extra/clang-doc/Generators.h      |   1 +
 clang-tools-extra/clang-doc/JSONGenerator.cpp | 316 ++++++++++++++++++
 .../clang-doc/tool/ClangDocMain.cpp           |   8 +-
 .../test/clang-doc/json/class.cpp             | 183 ++++++++++
 6 files changed, 509 insertions(+), 2 deletions(-)
 create mode 100644 clang-tools-extra/clang-doc/JSONGenerator.cpp
 create mode 100644 clang-tools-extra/test/clang-doc/json/class.cpp

diff --git a/clang-tools-extra/clang-doc/CMakeLists.txt 
b/clang-tools-extra/clang-doc/CMakeLists.txt
index 79563c41435eb..5989e5fe60cf3 100644
--- a/clang-tools-extra/clang-doc/CMakeLists.txt
+++ b/clang-tools-extra/clang-doc/CMakeLists.txt
@@ -17,6 +17,7 @@ add_clang_library(clangDoc STATIC
   Serialize.cpp
   YAMLGenerator.cpp
   HTMLMustacheGenerator.cpp
+  JSONGenerator.cpp
 
   DEPENDS
   omp_gen
diff --git a/clang-tools-extra/clang-doc/Generators.cpp 
b/clang-tools-extra/clang-doc/Generators.cpp
index a3c2773412cff..3fb5b63c403a7 100644
--- a/clang-tools-extra/clang-doc/Generators.cpp
+++ b/clang-tools-extra/clang-doc/Generators.cpp
@@ -105,5 +105,7 @@ static int LLVM_ATTRIBUTE_UNUSED HTMLGeneratorAnchorDest =
     HTMLGeneratorAnchorSource;
 static int LLVM_ATTRIBUTE_UNUSED MHTMLGeneratorAnchorDest =
     MHTMLGeneratorAnchorSource;
+static int LLVM_ATTRIBUTE_UNUSED JSONGeneratorAnchorDest =
+    JSONGeneratorAnchorSource;
 } // namespace doc
 } // namespace clang
diff --git a/clang-tools-extra/clang-doc/Generators.h 
b/clang-tools-extra/clang-doc/Generators.h
index aee04b9d58d9d..92d3006e6002d 100644
--- a/clang-tools-extra/clang-doc/Generators.h
+++ b/clang-tools-extra/clang-doc/Generators.h
@@ -58,6 +58,7 @@ extern volatile int YAMLGeneratorAnchorSource;
 extern volatile int MDGeneratorAnchorSource;
 extern volatile int HTMLGeneratorAnchorSource;
 extern volatile int MHTMLGeneratorAnchorSource;
+extern volatile int JSONGeneratorAnchorSource;
 
 } // namespace doc
 } // namespace clang
diff --git a/clang-tools-extra/clang-doc/JSONGenerator.cpp 
b/clang-tools-extra/clang-doc/JSONGenerator.cpp
new file mode 100644
index 0000000000000..499ca4dd05e6e
--- /dev/null
+++ b/clang-tools-extra/clang-doc/JSONGenerator.cpp
@@ -0,0 +1,316 @@
+#include "Generators.h"
+#include "llvm/Support/JSON.h"
+
+using namespace llvm;
+using namespace llvm::json;
+
+static llvm::ExitOnError ExitOnErr;
+
+namespace clang {
+namespace doc {
+
+class JSONGenerator : public Generator {
+public:
+  static const char *Format;
+
+  Error generateDocs(StringRef RootDir,
+                     llvm::StringMap<std::unique_ptr<doc::Info>> Infos,
+                     const ClangDocContext &CDCtx) override;
+  Error createResources(ClangDocContext &CDCtx) override;
+  Error generateDocForInfo(Info *I, llvm::raw_ostream &OS,
+                           const ClangDocContext &CDCtx) override;
+};
+
+const char *JSONGenerator::Format = "json";
+
+static json::Object serializeLocation(const Location &Loc,
+                                      std::optional<StringRef> RepositoryUrl) {
+  Object LocationObj = Object();
+  LocationObj["LineNumber"] = Loc.StartLineNumber;
+  LocationObj["Filename"] = Loc.Filename;
+
+  if (!Loc.IsFileInRootDir || !RepositoryUrl)
+    return LocationObj;
+  SmallString<128> FileURL(*RepositoryUrl);
+  sys::path::append(FileURL, sys::path::Style::posix, Loc.Filename);
+  FileURL += "#" + std::to_string(Loc.StartLineNumber);
+  LocationObj["FileURL"] = FileURL;
+  return LocationObj;
+}
+
+static json::Value serializeComment(const CommentInfo &Comment) {
+  assert((Comment.Kind == "BlockCommandComment" ||
+          Comment.Kind == "FullComment" || Comment.Kind == "ParagraphComment" 
||
+          Comment.Kind == "TextComment") &&
+         "Unknown Comment type in CommentInfo.");
+
+  Object Obj = Object();
+  json::Value Child = Object();
+
+  // TextComment has no children, so return it.
+  if (Comment.Kind == "TextComment") {
+    Obj["TextComment"] = Comment.Text;
+    return Obj;
+  }
+
+  // BlockCommandComment needs to generate a Command key.
+  if (Comment.Kind == "BlockCommandComment")
+    Child.getAsObject()->insert({"Command", Comment.Name});
+
+  // Use the same handling for everything else.
+  // Only valid for:
+  //  - BlockCommandComment
+  //  - FullComment
+  //  - ParagraphComment
+  json::Value ChildArr = Array();
+  auto &CARef = *ChildArr.getAsArray();
+  CARef.reserve(Comment.Children.size());
+  for (const auto &C : Comment.Children)
+    CARef.emplace_back(serializeComment(*C));
+  Child.getAsObject()->insert({"Children", ChildArr});
+  Obj.insert({Comment.Kind, Child});
+  return Obj;
+}
+
+static void serializeCommonAttributes(const Info &I, json::Object &Obj,
+                                      std::optional<StringRef> RepositoryUrl) {
+  Obj["Name"] = I.Name.str();
+  Obj["USR"] = toHex(toStringRef(I.USR));
+
+  if (!I.Path.empty())
+    Obj["Path"] = I.Path.str();
+
+  if (!I.Namespace.empty()) {
+    Obj["Namespace"] = json::Array();
+    for (const auto &NS : I.Namespace)
+      Obj["Namespace"].getAsArray()->push_back(NS.Name.str());
+  }
+
+  if (!I.Description.empty()) {
+    json::Value DescArray = json::Array();
+    auto &DescArrayRef = *DescArray.getAsArray();
+    for (const auto &Comment : I.Description)
+      DescArrayRef.push_back(serializeComment(Comment));
+    Obj["Description"] = std::move(DescArray);
+  }
+
+  // Namespaces aren't SymbolInfos, so they dont have a DefLoc
+  if (I.IT != InfoType::IT_namespace) {
+    const auto *Symbol = static_cast<const SymbolInfo *>(&I);
+    if (Symbol->DefLoc)
+      Obj["Location"] =
+          serializeLocation(Symbol->DefLoc.value(), RepositoryUrl);
+  }
+}
+
+static void serializeTypeInfo(const TypeInfo &I, Object &Obj) {
+  Obj["Name"] = I.Type.Name;
+  Obj["QualName"] = I.Type.QualName;
+  Obj["ID"] = toHex(toStringRef(I.Type.USR));
+  Obj["IsTemplate"] = I.IsTemplate;
+  Obj["IsBuiltIn"] = I.IsBuiltIn;
+}
+
+static void serializeInfo(const FunctionInfo &F, json::Object &Obj,
+                          std::optional<StringRef> RepositoryURL) {
+  serializeCommonAttributes(F, Obj, RepositoryURL);
+  Obj["IsStatic"] = F.IsStatic;
+
+  auto ReturnTypeObj = Object();
+  serializeTypeInfo(F.ReturnType, ReturnTypeObj);
+  Obj["ReturnType"] = std::move(ReturnTypeObj);
+
+  if (!F.Params.empty()) {
+    json::Value ParamsArray = json::Array();
+    auto &ParamsArrayRef = *ParamsArray.getAsArray();
+    for (const auto &Param : F.Params) {
+      json::Object ParamObj;
+      ParamObj["Name"] = Param.Name;
+      ParamObj["Type"] = Param.Type.Name;
+      ParamsArrayRef.push_back(std::move(ParamObj));
+    }
+    Obj["Params"] = std::move(ParamsArray);
+  }
+}
+
+static void serializeInfo(const EnumInfo &I, json::Object &Obj,
+                          std::optional<StringRef> RepositoryUrl) {
+  serializeCommonAttributes(I, Obj, RepositoryUrl);
+  Obj["Scoped"] = I.Scoped;
+
+  if (I.BaseType) {
+    json::Object BaseTypeObj;
+    BaseTypeObj["Name"] = I.BaseType->Type.Name;
+    BaseTypeObj["QualName"] = I.BaseType->Type.QualName;
+    BaseTypeObj["ID"] = toHex(toStringRef(I.BaseType->Type.USR));
+    Obj["BaseType"] = std::move(BaseTypeObj);
+  }
+
+  if (!I.Members.empty()) {
+    json::Value MembersArray = Array();
+    auto &MembersArrayRef = *MembersArray.getAsArray();
+    for (const auto &Member : I.Members) {
+      json::Object MemberObj;
+      MemberObj["Name"] = Member.Name;
+      if (!Member.ValueExpr.empty())
+        MemberObj["ValueExpr"] = Member.ValueExpr;
+      else
+        MemberObj["Value"] = Member.Value;
+      MembersArrayRef.push_back(std::move(MemberObj));
+    }
+    Obj["Members"] = std::move(MembersArray);
+  }
+}
+
+static void serializeInfo(const TypedefInfo &I, json::Object &Obj,
+                          std::optional<StringRef> RepositoryUrl) {
+  serializeCommonAttributes(I, Obj, RepositoryUrl);
+  Obj["TypeDeclaration"] = I.TypeDeclaration;
+  Obj["IsUsing"] = I.IsUsing;
+  Object TypeObj = Object();
+  serializeTypeInfo(I.Underlying, TypeObj);
+  Obj["Underlying"] = std::move(TypeObj);
+}
+
+static void serializeInfo(const RecordInfo &I, json::Object &Obj,
+                          std::optional<StringRef> RepositoryUrl) {
+  serializeCommonAttributes(I, Obj, RepositoryUrl);
+  Obj["FullName"] = I.Name.str();
+  Obj["TagType"] = getTagType(I.TagType);
+  Obj["IsTypedef"] = I.IsTypeDef;
+
+  if (!I.Children.Functions.empty()) {
+    json::Value PublicFunctionArr = Array();
+    json::Array &PublicFunctionARef = *PublicFunctionArr.getAsArray();
+    json::Value ProtectedFunctionArr = Array();
+    json::Array &ProtectedFunctionARef = *ProtectedFunctionArr.getAsArray();
+
+    for (const auto &Function : I.Children.Functions) {
+      json::Object FunctionObj;
+      serializeInfo(Function, FunctionObj, RepositoryUrl);
+      AccessSpecifier Access = Function.Access;
+      if (Access == AccessSpecifier::AS_public)
+        PublicFunctionARef.push_back(std::move(FunctionObj));
+      else if (Access == AccessSpecifier::AS_protected)
+        ProtectedFunctionARef.push_back(std::move(FunctionObj));
+    }
+
+    if (!PublicFunctionARef.empty())
+      Obj["PublicFunctions"] = std::move(PublicFunctionArr);
+    if (!ProtectedFunctionARef.empty())
+      Obj["ProtectedFunctions"] = std::move(ProtectedFunctionArr);
+  }
+
+  if (!I.Members.empty()) {
+    json::Value PublicMembers = Array();
+    json::Array &PubMemberRef = *PublicMembers.getAsArray();
+    json::Value ProtectedMembers = Array();
+    json::Array &ProtMemberRef = *ProtectedMembers.getAsArray();
+
+    for (const MemberTypeInfo &Member : I.Members) {
+      json::Object MemberObj = Object();
+      MemberObj["Name"] = Member.Name;
+      MemberObj["Type"] = Member.Type.Name;
+
+      if (Member.Access == AccessSpecifier::AS_public)
+        PubMemberRef.push_back(std::move(MemberObj));
+      else if (Member.Access == AccessSpecifier::AS_protected)
+        ProtMemberRef.push_back(std::move(MemberObj));
+    }
+
+    if (!PubMemberRef.empty())
+      Obj["PublicMembers"] = std::move(PublicMembers);
+    if (!ProtMemberRef.empty())
+      Obj["ProtectedMembers"] = std::move(ProtectedMembers);
+  }
+
+  if (!I.Children.Enums.empty()) {
+    json::Value EnumsArray = Array();
+    auto &EnumsArrayRef = *EnumsArray.getAsArray();
+    for (const auto &Enum : I.Children.Enums) {
+      json::Object EnumObj;
+      serializeInfo(Enum, EnumObj, RepositoryUrl);
+      EnumsArrayRef.push_back(std::move(EnumObj));
+    }
+    Obj["Enums"] = std::move(EnumsArray);
+  }
+
+  if (!I.Children.Typedefs.empty()) {
+    json::Value TypedefsArray = Array();
+    auto &TypedefsArrayRef = *TypedefsArray.getAsArray();
+    for (const auto &Typedef : I.Children.Typedefs) {
+      json::Object TypedefObj;
+      serializeInfo(Typedef, TypedefObj, RepositoryUrl);
+      TypedefsArrayRef.push_back(std::move(TypedefObj));
+    }
+    Obj["Typedefs"] = std::move(TypedefsArray);
+  }
+}
+
+Error JSONGenerator::generateDocs(
+    StringRef RootDir, llvm::StringMap<std::unique_ptr<doc::Info>> Infos,
+    const ClangDocContext &CDCtx) {
+  StringSet<> CreatedDirs;
+  StringMap<std::vector<doc::Info *>> FileToInfos;
+  for (const auto &Group : Infos) {
+    Info *Info = Group.getValue().get();
+
+    SmallString<128> Path;
+    sys::path::native(RootDir, Path);
+    sys::path::append(Path, Info->getRelativeFilePath(""));
+    if (!CreatedDirs.contains(Path)) {
+      if (std::error_code Err = sys::fs::create_directories(Path);
+          Err != std::error_code())
+        ExitOnErr(createFileError(Twine(Path), Err));
+      CreatedDirs.insert(Path);
+    }
+
+    sys::path::append(Path, Info->getFileBaseName() + ".json");
+    FileToInfos[Path].push_back(Info);
+  }
+
+  for (const auto &Group : FileToInfos) {
+    std::error_code FileErr;
+    raw_fd_ostream InfoOS(Group.getKey(), FileErr, sys::fs::OF_Text);
+    if (FileErr)
+      ExitOnErr(createFileError("cannot open file " + Group.getKey(), 
FileErr));
+
+    for (const auto &Info : Group.getValue())
+      if (Error Err = generateDocForInfo(Info, InfoOS, CDCtx))
+        return Err;
+  }
+
+  return Error::success();
+}
+
+Error JSONGenerator::generateDocForInfo(Info *I, raw_ostream &OS,
+                                        const ClangDocContext &CDCtx) {
+  json::Object Obj = Object();
+
+  switch (I->IT) {
+  case InfoType::IT_namespace:
+    break;
+  case InfoType::IT_record:
+    serializeInfo(*static_cast<RecordInfo *>(I), Obj, CDCtx.RepositoryUrl);
+    break;
+  case InfoType::IT_enum:
+  case InfoType::IT_function:
+  case InfoType::IT_typedef:
+    break;
+  case InfoType::IT_default:
+    ExitOnErr(
+        createStringError(inconvertibleErrorCode(), "unexpected info type"));
+  }
+  OS << llvm::formatv("{0:2}", llvm::json::Value(std::move(Obj)));
+  return Error::success();
+}
+
+Error JSONGenerator::createResources(ClangDocContext &CDCtx) {
+  return Error::success();
+}
+
+static GeneratorRegistry::Add<JSONGenerator> JSON(JSONGenerator::Format,
+                                                  "Generator for JSON 
output.");
+volatile int JSONGeneratorAnchorSource = 0;
+} // namespace doc
+} // namespace clang
diff --git a/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp 
b/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp
index 8253ef298db4d..2d0cc4a32fc50 100644
--- a/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp
+++ b/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp
@@ -104,7 +104,7 @@ static llvm::cl::opt<std::string> RepositoryCodeLinePrefix(
     llvm::cl::desc("Prefix of line code for repository."),
     llvm::cl::cat(ClangDocCategory));
 
-enum OutputFormatTy { md, yaml, html, mustache };
+enum OutputFormatTy { md, yaml, html, mustache, json };
 
 static llvm::cl::opt<OutputFormatTy> FormatEnum(
     "format", llvm::cl::desc("Format for outputted docs."),
@@ -115,7 +115,9 @@ static llvm::cl::opt<OutputFormatTy> FormatEnum(
                      clEnumValN(OutputFormatTy::html, "html",
                                 "Documentation in HTML format."),
                      clEnumValN(OutputFormatTy::mustache, "mustache",
-                                "Documentation in mustache HTML format")),
+                                "Documentation in mustache HTML format"),
+                     clEnumValN(OutputFormatTy::json, "json",
+                                "Documentation in JSON format")),
     llvm::cl::init(OutputFormatTy::yaml), llvm::cl::cat(ClangDocCategory));
 
 static llvm::ExitOnError ExitOnErr;
@@ -130,6 +132,8 @@ static std::string getFormatString() {
     return "html";
   case OutputFormatTy::mustache:
     return "mustache";
+  case OutputFormatTy::json:
+    return "json";
   }
   llvm_unreachable("Unknown OutputFormatTy");
 }
diff --git a/clang-tools-extra/test/clang-doc/json/class.cpp 
b/clang-tools-extra/test/clang-doc/json/class.cpp
new file mode 100644
index 0000000000000..3126fc689ffae
--- /dev/null
+++ b/clang-tools-extra/test/clang-doc/json/class.cpp
@@ -0,0 +1,183 @@
+// RUN: rm -rf %t && mkdir -p %t
+// RUN: clang-doc --output=%t --format=json --executor=standalone %s
+// RUN: FileCheck %s < %t/GlobalNamespace/MyClass.json
+
+struct Foo;
+
+// This is a nice class.
+// It has some nice methods and fields.
+// @brief This is a brief description.
+struct MyClass {
+  int PublicField;
+
+  int myMethod(int MyParam);
+  static void staticMethod();
+  const int& getConst();
+  
+  enum Color {
+    RED,
+    GREEN,
+    BLUE = 5
+  };
+  
+  typedef int MyTypedef;
+protected:
+  int protectedMethod();
+
+  int ProtectedField;
+};
+
+// CHECK:       {
+// CHECK-NEXT:    "Description": [
+// CHECK-NEXT:      {
+// CHECK-NEXT:       "FullComment": {
+// CHECK-NEXT:         "Children": [
+// CHECK-NEXT:           {
+// CHECK-NEXT:             "ParagraphComment": {
+// CHECK-NEXT:               "Children": [
+// CHECK-NEXT:                 {
+// CHECK-NEXT:                   "TextComment": " This is a nice class."
+// CHECK-NEXT:                 },
+// CHECK-NEXT:                 {
+// CHECK-NEXT:                   "TextComment": " It has some nice methods and 
fields."
+// CHECK-NEXT:                 },
+// CHECK-NEXT:                 {
+// CHECK-NEXT:                   "TextComment": ""
+// CHECK-NEXT:                 }
+// CHECK-NEXT:               ]
+// CHECK:               {
+// CHECK-NEXT:             "BlockCommandComment": {
+// CHECK-NEXT:               "Children": [
+// CHECK-NEXT:                 {
+// CHECK-NEXT:                   "ParagraphComment": {
+// CHECK-NEXT:                     "Children": [
+// CHECK-NEXT:                       { 
+// CHECK-NEXT:                         "TextComment": " This is a brief 
description." 
+// CHECK-NEXT:                       }
+// CHECK:                   "Command": "brief"
+// CHECK:         "Enums": [
+// CHECK-NEXT:      {
+// CHECK-NEXT:        "Location": {
+// CHECK-NEXT:          "Filename": "{{.*}}class.cpp",
+// CHECK-NEXT:          "LineNumber": 17
+// CHECK-NEXT:        },
+// CHECK-NEXT:        "Members": [
+// CHECK-NEXT:          {
+// CHECK-NEXT:            "Name": "RED",
+// CHECK-NEXT:            "Value": "0"
+// CHECK-NEXT:          },
+// CHECK-NEXT:          {
+// CHECK-NEXT:            "Name": "GREEN",
+// CHECK-NEXT:            "Value": "1"
+// CHECK-NEXT:          },
+// CHECK-NEXT:          {
+// CHECK-NEXT:            "Name": "BLUE",
+// CHECK-NEXT:            "ValueExpr": "5"
+// CHECK-NEXT:          }
+// CHECK-NEXT:        ],
+// CHECK-NEXT:        "Name": "Color",
+// CHECK-NEXT:        "Namespace": [
+// CHECK-NEXT:          "MyClass",
+// CHECK-NEXT:          "GlobalNamespace"
+// CHECK-NEXT:        ],
+// CHECK-NEXT:        "Scoped": false,
+// CHECK-NEXT:        "USR": "{{[0-9A-F]*}}"
+// CHECK-NEXT:      }
+// CHECK-NEXT:    ],
+// CHECK-NEXT:    "FullName": "MyClass",
+// CHECK-NEXT:    "IsTypedef": false,
+// CHECK-NEXT:    "Location": {
+// CHECK-NEXT:      "Filename": "{{.*}}class.cpp",
+// CHECK-NEXT:      "LineNumber": 10
+// CHECK-NEXT:    },
+// CHECK-NEXT:    "Name": "MyClass",
+// CHECK-NEXT:    "Namespace": [
+// CHECK-NEXT:      "GlobalNamespace"
+// CHECK-NEXT:    ],
+// CHECK-NEXT:   "Path": "GlobalNamespace",
+// CHECK-NEXT:   "ProtectedFunctions": [
+// CHECK-NEXT:     {
+// CHECK-NEXT:       "IsStatic": false,
+// CHECK-NEXT:       "Name": "protectedMethod",
+// CHECK-NEXT:       "Namespace": [
+// CHECK-NEXT:         "MyClass",
+// CHECK-NEXT:         "GlobalNamespace"
+// CHECK-NEXT:       ],
+// CHECK-NEXT:       "ReturnType": {
+// CHECK-NEXT:         "ID": "{{[0-9A-F]*}}",
+// CHECK-NEXT:         "IsBuiltIn": false,
+// CHECK-NEXT:         "IsTemplate": false,
+// CHECK-NEXT:         "Name": "int",
+// CHECK-NEXT:         "QualName": "int"
+// CHECK-NEXT:       },
+// CHECK-NEXT:       "USR": "{{[0-9A-F]*}}"
+// CHECK-NEXT:      }
+// CHECK-NEXT:    ],
+// CHECK-NEXT:    "ProtectedMembers": [
+// CHECK-NEXT:      {
+// CHECK-NEXT:        "Name": "ProtectedField",
+// CHECK-NEXT:        "Type": "int"
+// CHECK-NEXT:      }
+// CHECK-NEXT:    ],
+// CHECK-NEXT:    "PublicFunctions": [
+// CHECK-NEXT:      {
+// CHECK-NEXT:        "IsStatic": false,
+// CHECK-NEXT:        "Name": "myMethod",
+// CHECK-NEXT:        "Namespace": [
+// CHECK-NEXT:          "MyClass",
+// CHECK-NEXT:          "GlobalNamespace"
+// CHECK-NEXT:        ],
+// CHECK-NEXT:        "Params": [
+// CHECK-NEXT:          {
+// CHECK-NEXT:            "Name": "MyParam",
+// CHECK-NEXT:            "Type": "int"
+// CHECK-NEXT:          }
+// CHECK-NEXT:        ],
+// CHECK-NEXT:        "ReturnType": {
+// CHECK-NEXT:          "ID": "{{[0-9A-F]*}}",
+// CHECK-NEXT:          "IsBuiltIn": false,
+// CHECK-NEXT:          "IsTemplate": false,
+// CHECK-NEXT:          "Name": "int",
+// CHECK-NEXT:          "QualName": "int"
+// CHECK-NEXT:        },
+// CHECK-NEXT:        "USR": "{{[0-9A-F]*}}"
+// CHECK-NEXT:      },
+// CHECK:             "IsStatic": true,
+// CHECk:             "IsStatic": false,
+// CHECK:             "Name": "getConst",
+// CHECK:             "ReturnType": {
+// CHECK-NEXT:          "ID": "{{[0-9A-F]*}}",
+// CHECK-NEXT:          "IsBuiltIn": false,
+// CHECK-NEXT:          "IsTemplate": false,
+// CHECK-NEXT:          "Name": "const int &",
+// CHECK-NEXT:          "QualName": "const int &"
+// CHECK-NEXT:        },
+// CHECK:         "PublicMembers": [
+// CHECK-NEXT:      {
+// CHECK-NEXT:        "Name": "PublicField",
+// CHECK-NEXT:        "Type": "int"
+// CHECK-NEXT:      }
+// CHECK-NEXT:    ],
+// CHECK-NEXT:    "TagType": "struct",
+// CHECK-NEXT:    "Typedefs": [
+// CHECK-NEXT:      {
+// CHECK-NEXT:        "IsUsing": false,
+// CHECK-NEXT:        "Location": {
+// CHECK-NEXT:          "Filename": "{{.*}}class.cpp",
+// CHECK-NEXT:          "LineNumber": 23
+// CHECK-NEXT:        },
+// CHECK-NEXT:        "Name": "MyTypedef",
+// CHECK-NEXT:        "Namespace": [
+// CHECK-NEXT:          "MyClass",
+// CHECK-NEXT:          "GlobalNamespace"
+// CHECK-NEXT:        ],
+// CHECK-NEXT:        "TypeDeclaration": "",
+// CHECK-NEXT:        "USR": "{{[0-9A-F]*}}",
+// CHECK-NEXT:        "Underlying": {
+// CHECK-NEXT:          "ID": "0000000000000000000000000000000000000000",
+// CHECK-NEXT:          "IsBuiltIn": false,
+// CHECK-NEXT:          "IsTemplate": false,
+// CHECK-NEXT:          "Name": "int",
+// CHECK-NEXT:          "QualName": "int"
+// CHECK:         "USR": "{{[0-9A-F]*}}"
+// CHECK-NEXT:  }

>From f427d3aa9d43b37e6318677f96262aab73545d57 Mon Sep 17 00:00:00 2001
From: Erick Velez <erickvel...@gmail.com>
Date: Tue, 3 Jun 2025 12:35:26 -0700
Subject: [PATCH 2/4] move common children serialization to a new function

---
 clang-tools-extra/clang-doc/JSONGenerator.cpp | 84 +++++++++++++------
 .../test/clang-doc/json/class.cpp             | 10 +++
 2 files changed, 70 insertions(+), 24 deletions(-)

diff --git a/clang-tools-extra/clang-doc/JSONGenerator.cpp 
b/clang-tools-extra/clang-doc/JSONGenerator.cpp
index 499ca4dd05e6e..2c4cc18643e1c 100644
--- a/clang-tools-extra/clang-doc/JSONGenerator.cpp
+++ b/clang-tools-extra/clang-doc/JSONGenerator.cpp
@@ -23,6 +23,11 @@ class JSONGenerator : public Generator {
 
 const char *JSONGenerator::Format = "json";
 
+static void serializeInfo(const TypedefInfo &I, json::Object &Obj,
+                          std::optional<StringRef> RepositoryUrl);
+static void serializeInfo(const EnumInfo &I, json::Object &Obj,
+                          std::optional<StringRef> RepositoryUrl);
+
 static json::Object serializeLocation(const Location &Loc,
                                       std::optional<StringRef> RepositoryUrl) {
   Object LocationObj = Object();
@@ -103,7 +108,58 @@ static void serializeCommonAttributes(const Info &I, 
json::Object &Obj,
   }
 }
 
-static void serializeTypeInfo(const TypeInfo &I, Object &Obj) {
+static void serializeReference(const Reference &Ref, Object &ReferenceObj,
+                               SmallString<64> CurrentDirectory) {
+  SmallString<64> Path = Ref.getRelativeFilePath(CurrentDirectory);
+  sys::path::append(Path, Ref.getFileBaseName() + ".json");
+  sys::path::native(Path, sys::path::Style::posix);
+  ReferenceObj["Link"] = Path;
+  ReferenceObj["Name"] = Ref.Name;
+  ReferenceObj["QualName"] = Ref.QualName;
+  ReferenceObj["ID"] = toHex(toStringRef(Ref.USR));
+}
+
+// Although namespaces and records both have ScopeChildren, they serialize them
+// differently. Only enums, records, and typedefs are handled here.
+static void serializeCommonChildren(const ScopeChildren &Children,
+                                    json::Object &Obj,
+                                    std::optional<StringRef> RepositoryUrl) {
+  if (!Children.Enums.empty()) {
+    json::Value EnumsArray = Array();
+    auto &EnumsArrayRef = *EnumsArray.getAsArray();
+    for (const auto &Enum : Children.Enums) {
+      json::Object EnumObj;
+      serializeInfo(Enum, EnumObj, RepositoryUrl);
+      EnumsArrayRef.push_back(std::move(EnumObj));
+    }
+    Obj["Enums"] = std::move(EnumsArray);
+  }
+
+  if (!Children.Typedefs.empty()) {
+    json::Value TypedefsArray = Array();
+    auto &TypedefsArrayRef = *TypedefsArray.getAsArray();
+    for (const auto &Typedef : Children.Typedefs) {
+      json::Object TypedefObj;
+      serializeInfo(Typedef, TypedefObj, RepositoryUrl);
+      TypedefsArrayRef.push_back(std::move(TypedefObj));
+    }
+    Obj["Typedefs"] = std::move(TypedefsArray);
+  }
+
+  if (!Children.Records.empty()) {
+    json::Value RecordsArray = Array();
+    auto &RecordsArrayRef = *RecordsArray.getAsArray();
+    for (const auto &Record : Children.Records) {
+      json::Object RecordObj;
+      SmallString<64> BasePath = Record.getRelativeFilePath("");
+      serializeReference(Record, RecordObj, BasePath);
+      RecordsArrayRef.push_back(std::move(RecordObj));
+    }
+    Obj["Records"] = std::move(RecordsArray);
+  }
+}
+
+static void serializeInfo(const TypeInfo &I, Object &Obj) {
   Obj["Name"] = I.Type.Name;
   Obj["QualName"] = I.Type.QualName;
   Obj["ID"] = toHex(toStringRef(I.Type.USR));
@@ -117,7 +173,7 @@ static void serializeInfo(const FunctionInfo &F, 
json::Object &Obj,
   Obj["IsStatic"] = F.IsStatic;
 
   auto ReturnTypeObj = Object();
-  serializeTypeInfo(F.ReturnType, ReturnTypeObj);
+  serializeInfo(F.ReturnType, ReturnTypeObj);
   Obj["ReturnType"] = std::move(ReturnTypeObj);
 
   if (!F.Params.empty()) {
@@ -168,7 +224,7 @@ static void serializeInfo(const TypedefInfo &I, 
json::Object &Obj,
   Obj["TypeDeclaration"] = I.TypeDeclaration;
   Obj["IsUsing"] = I.IsUsing;
   Object TypeObj = Object();
-  serializeTypeInfo(I.Underlying, TypeObj);
+  serializeInfo(I.Underlying, TypeObj);
   Obj["Underlying"] = std::move(TypeObj);
 }
 
@@ -224,27 +280,7 @@ static void serializeInfo(const RecordInfo &I, 
json::Object &Obj,
       Obj["ProtectedMembers"] = std::move(ProtectedMembers);
   }
 
-  if (!I.Children.Enums.empty()) {
-    json::Value EnumsArray = Array();
-    auto &EnumsArrayRef = *EnumsArray.getAsArray();
-    for (const auto &Enum : I.Children.Enums) {
-      json::Object EnumObj;
-      serializeInfo(Enum, EnumObj, RepositoryUrl);
-      EnumsArrayRef.push_back(std::move(EnumObj));
-    }
-    Obj["Enums"] = std::move(EnumsArray);
-  }
-
-  if (!I.Children.Typedefs.empty()) {
-    json::Value TypedefsArray = Array();
-    auto &TypedefsArrayRef = *TypedefsArray.getAsArray();
-    for (const auto &Typedef : I.Children.Typedefs) {
-      json::Object TypedefObj;
-      serializeInfo(Typedef, TypedefObj, RepositoryUrl);
-      TypedefsArrayRef.push_back(std::move(TypedefObj));
-    }
-    Obj["Typedefs"] = std::move(TypedefsArray);
-  }
+  serializeCommonChildren(I.Children, Obj, RepositoryUrl);
 }
 
 Error JSONGenerator::generateDocs(
diff --git a/clang-tools-extra/test/clang-doc/json/class.cpp 
b/clang-tools-extra/test/clang-doc/json/class.cpp
index 3126fc689ffae..c50dcfc8f7369 100644
--- a/clang-tools-extra/test/clang-doc/json/class.cpp
+++ b/clang-tools-extra/test/clang-doc/json/class.cpp
@@ -21,6 +21,8 @@ struct MyClass {
   };
   
   typedef int MyTypedef;
+  
+  class NestedClass;
 protected:
   int protectedMethod();
 
@@ -158,6 +160,14 @@ struct MyClass {
 // CHECK-NEXT:        "Type": "int"
 // CHECK-NEXT:      }
 // CHECK-NEXT:    ],
+// CHECK-NEXT:    "Records": [
+// CHECK-NEXT:      {
+// CHECK-NEXT:        "ID": "{{[0-9A-F]*}}",
+// CHECK-NEXT:        "Link": "NestedClass.json",
+// CHECK-NEXT:        "Name": "NestedClass",
+// CHECK-NEXT:        "QualName": "NestedClass"
+// CHECK-NEXT:      }
+// CHECK-NEXT:    ]
 // CHECK-NEXT:    "TagType": "struct",
 // CHECK-NEXT:    "Typedefs": [
 // CHECK-NEXT:      {

>From 7c8324bf6fd8d7f82f8598bb53d1ca9aa51d7acd Mon Sep 17 00:00:00 2001
From: Erick Velez <erickvel...@gmail.com>
Date: Thu, 5 Jun 2025 15:18:50 -0700
Subject: [PATCH 3/4] return errors, add unit tests

---
 clang-tools-extra/clang-doc/JSONGenerator.cpp |  42 ++++-
 .../unittests/clang-doc/CMakeLists.txt        |   1 +
 .../unittests/clang-doc/JSONGeneratorTest.cpp | 166 ++++++++++++++++++
 3 files changed, 203 insertions(+), 6 deletions(-)
 create mode 100644 clang-tools-extra/unittests/clang-doc/JSONGeneratorTest.cpp

diff --git a/clang-tools-extra/clang-doc/JSONGenerator.cpp 
b/clang-tools-extra/clang-doc/JSONGenerator.cpp
index 2c4cc18643e1c..035277f6af6c5 100644
--- a/clang-tools-extra/clang-doc/JSONGenerator.cpp
+++ b/clang-tools-extra/clang-doc/JSONGenerator.cpp
@@ -1,11 +1,10 @@
 #include "Generators.h"
+#include "clang/Basic/Specifiers.h"
 #include "llvm/Support/JSON.h"
 
 using namespace llvm;
 using namespace llvm::json;
 
-static llvm::ExitOnError ExitOnErr;
-
 namespace clang {
 namespace doc {
 
@@ -119,6 +118,18 @@ static void serializeReference(const Reference &Ref, 
Object &ReferenceObj,
   ReferenceObj["ID"] = toHex(toStringRef(Ref.USR));
 }
 
+static void serializeReference(Object &Obj, SmallVector<Reference, 4> 
References, std::string Key) {
+  json::Value ReferencesArray = Array();
+  json::Array &ReferencesArrayRef = *ReferencesArray.getAsArray();
+  for (const auto& Reference : References) {
+    Object ReferenceObject = Object();
+    auto BasePath = Reference.getRelativeFilePath("");
+    serializeReference(Reference, ReferenceObject, BasePath);
+    ReferencesArrayRef.push_back(std::move(ReferenceObject));
+  } 
+  Obj[Key] = std::move(ReferencesArray);
+}
+
 // Although namespaces and records both have ScopeChildren, they serialize them
 // differently. Only enums, records, and typedefs are handled here.
 static void serializeCommonChildren(const ScopeChildren &Children,
@@ -280,6 +291,26 @@ static void serializeInfo(const RecordInfo &I, 
json::Object &Obj,
       Obj["ProtectedMembers"] = std::move(ProtectedMembers);
   }
 
+  if (!I.Bases.empty()) {
+    json::Value BasesArray = Array();
+    json::Array &BasesArrayRef = *BasesArray.getAsArray();
+    for (const auto &BaseInfo : I.Bases) {
+      Object BaseInfoObj = Object();
+      serializeInfo(BaseInfo, BaseInfoObj, RepositoryUrl);
+      BaseInfoObj["IsVirtual"] = BaseInfo.IsVirtual;
+      BaseInfoObj["Access"] = getAccessSpelling(BaseInfo.Access);
+      BaseInfoObj["IsParent"] = BaseInfo.IsParent;
+      BasesArrayRef.push_back(std::move(BaseInfoObj));
+    }
+    Obj["Bases"] = std::move(BasesArray);
+  }
+
+  if (!I.Parents.empty())
+    serializeReference(Obj, I.Parents, "Parents");
+
+  if (!I.VirtualParents.empty())
+    serializeReference(Obj, I.VirtualParents, "VirtualParents");
+
   serializeCommonChildren(I.Children, Obj, RepositoryUrl);
 }
 
@@ -297,7 +328,7 @@ Error JSONGenerator::generateDocs(
     if (!CreatedDirs.contains(Path)) {
       if (std::error_code Err = sys::fs::create_directories(Path);
           Err != std::error_code())
-        ExitOnErr(createFileError(Twine(Path), Err));
+        return createFileError(Twine(Path), Err);
       CreatedDirs.insert(Path);
     }
 
@@ -309,7 +340,7 @@ Error JSONGenerator::generateDocs(
     std::error_code FileErr;
     raw_fd_ostream InfoOS(Group.getKey(), FileErr, sys::fs::OF_Text);
     if (FileErr)
-      ExitOnErr(createFileError("cannot open file " + Group.getKey(), 
FileErr));
+      return createFileError("cannot open file " + Group.getKey(), FileErr);
 
     for (const auto &Info : Group.getValue())
       if (Error Err = generateDocForInfo(Info, InfoOS, CDCtx))
@@ -334,8 +365,7 @@ Error JSONGenerator::generateDocForInfo(Info *I, 
raw_ostream &OS,
   case InfoType::IT_typedef:
     break;
   case InfoType::IT_default:
-    ExitOnErr(
-        createStringError(inconvertibleErrorCode(), "unexpected info type"));
+    return createStringError(inconvertibleErrorCode(), "unexpected info type");
   }
   OS << llvm::formatv("{0:2}", llvm::json::Value(std::move(Obj)));
   return Error::success();
diff --git a/clang-tools-extra/unittests/clang-doc/CMakeLists.txt 
b/clang-tools-extra/unittests/clang-doc/CMakeLists.txt
index 59a856ed987dc..18166acf9bbca 100644
--- a/clang-tools-extra/unittests/clang-doc/CMakeLists.txt
+++ b/clang-tools-extra/unittests/clang-doc/CMakeLists.txt
@@ -31,6 +31,7 @@ add_extra_unittest(ClangDocTests
   MergeTest.cpp
   SerializeTest.cpp
   YAMLGeneratorTest.cpp
+  JSONGeneratorTest.cpp
   )
 
 clang_target_link_libraries(ClangDocTests
diff --git a/clang-tools-extra/unittests/clang-doc/JSONGeneratorTest.cpp 
b/clang-tools-extra/unittests/clang-doc/JSONGeneratorTest.cpp
new file mode 100644
index 0000000000000..f319a89f06b5c
--- /dev/null
+++ b/clang-tools-extra/unittests/clang-doc/JSONGeneratorTest.cpp
@@ -0,0 +1,166 @@
+#include "ClangDocTest.h"
+#include "Generators.h"
+#include "Representation.h"
+#include "gtest/gtest.h"
+
+namespace clang {
+namespace doc {
+
+static std::unique_ptr<Generator> getJSONGenerator() {
+  auto G = doc::findGeneratorByName("json");
+  if (!G)
+    return nullptr;
+  return std::move(G.get());
+}
+
+TEST(JSONGeneratorTest, emitRecordJSON) {
+  RecordInfo I;
+  I.Name = "Foo";
+  I.FullName = "Foo";
+  I.IsTypeDef = false;
+  I.Namespace.emplace_back(EmptySID, "GlobalNamespace", 
InfoType::IT_namespace);
+  I.Path = "GlobalNamespace";
+  I.DefLoc = Location(1, 1, "main.cpp");
+  I.TagType = TagTypeKind::Class;
+
+  I.Children.Enums.emplace_back();
+  I.Children.Enums.back().Name = "Color";
+  I.Children.Enums.back().Scoped = false;
+  I.Children.Enums.back().Members.emplace_back();
+  I.Children.Enums.back().Members.back().Name = "RED";
+  I.Children.Enums.back().Members.back().Value = "0";
+
+  I.Members.emplace_back(TypeInfo("int"), "X", AccessSpecifier::AS_protected);
+
+  I.Bases.emplace_back(EmptySID, "F", "path/to/F", true,
+                       AccessSpecifier::AS_public, true);
+  I.Bases.back().Children.Functions.emplace_back();
+  I.Bases.back().Children.Functions.back().Name = "InheritedFunctionOne";
+  I.Bases.back().Members.emplace_back(TypeInfo("int"), "N",
+                                      AccessSpecifier::AS_public);
+
+  // F is in the global namespace
+  I.Parents.emplace_back(EmptySID, "F", InfoType::IT_record, "");
+  I.VirtualParents.emplace_back(EmptySID, "G", InfoType::IT_record,
+                                "path::to::G::G", "path/to/G");
+
+  I.Children.Records.emplace_back(EmptySID, "ChildStruct", InfoType::IT_record,
+                                  "path::to::A::r::ChildStruct", 
"path/to/A/r");
+  I.Children.Functions.emplace_back();
+  I.Children.Functions.back().Name = "OneFunction";
+
+  auto G = getJSONGenerator();
+  assert(G);
+  std::string Buffer;
+  llvm::raw_string_ostream Actual(Buffer);
+  auto Err = G->generateDocForInfo(&I, Actual, ClangDocContext());
+  assert(!Err);
+  std::string Expected = R"raw({
+  "Bases": [
+    {
+      "Access": "public",
+      "FullName": "F",
+      "IsParent": true,
+      "IsTypedef": false,
+      "IsVirtual": true,
+      "Name": "F",
+      "Path": "path/to/F",
+      "PublicFunctions": [
+        {
+          "IsStatic": false,
+          "Name": "InheritedFunctionOne",
+          "ReturnType": {
+            "ID": "0000000000000000000000000000000000000000",
+            "IsBuiltIn": false,
+            "IsTemplate": false,
+            "Name": "",
+            "QualName": ""
+          },
+          "USR": "0000000000000000000000000000000000000000"
+        }
+      ],
+      "PublicMembers": [
+        {
+          "Name": "N",
+          "Type": "int"
+        }
+      ],
+      "TagType": "struct",
+      "USR": "0000000000000000000000000000000000000000"
+    }
+  ],
+  "Enums": [
+    {
+      "Members": [
+        {
+          "Name": "RED",
+          "Value": "0"
+        }
+      ],
+      "Name": "Color",
+      "Scoped": false,
+      "USR": "0000000000000000000000000000000000000000"
+    }
+  ],
+  "FullName": "Foo",
+  "IsTypedef": false,
+  "Location": {
+    "Filename": "main.cpp",
+    "LineNumber": 1
+  },
+  "Name": "Foo",
+  "Namespace": [
+    "GlobalNamespace"
+  ],
+  "Parents": [
+    {
+      "ID": "0000000000000000000000000000000000000000",
+      "Link": "F.json",
+      "Name": "F",
+      "QualName": ""
+    }
+  ],
+  "Path": "GlobalNamespace",
+  "ProtectedMembers": [
+    {
+      "Name": "X",
+      "Type": "int"
+    }
+  ],
+  "PublicFunctions": [
+    {
+      "IsStatic": false,
+      "Name": "OneFunction",
+      "ReturnType": {
+        "ID": "0000000000000000000000000000000000000000",
+        "IsBuiltIn": false,
+        "IsTemplate": false,
+        "Name": "",
+        "QualName": ""
+      },
+      "USR": "0000000000000000000000000000000000000000"
+    }
+  ],
+  "Records": [
+    {
+      "ID": "0000000000000000000000000000000000000000",
+      "Link": "ChildStruct.json",
+      "Name": "ChildStruct",
+      "QualName": "path::to::A::r::ChildStruct"
+    }
+  ],
+  "TagType": "class",
+  "USR": "0000000000000000000000000000000000000000",
+  "VirtualParents": [
+    {
+      "ID": "0000000000000000000000000000000000000000",
+      "Link": "G.json",
+      "Name": "G",
+      "QualName": "path::to::G::G"
+    }
+  ]
+})raw";
+  EXPECT_EQ(Expected, Actual.str());
+}
+} // namespace doc
+} // namespace clang

>From 9995fe0d8bae198b05a6d5f329de614c671ed8bf Mon Sep 17 00:00:00 2001
From: Erick Velez <erickvel...@gmail.com>
Date: Fri, 6 Jun 2025 11:26:07 -0700
Subject: [PATCH 4/4] add templates, use JSON values

---
 clang-tools-extra/clang-doc/JSONGenerator.cpp | 168 +++++++++++-------
 .../test/clang-doc/json/class-template.cpp    |  29 +++
 .../test/clang-doc/json/class.cpp             |  23 +--
 .../test/clang-doc/json/method-template.cpp   |  40 +++++
 .../unittests/clang-doc/JSONGeneratorTest.cpp |  35 ++--
 5 files changed, 211 insertions(+), 84 deletions(-)
 create mode 100644 clang-tools-extra/test/clang-doc/json/class-template.cpp
 create mode 100644 clang-tools-extra/test/clang-doc/json/method-template.cpp

diff --git a/clang-tools-extra/clang-doc/JSONGenerator.cpp 
b/clang-tools-extra/clang-doc/JSONGenerator.cpp
index 035277f6af6c5..d874505c783ee 100644
--- a/clang-tools-extra/clang-doc/JSONGenerator.cpp
+++ b/clang-tools-extra/clang-doc/JSONGenerator.cpp
@@ -78,16 +78,16 @@ static json::Value serializeComment(const CommentInfo 
&Comment) {
 
 static void serializeCommonAttributes(const Info &I, json::Object &Obj,
                                       std::optional<StringRef> RepositoryUrl) {
-  Obj["Name"] = I.Name.str();
+  Obj["Name"] = I.Name;
   Obj["USR"] = toHex(toStringRef(I.USR));
 
   if (!I.Path.empty())
-    Obj["Path"] = I.Path.str();
+    Obj["Path"] = I.Path;
 
   if (!I.Namespace.empty()) {
     Obj["Namespace"] = json::Array();
     for (const auto &NS : I.Namespace)
-      Obj["Namespace"].getAsArray()->push_back(NS.Name.str());
+      Obj["Namespace"].getAsArray()->push_back(NS.Name);
   }
 
   if (!I.Description.empty()) {
@@ -95,7 +95,7 @@ static void serializeCommonAttributes(const Info &I, 
json::Object &Obj,
     auto &DescArrayRef = *DescArray.getAsArray();
     for (const auto &Comment : I.Description)
       DescArrayRef.push_back(serializeComment(Comment));
-    Obj["Description"] = std::move(DescArray);
+    Obj["Description"] = DescArray;
   }
 
   // Namespaces aren't SymbolInfos, so they dont have a DefLoc
@@ -115,19 +115,21 @@ static void serializeReference(const Reference &Ref, 
Object &ReferenceObj,
   ReferenceObj["Link"] = Path;
   ReferenceObj["Name"] = Ref.Name;
   ReferenceObj["QualName"] = Ref.QualName;
-  ReferenceObj["ID"] = toHex(toStringRef(Ref.USR));
+  ReferenceObj["USR"] = toHex(toStringRef(Ref.USR));
 }
 
-static void serializeReference(Object &Obj, SmallVector<Reference, 4> 
References, std::string Key) {
+static void serializeReference(const SmallVector<Reference, 4> &References,
+                               Object &Obj, std::string Key) {
   json::Value ReferencesArray = Array();
   json::Array &ReferencesArrayRef = *ReferencesArray.getAsArray();
   for (const auto& Reference : References) {
-    Object ReferenceObject = Object();
+    json::Value ReferenceVal = Object();
+    auto &ReferenceObj = *ReferenceVal.getAsObject();
     auto BasePath = Reference.getRelativeFilePath("");
-    serializeReference(Reference, ReferenceObject, BasePath);
-    ReferencesArrayRef.push_back(std::move(ReferenceObject));
-  } 
-  Obj[Key] = std::move(ReferencesArray);
+    serializeReference(Reference, ReferenceObj, BasePath);
+    ReferencesArrayRef.push_back(ReferenceVal);
+  }
+  Obj[Key] = ReferencesArray;
 }
 
 // Although namespaces and records both have ScopeChildren, they serialize them
@@ -139,41 +141,74 @@ static void serializeCommonChildren(const ScopeChildren 
&Children,
     json::Value EnumsArray = Array();
     auto &EnumsArrayRef = *EnumsArray.getAsArray();
     for (const auto &Enum : Children.Enums) {
-      json::Object EnumObj;
+      json::Value EnumVal = Object();
+      auto &EnumObj = *EnumVal.getAsObject();
       serializeInfo(Enum, EnumObj, RepositoryUrl);
-      EnumsArrayRef.push_back(std::move(EnumObj));
+      EnumsArrayRef.push_back(EnumVal);
     }
-    Obj["Enums"] = std::move(EnumsArray);
+    Obj["Enums"] = EnumsArray;
   }
 
   if (!Children.Typedefs.empty()) {
     json::Value TypedefsArray = Array();
     auto &TypedefsArrayRef = *TypedefsArray.getAsArray();
     for (const auto &Typedef : Children.Typedefs) {
-      json::Object TypedefObj;
+      json::Value TypedefVal = Object();
+      auto &TypedefObj = *TypedefVal.getAsObject();
       serializeInfo(Typedef, TypedefObj, RepositoryUrl);
-      TypedefsArrayRef.push_back(std::move(TypedefObj));
+      TypedefsArrayRef.push_back(TypedefVal);
     }
-    Obj["Typedefs"] = std::move(TypedefsArray);
+    Obj["Typedefs"] = TypedefsArray;
   }
 
   if (!Children.Records.empty()) {
     json::Value RecordsArray = Array();
     auto &RecordsArrayRef = *RecordsArray.getAsArray();
     for (const auto &Record : Children.Records) {
-      json::Object RecordObj;
+      json::Value RecordVal = Object();
+      auto &RecordObj = *RecordVal.getAsObject();
       SmallString<64> BasePath = Record.getRelativeFilePath("");
       serializeReference(Record, RecordObj, BasePath);
-      RecordsArrayRef.push_back(std::move(RecordObj));
+      RecordsArrayRef.push_back(RecordVal);
     }
-    Obj["Records"] = std::move(RecordsArray);
+    Obj["Records"] = RecordsArray;
   }
 }
 
+static void serializeInfo(const TemplateInfo &Template, Object &Obj) {
+  json::Value TemplateVal = Object();
+  auto &TemplateObj = *TemplateVal.getAsObject();
+
+  if (Template.Specialization) {
+    json::Value TemplateSpecializationVal = Object();
+    auto &TemplateSpecializationObj = *TemplateSpecializationVal.getAsObject();
+    TemplateSpecializationObj["SpecializationOf"] =
+        toHex(toStringRef(Template.Specialization->SpecializationOf));
+    if (!Template.Specialization->Params.empty()) {
+      json::Value ParamsArray = Array();
+      auto &ParamsArrayRef = *ParamsArray.getAsArray();
+      for (const auto &Param : Template.Specialization->Params)
+        ParamsArrayRef.push_back(Param.Contents);
+      TemplateSpecializationObj["Parameters"] = ParamsArray;
+    }
+    TemplateObj["Specialization"] = TemplateSpecializationVal;
+  }
+
+  if (!Template.Params.empty()) {
+    json::Value ParamsArray = Array();
+    auto &ParamsArrayRef = *ParamsArray.getAsArray();
+    for (const auto &Param : Template.Params)
+      ParamsArrayRef.push_back(Param.Contents);
+    TemplateObj["Parameters"] = ParamsArray;
+  }
+
+  Obj["Template"] = TemplateVal;
+}
+
 static void serializeInfo(const TypeInfo &I, Object &Obj) {
   Obj["Name"] = I.Type.Name;
   Obj["QualName"] = I.Type.QualName;
-  Obj["ID"] = toHex(toStringRef(I.Type.USR));
+  Obj["USR"] = toHex(toStringRef(I.Type.USR));
   Obj["IsTemplate"] = I.IsTemplate;
   Obj["IsBuiltIn"] = I.IsBuiltIn;
 }
@@ -191,13 +226,17 @@ static void serializeInfo(const FunctionInfo &F, 
json::Object &Obj,
     json::Value ParamsArray = json::Array();
     auto &ParamsArrayRef = *ParamsArray.getAsArray();
     for (const auto &Param : F.Params) {
-      json::Object ParamObj;
+      json::Value ParamVal = Object();
+      auto &ParamObj = *ParamVal.getAsObject();
       ParamObj["Name"] = Param.Name;
       ParamObj["Type"] = Param.Type.Name;
-      ParamsArrayRef.push_back(std::move(ParamObj));
+      ParamsArrayRef.push_back(ParamVal);
     }
-    Obj["Params"] = std::move(ParamsArray);
+    Obj["Params"] = ParamsArray;
   }
+
+  if (F.Template)
+    serializeInfo(F.Template.value(), Obj);
 }
 
 static void serializeInfo(const EnumInfo &I, json::Object &Obj,
@@ -206,26 +245,28 @@ static void serializeInfo(const EnumInfo &I, json::Object 
&Obj,
   Obj["Scoped"] = I.Scoped;
 
   if (I.BaseType) {
-    json::Object BaseTypeObj;
+    json::Value BaseTypeVal = Object();
+    auto &BaseTypeObj = *BaseTypeVal.getAsObject();
     BaseTypeObj["Name"] = I.BaseType->Type.Name;
     BaseTypeObj["QualName"] = I.BaseType->Type.QualName;
-    BaseTypeObj["ID"] = toHex(toStringRef(I.BaseType->Type.USR));
-    Obj["BaseType"] = std::move(BaseTypeObj);
+    BaseTypeObj["USR"] = toHex(toStringRef(I.BaseType->Type.USR));
+    Obj["BaseType"] = BaseTypeVal;
   }
 
   if (!I.Members.empty()) {
     json::Value MembersArray = Array();
     auto &MembersArrayRef = *MembersArray.getAsArray();
     for (const auto &Member : I.Members) {
-      json::Object MemberObj;
+      json::Value MemberVal = Object();
+      auto &MemberObj = *MemberVal.getAsObject();
       MemberObj["Name"] = Member.Name;
       if (!Member.ValueExpr.empty())
         MemberObj["ValueExpr"] = Member.ValueExpr;
       else
         MemberObj["Value"] = Member.Value;
-      MembersArrayRef.push_back(std::move(MemberObj));
+      MembersArrayRef.push_back(MemberVal);
     }
-    Obj["Members"] = std::move(MembersArray);
+    Obj["Members"] = MembersArray;
   }
 }
 
@@ -234,82 +275,89 @@ static void serializeInfo(const TypedefInfo &I, 
json::Object &Obj,
   serializeCommonAttributes(I, Obj, RepositoryUrl);
   Obj["TypeDeclaration"] = I.TypeDeclaration;
   Obj["IsUsing"] = I.IsUsing;
-  Object TypeObj = Object();
+  json::Value TypeVal = Object();
+  auto &TypeObj = *TypeVal.getAsObject();
   serializeInfo(I.Underlying, TypeObj);
-  Obj["Underlying"] = std::move(TypeObj);
+  Obj["Underlying"] = TypeVal;
 }
 
 static void serializeInfo(const RecordInfo &I, json::Object &Obj,
                           std::optional<StringRef> RepositoryUrl) {
   serializeCommonAttributes(I, Obj, RepositoryUrl);
-  Obj["FullName"] = I.Name.str();
+  Obj["FullName"] = I.FullName;
   Obj["TagType"] = getTagType(I.TagType);
   Obj["IsTypedef"] = I.IsTypeDef;
 
   if (!I.Children.Functions.empty()) {
-    json::Value PublicFunctionArr = Array();
-    json::Array &PublicFunctionARef = *PublicFunctionArr.getAsArray();
-    json::Value ProtectedFunctionArr = Array();
-    json::Array &ProtectedFunctionARef = *ProtectedFunctionArr.getAsArray();
+    json::Value PubFunctionsArray = Array();
+    json::Array &PubFunctionsArrayRef = *PubFunctionsArray.getAsArray();
+    json::Value ProtFunctionsArray = Array();
+    json::Array &ProtFunctionsArrayRef = *ProtFunctionsArray.getAsArray();
 
     for (const auto &Function : I.Children.Functions) {
-      json::Object FunctionObj;
+      json::Value FunctionVal = Object();
+      auto &FunctionObj = *FunctionVal.getAsObject();
       serializeInfo(Function, FunctionObj, RepositoryUrl);
       AccessSpecifier Access = Function.Access;
       if (Access == AccessSpecifier::AS_public)
-        PublicFunctionARef.push_back(std::move(FunctionObj));
+        PubFunctionsArrayRef.push_back(FunctionVal);
       else if (Access == AccessSpecifier::AS_protected)
-        ProtectedFunctionARef.push_back(std::move(FunctionObj));
+        ProtFunctionsArrayRef.push_back(FunctionVal);
     }
 
-    if (!PublicFunctionARef.empty())
-      Obj["PublicFunctions"] = std::move(PublicFunctionArr);
-    if (!ProtectedFunctionARef.empty())
-      Obj["ProtectedFunctions"] = std::move(ProtectedFunctionArr);
+    if (!PubFunctionsArrayRef.empty())
+      Obj["PublicFunctions"] = PubFunctionsArray;
+    if (!ProtFunctionsArrayRef.empty())
+      Obj["ProtectedFunctions"] = ProtFunctionsArray;
   }
 
   if (!I.Members.empty()) {
-    json::Value PublicMembers = Array();
-    json::Array &PubMemberRef = *PublicMembers.getAsArray();
-    json::Value ProtectedMembers = Array();
-    json::Array &ProtMemberRef = *ProtectedMembers.getAsArray();
+    json::Value PublicMembersArray = Array();
+    json::Array &PubMembersArrayRef = *PublicMembersArray.getAsArray();
+    json::Value ProtectedMembersArray = Array();
+    json::Array &ProtMembersArrayRef = *ProtectedMembersArray.getAsArray();
 
     for (const MemberTypeInfo &Member : I.Members) {
-      json::Object MemberObj = Object();
+      json::Value MemberVal = Object();
+      auto &MemberObj = *MemberVal.getAsObject();
       MemberObj["Name"] = Member.Name;
       MemberObj["Type"] = Member.Type.Name;
 
       if (Member.Access == AccessSpecifier::AS_public)
-        PubMemberRef.push_back(std::move(MemberObj));
+        PubMembersArrayRef.push_back(MemberVal);
       else if (Member.Access == AccessSpecifier::AS_protected)
-        ProtMemberRef.push_back(std::move(MemberObj));
+        ProtMembersArrayRef.push_back(MemberVal);
     }
 
-    if (!PubMemberRef.empty())
-      Obj["PublicMembers"] = std::move(PublicMembers);
-    if (!ProtMemberRef.empty())
-      Obj["ProtectedMembers"] = std::move(ProtectedMembers);
+    if (!PubMembersArrayRef.empty())
+      Obj["PublicMembers"] = PublicMembersArray;
+    if (!ProtMembersArrayRef.empty())
+      Obj["ProtectedMembers"] = ProtectedMembersArray;
   }
 
   if (!I.Bases.empty()) {
     json::Value BasesArray = Array();
     json::Array &BasesArrayRef = *BasesArray.getAsArray();
     for (const auto &BaseInfo : I.Bases) {
-      Object BaseInfoObj = Object();
+      json::Value BaseInfoVal = Object();
+      auto &BaseInfoObj = *BaseInfoVal.getAsObject();
       serializeInfo(BaseInfo, BaseInfoObj, RepositoryUrl);
       BaseInfoObj["IsVirtual"] = BaseInfo.IsVirtual;
       BaseInfoObj["Access"] = getAccessSpelling(BaseInfo.Access);
       BaseInfoObj["IsParent"] = BaseInfo.IsParent;
-      BasesArrayRef.push_back(std::move(BaseInfoObj));
+      BasesArrayRef.push_back(BaseInfoVal);
     }
-    Obj["Bases"] = std::move(BasesArray);
+    Obj["Bases"] = BasesArray;
   }
 
   if (!I.Parents.empty())
-    serializeReference(Obj, I.Parents, "Parents");
+    serializeReference(I.Parents, Obj, "Parents");
 
   if (!I.VirtualParents.empty())
-    serializeReference(Obj, I.VirtualParents, "VirtualParents");
+    serializeReference(I.VirtualParents, Obj, "VirtualParents");
+
+  if (I.Template)
+    serializeInfo(I.Template.value(), Obj);
 
   serializeCommonChildren(I.Children, Obj, RepositoryUrl);
 }
diff --git a/clang-tools-extra/test/clang-doc/json/class-template.cpp 
b/clang-tools-extra/test/clang-doc/json/class-template.cpp
new file mode 100644
index 0000000000000..e3ca086d1d9a4
--- /dev/null
+++ b/clang-tools-extra/test/clang-doc/json/class-template.cpp
@@ -0,0 +1,29 @@
+// RUN: rm -rf %t && mkdir -p %t
+// RUN: clang-doc --output=%t --format=json --executor=standalone %s
+// RUN: FileCheck %s < %t/GlobalNamespace/MyClass.json
+
+template<typename T> struct MyClass {
+  T MemberTemplate;
+  T method(T Param); 
+};
+
+// CHECK:         "Name": "MyClass",
+// CHECK:         "Name": "method",
+// CHECK:         "Params": [
+// CHECK-NEXT:      {
+// CHECK-NEXT:        "Name": "Param",
+// CHECK-NEXT:        "Type": "T"
+// CHECK-NEXT:      } 
+// CHECK-NEXT:    ], 
+// CHECK-NEXT:    "ReturnType": {
+// CHECK-NEXT:      "IsBuiltIn": false,
+// CHECK-NEXT:      "IsTemplate": false,
+// CHECK-NEXT:      "Name": "T",
+// CHECK-NEXT:      "QualName": "T"
+// CHECK-NEXT:      "USR": "0000000000000000000000000000000000000000"
+// CHECK:           "Name": "MemberTemplate",
+// CHECK:           "Type": "T"
+// CHECK:         "Template": {
+// CHECK-NEXT:      "Parameters": [
+// CHECK-NEXT:        "typename T"
+// CHECK-NEXT:      ] 
diff --git a/clang-tools-extra/test/clang-doc/json/class.cpp 
b/clang-tools-extra/test/clang-doc/json/class.cpp
index c50dcfc8f7369..8cb8b1e5d3c06 100644
--- a/clang-tools-extra/test/clang-doc/json/class.cpp
+++ b/clang-tools-extra/test/clang-doc/json/class.cpp
@@ -86,7 +86,8 @@ struct MyClass {
 // CHECK-NEXT:        "USR": "{{[0-9A-F]*}}"
 // CHECK-NEXT:      }
 // CHECK-NEXT:    ],
-// CHECK-NEXT:    "FullName": "MyClass",
+// COM:           FIXME: FullName is not emitted correctly.
+// CHECK-NEXT:    "FullName": "",
 // CHECK-NEXT:    "IsTypedef": false,
 // CHECK-NEXT:    "Location": {
 // CHECK-NEXT:      "Filename": "{{.*}}class.cpp",
@@ -106,11 +107,11 @@ struct MyClass {
 // CHECK-NEXT:         "GlobalNamespace"
 // CHECK-NEXT:       ],
 // CHECK-NEXT:       "ReturnType": {
-// CHECK-NEXT:         "ID": "{{[0-9A-F]*}}",
 // CHECK-NEXT:         "IsBuiltIn": false,
 // CHECK-NEXT:         "IsTemplate": false,
 // CHECK-NEXT:         "Name": "int",
-// CHECK-NEXT:         "QualName": "int"
+// CHECK-NEXT:         "QualName": "int",
+// CHECK-NEXT:         "USR": "{{[0-9A-F]*}}"
 // CHECK-NEXT:       },
 // CHECK-NEXT:       "USR": "{{[0-9A-F]*}}"
 // CHECK-NEXT:      }
@@ -136,11 +137,11 @@ struct MyClass {
 // CHECK-NEXT:          }
 // CHECK-NEXT:        ],
 // CHECK-NEXT:        "ReturnType": {
-// CHECK-NEXT:          "ID": "{{[0-9A-F]*}}",
 // CHECK-NEXT:          "IsBuiltIn": false,
 // CHECK-NEXT:          "IsTemplate": false,
 // CHECK-NEXT:          "Name": "int",
-// CHECK-NEXT:          "QualName": "int"
+// CHECK-NEXT:          "QualName": "int",
+// CHECK-NEXT:          "USR": "{{[0-9A-F]*}}"
 // CHECK-NEXT:        },
 // CHECK-NEXT:        "USR": "{{[0-9A-F]*}}"
 // CHECK-NEXT:      },
@@ -148,11 +149,11 @@ struct MyClass {
 // CHECk:             "IsStatic": false,
 // CHECK:             "Name": "getConst",
 // CHECK:             "ReturnType": {
-// CHECK-NEXT:          "ID": "{{[0-9A-F]*}}",
 // CHECK-NEXT:          "IsBuiltIn": false,
 // CHECK-NEXT:          "IsTemplate": false,
 // CHECK-NEXT:          "Name": "const int &",
-// CHECK-NEXT:          "QualName": "const int &"
+// CHECK-NEXT:          "QualName": "const int &",
+// CHECK-NEXT:          "USR": "{{[0-9A-F]*}}"
 // CHECK-NEXT:        },
 // CHECK:         "PublicMembers": [
 // CHECK-NEXT:      {
@@ -162,10 +163,10 @@ struct MyClass {
 // CHECK-NEXT:    ],
 // CHECK-NEXT:    "Records": [
 // CHECK-NEXT:      {
-// CHECK-NEXT:        "ID": "{{[0-9A-F]*}}",
 // CHECK-NEXT:        "Link": "NestedClass.json",
 // CHECK-NEXT:        "Name": "NestedClass",
-// CHECK-NEXT:        "QualName": "NestedClass"
+// CHECK-NEXT:        "QualName": "NestedClass",
+// CHECK-NEXT:        "USR": "{{[0-9A-F]*}}"
 // CHECK-NEXT:      }
 // CHECK-NEXT:    ]
 // CHECK-NEXT:    "TagType": "struct",
@@ -184,10 +185,10 @@ struct MyClass {
 // CHECK-NEXT:        "TypeDeclaration": "",
 // CHECK-NEXT:        "USR": "{{[0-9A-F]*}}",
 // CHECK-NEXT:        "Underlying": {
-// CHECK-NEXT:          "ID": "0000000000000000000000000000000000000000",
 // CHECK-NEXT:          "IsBuiltIn": false,
 // CHECK-NEXT:          "IsTemplate": false,
 // CHECK-NEXT:          "Name": "int",
-// CHECK-NEXT:          "QualName": "int"
+// CHECK-NEXT:          "QualName": "int",
+// CHECK-NEXT:          "USR": "0000000000000000000000000000000000000000"
 // CHECK:         "USR": "{{[0-9A-F]*}}"
 // CHECK-NEXT:  }
diff --git a/clang-tools-extra/test/clang-doc/json/method-template.cpp 
b/clang-tools-extra/test/clang-doc/json/method-template.cpp
new file mode 100644
index 0000000000000..4c10275dd4293
--- /dev/null
+++ b/clang-tools-extra/test/clang-doc/json/method-template.cpp
@@ -0,0 +1,40 @@
+// RUN: rm -rf %t && mkdir -p %t
+// RUN: clang-doc --output=%t --format=json --executor=standalone %s
+// RUN: FileCheck %s < %t/GlobalNamespace/MyClass.json
+
+struct MyClass {
+  template<class T> T methodTemplate(T param) {
+  }
+};
+
+// CHECK:           "PublicFunctions": [
+// CHECK-NEXT:        {
+// CHECK-NEXT:          "IsStatic": false,
+// CHECK-NEXT:          "Location": {
+// CHECK-NEXT:            "Filename": "{{.*}}method-template.cpp",
+// CHECK-NEXT:            "LineNumber": 6
+// CHECK-NEXT:          },
+// CHECK-NEXT:          "Name": "methodTemplate",
+// CHECK-NEXT:          "Namespace": [
+// CHECK-NEXT:            "MyClass",
+// CHECK-NEXT:            "GlobalNamespace"
+// CHECK-NEXT:          ],
+// CHECK-NEXT:          "Params": [
+// CHECK-NEXT:            {
+// CHECK-NEXT:              "Name": "param",
+// CHECK-NEXT:              "Type": "T"
+// CHECK-NEXT:            }
+// CHECK-NEXT:          ],
+// CHECK-NEXT:          "ReturnType": {
+// CHECK-NEXT:            "IsBuiltIn": false,
+// CHECK-NEXT:            "IsTemplate": false,
+// CHECK-NEXT:            "Name": "T",
+// CHECK-NEXT:            "QualName": "T",
+// CHECK-NEXT:            "USR": "0000000000000000000000000000000000000000"
+// CHECK-NEXT:          },
+// CHECK-NEXT:          "Template": {
+// CHECK-NEXT:            "Parameters": [
+// CHECK-NEXT:              "class T"
+// CHECK-NEXT:            ]
+// CHECK-NEXT:          },
+// CHECK-NEXT:          "USR": "{{[0-9A-F]*}}"
\ No newline at end of file
diff --git a/clang-tools-extra/unittests/clang-doc/JSONGeneratorTest.cpp 
b/clang-tools-extra/unittests/clang-doc/JSONGeneratorTest.cpp
index f319a89f06b5c..8e56c6fe3bae8 100644
--- a/clang-tools-extra/unittests/clang-doc/JSONGeneratorTest.cpp
+++ b/clang-tools-extra/unittests/clang-doc/JSONGeneratorTest.cpp
@@ -16,13 +16,17 @@ static std::unique_ptr<Generator> getJSONGenerator() {
 TEST(JSONGeneratorTest, emitRecordJSON) {
   RecordInfo I;
   I.Name = "Foo";
-  I.FullName = "Foo";
+  // FIXME: FullName is not emitted correctly.
+  I.FullName = "";
   I.IsTypeDef = false;
   I.Namespace.emplace_back(EmptySID, "GlobalNamespace", 
InfoType::IT_namespace);
   I.Path = "GlobalNamespace";
   I.DefLoc = Location(1, 1, "main.cpp");
   I.TagType = TagTypeKind::Class;
 
+  I.Template = TemplateInfo();
+  I.Template->Params.emplace_back("class T");
+
   I.Children.Enums.emplace_back();
   I.Children.Enums.back().Name = "Color";
   I.Children.Enums.back().Scoped = false;
@@ -59,7 +63,7 @@ TEST(JSONGeneratorTest, emitRecordJSON) {
   "Bases": [
     {
       "Access": "public",
-      "FullName": "F",
+      "FullName": "",
       "IsParent": true,
       "IsTypedef": false,
       "IsVirtual": true,
@@ -70,11 +74,11 @@ TEST(JSONGeneratorTest, emitRecordJSON) {
           "IsStatic": false,
           "Name": "InheritedFunctionOne",
           "ReturnType": {
-            "ID": "0000000000000000000000000000000000000000",
             "IsBuiltIn": false,
             "IsTemplate": false,
             "Name": "",
-            "QualName": ""
+            "QualName": "",
+            "USR": "0000000000000000000000000000000000000000"
           },
           "USR": "0000000000000000000000000000000000000000"
         }
@@ -102,7 +106,7 @@ TEST(JSONGeneratorTest, emitRecordJSON) {
       "USR": "0000000000000000000000000000000000000000"
     }
   ],
-  "FullName": "Foo",
+  "FullName": "",
   "IsTypedef": false,
   "Location": {
     "Filename": "main.cpp",
@@ -114,10 +118,10 @@ TEST(JSONGeneratorTest, emitRecordJSON) {
   ],
   "Parents": [
     {
-      "ID": "0000000000000000000000000000000000000000",
       "Link": "F.json",
       "Name": "F",
-      "QualName": ""
+      "QualName": "",
+      "USR": "0000000000000000000000000000000000000000"
     }
   ],
   "Path": "GlobalNamespace",
@@ -132,31 +136,36 @@ TEST(JSONGeneratorTest, emitRecordJSON) {
       "IsStatic": false,
       "Name": "OneFunction",
       "ReturnType": {
-        "ID": "0000000000000000000000000000000000000000",
         "IsBuiltIn": false,
         "IsTemplate": false,
         "Name": "",
-        "QualName": ""
+        "QualName": "",
+        "USR": "0000000000000000000000000000000000000000"
       },
       "USR": "0000000000000000000000000000000000000000"
     }
   ],
   "Records": [
     {
-      "ID": "0000000000000000000000000000000000000000",
       "Link": "ChildStruct.json",
       "Name": "ChildStruct",
-      "QualName": "path::to::A::r::ChildStruct"
+      "QualName": "path::to::A::r::ChildStruct",
+      "USR": "0000000000000000000000000000000000000000"
     }
   ],
   "TagType": "class",
+  "Template": {
+    "Parameters": [
+      "class T"
+    ]
+  },
   "USR": "0000000000000000000000000000000000000000",
   "VirtualParents": [
     {
-      "ID": "0000000000000000000000000000000000000000",
       "Link": "G.json",
       "Name": "G",
-      "QualName": "path::to::G::G"
+      "QualName": "path::to::G::G",
+      "USR": "0000000000000000000000000000000000000000"
     }
   ]
 })raw";

_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to