[clang] 7c3c9c4 - [clang][ssaf] Implement JSONFormat (#180021)

via cfe-commits cfe-commits at lists.llvm.org
Mon Feb 16 13:03:36 PST 2026


Author: Aviral Goel
Date: 2026-02-16T21:03:32Z
New Revision: 7c3c9c45f85da6f786858358f2643bc6f46c51b4

URL: https://github.com/llvm/llvm-project/commit/7c3c9c45f85da6f786858358f2643bc6f46c51b4
DIFF: https://github.com/llvm/llvm-project/commit/7c3c9c45f85da6f786858358f2643bc6f46c51b4.diff

LOG: [clang][ssaf] Implement JSONFormat (#180021)

This PR implements JSON serialization and deserialization support for
SSAF. To enable error reporting, the interface of `SerializationFormat`
APIs has been updated to return `llvm::Expected`. Error messages
produced by the `JSONFormat` implementation include multiline context
indicating the _conceptual_ path that led to the failure.

rdar://168770769
rdar://168929437

Assisted-By: claude

Added: 
    clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
    clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
    clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest.cpp

Modified: 
    clang/include/clang/Analysis/Scalable/Model/EntityId.h
    clang/include/clang/Analysis/Scalable/Model/EntityIdTable.h
    clang/include/clang/Analysis/Scalable/Model/PrivateFieldNames.def
    clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h
    clang/lib/Analysis/Scalable/CMakeLists.txt
    clang/unittests/Analysis/Scalable/CMakeLists.txt
    clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.cpp
    clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.h
    clang/unittests/Analysis/Scalable/Registries/SerializationFormatRegistryTest.cpp

Removed: 
    


################################################################################
diff  --git a/clang/include/clang/Analysis/Scalable/Model/EntityId.h b/clang/include/clang/Analysis/Scalable/Model/EntityId.h
index 6fa059445d853..e348486386cb6 100644
--- a/clang/include/clang/Analysis/Scalable/Model/EntityId.h
+++ b/clang/include/clang/Analysis/Scalable/Model/EntityId.h
@@ -29,6 +29,7 @@ class EntityIdTable;
 /// \see EntityIdTable
 class EntityId {
   friend class EntityIdTable;
+  friend class SerializationFormat;
 
   size_t Index;
 

diff  --git a/clang/include/clang/Analysis/Scalable/Model/EntityIdTable.h b/clang/include/clang/Analysis/Scalable/Model/EntityIdTable.h
index cf4bb83efddd0..a1099c4e4d0f8 100644
--- a/clang/include/clang/Analysis/Scalable/Model/EntityIdTable.h
+++ b/clang/include/clang/Analysis/Scalable/Model/EntityIdTable.h
@@ -21,6 +21,8 @@ namespace clang::ssaf {
 /// The table maps each unique EntityName to exactly one EntityId.
 /// Entities are never removed.
 class EntityIdTable {
+  friend class SerializationFormat;
+
   std::map<EntityName, EntityId> Entities;
 
 public:

diff  --git a/clang/include/clang/Analysis/Scalable/Model/PrivateFieldNames.def b/clang/include/clang/Analysis/Scalable/Model/PrivateFieldNames.def
index 59064659996b4..2d4bf05eff924 100644
--- a/clang/include/clang/Analysis/Scalable/Model/PrivateFieldNames.def
+++ b/clang/include/clang/Analysis/Scalable/Model/PrivateFieldNames.def
@@ -19,6 +19,8 @@
 
 FIELD(BuildNamespace, Kind)
 FIELD(BuildNamespace, Name)
+FIELD(EntityId, Index)
+FIELD(EntityIdTable, Entities)
 FIELD(EntityName, Namespace)
 FIELD(EntityName, Suffix)
 FIELD(EntityName, USR)

diff  --git a/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h b/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
new file mode 100644
index 0000000000000..c10650c2133e7
--- /dev/null
+++ b/clang/include/clang/Analysis/Scalable/Serialization/JSONFormat.h
@@ -0,0 +1,128 @@
+//===- JSONFormat.h ---------------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// JSON serialization format implementation.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef CLANG_ANALYSIS_SCALABLE_SERIALIZATION_JSONFORMAT_H
+#define CLANG_ANALYSIS_SCALABLE_SERIALIZATION_JSONFORMAT_H
+
+#include "clang/Analysis/Scalable/Serialization/SerializationFormat.h"
+#include "llvm/ADT/STLFunctionalExtras.h"
+#include "llvm/Support/JSON.h"
+
+namespace clang::ssaf {
+
+class EntityIdTable;
+class EntitySummary;
+class SummaryName;
+
+class JSONFormat final : public SerializationFormat {
+  using Array = llvm::json::Array;
+  using Object = llvm::json::Object;
+
+public:
+  // Helper class to provide limited access to EntityId conversion methods.
+  // Only exposes EntityId serialization/deserialization to format handlers.
+  class EntityIdConverter {
+  public:
+    EntityId fromJSON(uint64_t EntityIdIndex) const {
+      return Format.entityIdFromJSON(EntityIdIndex);
+    }
+
+    uint64_t toJSON(EntityId EI) const { return Format.entityIdToJSON(EI); }
+
+  private:
+    friend class JSONFormat;
+    EntityIdConverter(const JSONFormat &Format) : Format(Format) {}
+    const JSONFormat &Format;
+  };
+
+  llvm::Expected<TUSummary> readTUSummary(llvm::StringRef Path) override;
+
+  llvm::Error writeTUSummary(const TUSummary &Summary,
+                             llvm::StringRef Path) override;
+
+  using SerializerFn = llvm::function_ref<Object(const EntitySummary &,
+                                                 const EntityIdConverter &)>;
+  using DeserializerFn =
+      llvm::function_ref<llvm::Expected<std::unique_ptr<EntitySummary>>(
+          const Object &, EntityIdTable &, const EntityIdConverter &)>;
+
+  using FormatInfo = FormatInfoEntry<SerializerFn, DeserializerFn>;
+
+private:
+  static std::map<SummaryName, FormatInfo> initFormatInfos();
+  const std::map<SummaryName, FormatInfo> FormatInfos = initFormatInfos();
+
+  EntityId entityIdFromJSON(const uint64_t EntityIdIndex) const;
+  uint64_t entityIdToJSON(EntityId EI) const;
+
+  llvm::Expected<BuildNamespaceKind>
+  buildNamespaceKindFromJSON(llvm::StringRef BuildNamespaceKindStr) const;
+
+  llvm::Expected<BuildNamespace>
+  buildNamespaceFromJSON(const Object &BuildNamespaceObject) const;
+  Object buildNamespaceToJSON(const BuildNamespace &BN) const;
+
+  llvm::Expected<NestedBuildNamespace>
+  nestedBuildNamespaceFromJSON(const Array &NestedBuildNamespaceArray) const;
+  Array nestedBuildNamespaceToJSON(const NestedBuildNamespace &NBN) const;
+
+  llvm::Expected<EntityName>
+  entityNameFromJSON(const Object &EntityNameObject) const;
+  Object entityNameToJSON(const EntityName &EN) const;
+
+  llvm::Expected<std::pair<EntityName, EntityId>>
+  entityIdTableEntryFromJSON(const Object &EntityIdTableEntryObject) const;
+  llvm::Expected<EntityIdTable>
+  entityIdTableFromJSON(const Array &EntityIdTableArray) const;
+  Object entityIdTableEntryToJSON(const EntityName &EN, EntityId EI) const;
+  Array entityIdTableToJSON(const EntityIdTable &IdTable) const;
+
+  llvm::Expected<std::unique_ptr<EntitySummary>>
+  entitySummaryFromJSON(const SummaryName &SN,
+                        const Object &EntitySummaryObject,
+                        EntityIdTable &IdTable) const;
+  llvm::Expected<Object> entitySummaryToJSON(const SummaryName &SN,
+                                             const EntitySummary &ES) const;
+
+  llvm::Expected<std::pair<EntityId, std::unique_ptr<EntitySummary>>>
+  entityDataMapEntryFromJSON(const Object &EntityDataMapEntryObject,
+                             const SummaryName &SN,
+                             EntityIdTable &IdTable) const;
+  llvm::Expected<std::map<EntityId, std::unique_ptr<EntitySummary>>>
+  entityDataMapFromJSON(const SummaryName &SN, const Array &EntityDataArray,
+                        EntityIdTable &IdTable) const;
+  llvm::Expected<Array>
+  entityDataMapToJSON(const SummaryName &SN,
+                      const std::map<EntityId, std::unique_ptr<EntitySummary>>
+                          &EntityDataMap) const;
+
+  llvm::Expected<std::pair<SummaryName,
+                           std::map<EntityId, std::unique_ptr<EntitySummary>>>>
+  summaryDataMapEntryFromJSON(const Object &SummaryDataObject,
+                              EntityIdTable &IdTable) const;
+  llvm::Expected<Object> summaryDataMapEntryToJSON(
+      const SummaryName &SN,
+      const std::map<EntityId, std::unique_ptr<EntitySummary>> &SD) const;
+
+  llvm::Expected<
+      std::map<SummaryName, std::map<EntityId, std::unique_ptr<EntitySummary>>>>
+  summaryDataMapFromJSON(const Array &SummaryDataArray,
+                         EntityIdTable &IdTable) const;
+  llvm::Expected<Array> summaryDataMapToJSON(
+      const std::map<SummaryName,
+                     std::map<EntityId, std::unique_ptr<EntitySummary>>>
+          &SummaryDataMap) const;
+};
+
+} // namespace clang::ssaf
+
+#endif // CLANG_ANALYSIS_SCALABLE_SERIALIZATION_JSONFORMAT_H

diff  --git a/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h b/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h
index 78e4df0eb88fe..9c420b84ffd21 100644
--- a/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h
+++ b/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h
@@ -18,6 +18,7 @@
 #include "clang/Analysis/Scalable/Model/SummaryName.h"
 #include "clang/Analysis/Scalable/TUSummary/TUSummary.h"
 #include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
 
 namespace clang::ssaf {
 
@@ -25,20 +26,25 @@ class EntityId;
 class EntityIdTable;
 class EntityName;
 class EntitySummary;
+class SummaryName;
+class TUSummary;
 
 /// Abstract base class for serialization formats.
 class SerializationFormat {
 public:
   virtual ~SerializationFormat() = default;
 
-  virtual TUSummary readTUSummary(llvm::StringRef Path) = 0;
+  virtual llvm::Expected<TUSummary> readTUSummary(llvm::StringRef Path) = 0;
 
-  virtual void writeTUSummary(const TUSummary &Summary,
-                              llvm::StringRef OutputDir) = 0;
+  virtual llvm::Error writeTUSummary(const TUSummary &Summary,
+                                     llvm::StringRef Path) = 0;
 
 protected:
   // Helpers providing access to implementation details of basic data structures
   // for efficient serialization/deserialization.
+
+  EntityId makeEntityId(const size_t Index) const { return EntityId(Index); }
+
 #define FIELD(CLASS, FIELD_NAME)                                               \
   static const auto &get##FIELD_NAME(const CLASS &X) { return X.FIELD_NAME; }  \
   static auto &get##FIELD_NAME(CLASS &X) { return X.FIELD_NAME; }

diff  --git a/clang/lib/Analysis/Scalable/CMakeLists.txt b/clang/lib/Analysis/Scalable/CMakeLists.txt
index 4145e7a521ba4..522fc9dcf078d 100644
--- a/clang/lib/Analysis/Scalable/CMakeLists.txt
+++ b/clang/lib/Analysis/Scalable/CMakeLists.txt
@@ -7,6 +7,7 @@ add_clang_library(clangAnalysisScalable
   Model/BuildNamespace.cpp
   Model/EntityIdTable.cpp
   Model/EntityName.cpp
+  Serialization/JSONFormat.cpp
   Serialization/SerializationFormatRegistry.cpp
   TUSummary/ExtractorRegistry.cpp
 

diff  --git a/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp b/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
new file mode 100644
index 0000000000000..6f7de45e863d1
--- /dev/null
+++ b/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
@@ -0,0 +1,986 @@
+#include "clang/Analysis/Scalable/Serialization/JSONFormat.h"
+#include "clang/Analysis/Scalable/TUSummary/TUSummary.h"
+
+#include "llvm/ADT/StringExtras.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/FormatVariadic.h"
+#include "llvm/Support/JSON.h"
+#include "llvm/Support/MemoryBuffer.h"
+#include "llvm/Support/Path.h"
+#include "llvm/Support/Registry.h"
+
+using namespace clang::ssaf;
+
+using Array = llvm::json::Array;
+using Object = llvm::json::Object;
+using Value = llvm::json::Value;
+
+//----------------------------------------------------------------------------
+// ErrorBuilder - Fluent API for constructing contextual errors.
+//----------------------------------------------------------------------------
+
+namespace {
+
+class ErrorBuilder {
+private:
+  std::error_code Code;
+  std::vector<std::string> ContextStack;
+
+  // Private constructor - only accessible via static factories.
+  explicit ErrorBuilder(std::error_code EC) : Code(EC) {}
+
+  // Helper: Format message and add to context stack.
+  template <typename... Args>
+  void addFormattedContext(const char *Fmt, Args &&...ArgVals) {
+    std::string Message =
+        llvm::formatv(Fmt, std::forward<Args>(ArgVals)...).str();
+    ContextStack.push_back(std::move(Message));
+  }
+
+public:
+  // Static factory: Create new error from error code and formatted message.
+  template <typename... Args>
+  static ErrorBuilder create(std::error_code EC, const char *Fmt,
+                             Args &&...ArgVals) {
+    ErrorBuilder Builder(EC);
+    Builder.addFormattedContext(Fmt, std::forward<Args>(ArgVals)...);
+    return Builder;
+  }
+
+  // Convenience overload for std::errc.
+  template <typename... Args>
+  static ErrorBuilder create(std::errc EC, const char *Fmt, Args &&...ArgVals) {
+    return create(std::make_error_code(EC), Fmt,
+                  std::forward<Args>(ArgVals)...);
+  }
+
+  // Static factory: Wrap existing error and optionally add context.
+  static ErrorBuilder wrap(llvm::Error E) {
+    if (!E) {
+      llvm::consumeError(std::move(E));
+      // Return builder with generic error code for success case.
+      return ErrorBuilder(std::make_error_code(std::errc::invalid_argument));
+    }
+
+    std::error_code EC;
+    bool FirstError = true;
+    ErrorBuilder Builder(std::make_error_code(std::errc::invalid_argument));
+
+    llvm::handleAllErrors(std::move(E), [&](const llvm::ErrorInfoBase &EI) {
+      // Capture error code from the first error only.
+      if (FirstError) {
+        EC = EI.convertToErrorCode();
+        Builder.Code = EC;
+        FirstError = false;
+      }
+
+      // Collect messages from all errors.
+      std::string ErrorMsg = EI.message();
+      if (!ErrorMsg.empty()) {
+        Builder.ContextStack.push_back(std::move(ErrorMsg));
+      }
+    });
+
+    return Builder;
+  }
+
+  // Add context (plain string).
+  ErrorBuilder &context(const char *Msg) {
+    ContextStack.push_back(Msg);
+    return *this;
+  }
+
+  // Add context (formatted string).
+  template <typename... Args>
+  ErrorBuilder &context(const char *Fmt, Args &&...ArgVals) {
+    addFormattedContext(Fmt, std::forward<Args>(ArgVals)...);
+    return *this;
+  }
+
+  // Build the final error.
+  llvm::Error build() {
+    if (ContextStack.empty())
+      return llvm::Error::success();
+
+    // Reverse the context stack so that the most recent context appears first
+    // and the wrapped error (if any) appears last.
+    return llvm::createStringError(
+        llvm::join(llvm::reverse(ContextStack), "\n"), Code);
+  }
+};
+
+} // namespace
+
+//----------------------------------------------------------------------------
+// File Format Constant
+//----------------------------------------------------------------------------
+
+namespace {
+
+constexpr const char *JSONFormatFileExtension = ".json";
+
+}
+
+//----------------------------------------------------------------------------
+// Error Message Constants
+//----------------------------------------------------------------------------
+
+namespace {
+
+namespace ErrorMessages {
+
+constexpr const char *FailedToReadFile = "failed to read file '{0}': {1}";
+constexpr const char *FailedToWriteFile = "failed to write file '{0}': {1}";
+constexpr const char *FileNotFound = "file does not exist";
+constexpr const char *FileIsDirectory = "path is a directory, not a file";
+constexpr const char *FileIsNotJSON = "file does not end with '{0}' extension";
+constexpr const char *FileExists = "file already exists";
+constexpr const char *ParentDirectoryNotFound =
+    "parent directory does not exist";
+
+constexpr const char *ReadingFromField = "reading {0} from field '{1}'";
+constexpr const char *WritingToField = "writing {0} to field '{1}'";
+constexpr const char *ReadingFromIndex = "reading {0} from index '{1}'";
+constexpr const char *WritingToIndex = "writing {0} to index '{1}'";
+constexpr const char *ReadingFromFile = "reading {0} from file '{1}'";
+constexpr const char *WritingToFile = "writing {0} to file '{1}'";
+
+constexpr const char *FailedInsertionOnDuplication =
+    "failed to insert {0} at index '{1}': encountered duplicate {2} '{3}'";
+
+constexpr const char *FailedToReadObject =
+    "failed to read {0}: expected JSON {1}";
+constexpr const char *FailedToReadObjectAtField =
+    "failed to read {0} from field '{1}': expected JSON {2}";
+constexpr const char *FailedToReadObjectAtIndex =
+    "failed to read {0} from index '{1}': expected JSON {2}";
+
+constexpr const char *FailedToDeserializeEntitySummary =
+    "failed to deserialize EntitySummary: no FormatInfo registered for summary "
+    "'{0}'";
+constexpr const char *FailedToSerializeEntitySummary =
+    "failed to serialize EntitySummary: no FormatInfo registered for summary "
+    "'{0}'";
+
+constexpr const char *InvalidBuildNamespaceKind =
+    "invalid 'kind' BuildNamespaceKind value '{0}'";
+
+} // namespace ErrorMessages
+
+} // namespace
+
+//----------------------------------------------------------------------------
+// JSON Reader and Writer
+//----------------------------------------------------------------------------
+
+namespace {
+
+llvm::Expected<Value> readJSON(llvm::StringRef Path) {
+  if (!llvm::sys::fs::exists(Path)) {
+    return ErrorBuilder::create(std::errc::no_such_file_or_directory,
+                                ErrorMessages::FailedToReadFile, Path,
+                                ErrorMessages::FileNotFound)
+        .build();
+  }
+
+  if (llvm::sys::fs::is_directory(Path)) {
+    return ErrorBuilder::create(std::errc::is_a_directory,
+                                ErrorMessages::FailedToReadFile, Path,
+                                ErrorMessages::FileIsDirectory)
+        .build();
+  }
+
+  if (!Path.ends_with_insensitive(JSONFormatFileExtension)) {
+    return ErrorBuilder::create(std::errc::invalid_argument,
+                                ErrorMessages::FailedToReadFile, Path,
+                                llvm::formatv(ErrorMessages::FileIsNotJSON,
+                                              JSONFormatFileExtension))
+        .build();
+  }
+
+  auto BufferOrError = llvm::MemoryBuffer::getFile(Path);
+  if (!BufferOrError) {
+    const std::error_code EC = BufferOrError.getError();
+    return ErrorBuilder::create(EC, ErrorMessages::FailedToReadFile, Path,
+                                EC.message())
+        .build();
+  }
+
+  return llvm::json::parse(BufferOrError.get()->getBuffer());
+}
+
+llvm::Error writeJSON(Value &&Value, llvm::StringRef Path) {
+  if (llvm::sys::fs::exists(Path)) {
+    return ErrorBuilder::create(std::errc::file_exists,
+                                ErrorMessages::FailedToWriteFile, Path,
+                                ErrorMessages::FileExists)
+        .build();
+  }
+
+  llvm::StringRef Dir = llvm::sys::path::parent_path(Path);
+  if (!Dir.empty() && !llvm::sys::fs::is_directory(Dir)) {
+    return ErrorBuilder::create(std::errc::no_such_file_or_directory,
+                                ErrorMessages::FailedToWriteFile, Path,
+                                ErrorMessages::ParentDirectoryNotFound)
+        .build();
+  }
+
+  if (!Path.ends_with_insensitive(JSONFormatFileExtension)) {
+    return ErrorBuilder::create(std::errc::invalid_argument,
+                                ErrorMessages::FailedToWriteFile, Path,
+                                llvm::formatv(ErrorMessages::FileIsNotJSON,
+                                              JSONFormatFileExtension))
+        .build();
+  }
+
+  std::error_code EC;
+  llvm::raw_fd_ostream OutStream(Path, EC, llvm::sys::fs::OF_Text);
+
+  if (EC) {
+    return ErrorBuilder::create(EC, ErrorMessages::FailedToWriteFile, Path,
+                                EC.message())
+        .build();
+  }
+
+  OutStream << llvm::formatv("{0:2}\n", Value);
+  OutStream.flush();
+
+  if (OutStream.has_error()) {
+    return ErrorBuilder::create(OutStream.error(),
+                                ErrorMessages::FailedToWriteFile, Path,
+                                OutStream.error().message())
+        .build();
+  }
+
+  return llvm::Error::success();
+}
+
+} // namespace
+
+//----------------------------------------------------------------------------
+// JSONFormat Static Methods
+//----------------------------------------------------------------------------
+
+std::map<SummaryName, JSONFormat::FormatInfo> JSONFormat::initFormatInfos() {
+  std::map<SummaryName, FormatInfo> FormatInfos;
+  for (const auto &FormatInfoEntry : llvm::Registry<FormatInfo>::entries()) {
+    std::unique_ptr<FormatInfo> Info = FormatInfoEntry.instantiate();
+    bool Inserted = FormatInfos.try_emplace(Info->ForSummary, *Info).second;
+    if (!Inserted) {
+      llvm::report_fatal_error(
+          "FormatInfo is already registered for summary: " +
+          Info->ForSummary.str());
+    }
+  }
+  return FormatInfos;
+}
+
+//----------------------------------------------------------------------------
+// SummaryName
+//----------------------------------------------------------------------------
+
+namespace {
+
+SummaryName summaryNameFromJSON(llvm::StringRef SummaryNameStr) {
+  return SummaryName(SummaryNameStr.str());
+}
+
+llvm::StringRef summaryNameToJSON(const SummaryName &SN) { return SN.str(); }
+
+} // namespace
+
+//----------------------------------------------------------------------------
+// EntityId
+//----------------------------------------------------------------------------
+
+EntityId JSONFormat::entityIdFromJSON(const uint64_t EntityIdIndex) const {
+  return makeEntityId(static_cast<size_t>(EntityIdIndex));
+}
+
+uint64_t JSONFormat::entityIdToJSON(EntityId EI) const {
+  return static_cast<uint64_t>(getIndex(EI));
+}
+
+//----------------------------------------------------------------------------
+// BuildNamespaceKind
+//----------------------------------------------------------------------------
+
+llvm::Expected<BuildNamespaceKind> JSONFormat::buildNamespaceKindFromJSON(
+    llvm::StringRef BuildNamespaceKindStr) const {
+  auto OptBuildNamespaceKind = parseBuildNamespaceKind(BuildNamespaceKindStr);
+  if (!OptBuildNamespaceKind) {
+    return ErrorBuilder::create(std::errc::invalid_argument,
+                                ErrorMessages::InvalidBuildNamespaceKind,
+                                BuildNamespaceKindStr)
+        .build();
+  }
+
+  return *OptBuildNamespaceKind;
+}
+
+namespace {
+
+llvm::StringRef buildNamespaceKindToJSON(BuildNamespaceKind BNK) {
+  return toString(BNK);
+}
+
+} // namespace
+
+//----------------------------------------------------------------------------
+// BuildNamespace
+//----------------------------------------------------------------------------
+
+llvm::Expected<BuildNamespace>
+JSONFormat::buildNamespaceFromJSON(const Object &BuildNamespaceObject) const {
+  auto OptBuildNamespaceKindStr = BuildNamespaceObject.getString("kind");
+  if (!OptBuildNamespaceKindStr) {
+    return ErrorBuilder::create(std::errc::invalid_argument,
+                                ErrorMessages::FailedToReadObjectAtField,
+                                "BuildNamespaceKind", "kind", "string")
+        .build();
+  }
+
+  auto ExpectedKind = buildNamespaceKindFromJSON(*OptBuildNamespaceKindStr);
+  if (!ExpectedKind)
+    return ErrorBuilder::wrap(ExpectedKind.takeError())
+        .context(ErrorMessages::ReadingFromField, "BuildNamespaceKind", "kind")
+        .build();
+
+  auto OptNameStr = BuildNamespaceObject.getString("name");
+  if (!OptNameStr) {
+    return ErrorBuilder::create(std::errc::invalid_argument,
+                                ErrorMessages::FailedToReadObjectAtField,
+                                "BuildNamespaceName", "name", "string")
+        .build();
+  }
+
+  return {BuildNamespace(*ExpectedKind, *OptNameStr)};
+}
+
+Object JSONFormat::buildNamespaceToJSON(const BuildNamespace &BN) const {
+  Object Result;
+  Result["kind"] = buildNamespaceKindToJSON(getKind(BN));
+  Result["name"] = getName(BN);
+  return Result;
+}
+
+//----------------------------------------------------------------------------
+// NestedBuildNamespace
+//----------------------------------------------------------------------------
+
+llvm::Expected<NestedBuildNamespace> JSONFormat::nestedBuildNamespaceFromJSON(
+    const Array &NestedBuildNamespaceArray) const {
+  std::vector<BuildNamespace> Namespaces;
+
+  size_t NamespaceCount = NestedBuildNamespaceArray.size();
+  Namespaces.reserve(NamespaceCount);
+
+  for (const auto &[Index, BuildNamespaceValue] :
+       llvm::enumerate(NestedBuildNamespaceArray)) {
+
+    const Object *BuildNamespaceObject = BuildNamespaceValue.getAsObject();
+    if (!BuildNamespaceObject) {
+      return ErrorBuilder::create(std::errc::invalid_argument,
+                                  ErrorMessages::FailedToReadObjectAtIndex,
+                                  "BuildNamespace", Index, "object")
+          .build();
+    }
+
+    auto ExpectedBuildNamespace = buildNamespaceFromJSON(*BuildNamespaceObject);
+    if (!ExpectedBuildNamespace) {
+      return ErrorBuilder::wrap(ExpectedBuildNamespace.takeError())
+          .context(ErrorMessages::ReadingFromIndex, "BuildNamespace", Index)
+          .build();
+    }
+
+    Namespaces.push_back(std::move(*ExpectedBuildNamespace));
+  }
+
+  return NestedBuildNamespace(std::move(Namespaces));
+}
+
+Array JSONFormat::nestedBuildNamespaceToJSON(
+    const NestedBuildNamespace &NBN) const {
+  Array Result;
+  const auto &Namespaces = getNamespaces(NBN);
+  Result.reserve(Namespaces.size());
+
+  for (const auto &BN : Namespaces) {
+    Result.push_back(buildNamespaceToJSON(BN));
+  }
+
+  return Result;
+}
+
+//----------------------------------------------------------------------------
+// EntityName
+//----------------------------------------------------------------------------
+
+llvm::Expected<EntityName>
+JSONFormat::entityNameFromJSON(const Object &EntityNameObject) const {
+  const auto OptUSR = EntityNameObject.getString("usr");
+  if (!OptUSR) {
+    return ErrorBuilder::create(std::errc::invalid_argument,
+                                ErrorMessages::FailedToReadObjectAtField, "USR",
+                                "usr", "string")
+        .build();
+  }
+
+  const auto OptSuffix = EntityNameObject.getString("suffix");
+  if (!OptSuffix) {
+    return ErrorBuilder::create(std::errc::invalid_argument,
+                                ErrorMessages::FailedToReadObjectAtField,
+                                "Suffix", "suffix", "string")
+        .build();
+  }
+
+  const Array *OptNamespaceArray = EntityNameObject.getArray("namespace");
+  if (!OptNamespaceArray) {
+    return ErrorBuilder::create(std::errc::invalid_argument,
+                                ErrorMessages::FailedToReadObjectAtField,
+                                "NestedBuildNamespace", "namespace", "array")
+        .build();
+  }
+
+  auto ExpectedNamespace = nestedBuildNamespaceFromJSON(*OptNamespaceArray);
+  if (!ExpectedNamespace) {
+    return ErrorBuilder::wrap(ExpectedNamespace.takeError())
+        .context(ErrorMessages::ReadingFromField, "NestedBuildNamespace",
+                 "namespace")
+        .build();
+  }
+
+  return EntityName{*OptUSR, *OptSuffix, std::move(*ExpectedNamespace)};
+}
+
+Object JSONFormat::entityNameToJSON(const EntityName &EN) const {
+  Object Result;
+  Result["usr"] = getUSR(EN);
+  Result["suffix"] = getSuffix(EN);
+  Result["namespace"] = nestedBuildNamespaceToJSON(getNamespace(EN));
+  return Result;
+}
+
+//----------------------------------------------------------------------------
+// EntityIdTableEntry
+//----------------------------------------------------------------------------
+
+llvm::Expected<std::pair<EntityName, EntityId>>
+JSONFormat::entityIdTableEntryFromJSON(
+    const Object &EntityIdTableEntryObject) const {
+
+  const Object *OptEntityNameObject =
+      EntityIdTableEntryObject.getObject("name");
+  if (!OptEntityNameObject) {
+    return ErrorBuilder::create(std::errc::invalid_argument,
+                                ErrorMessages::FailedToReadObjectAtField,
+                                "EntityName", "name", "object")
+        .build();
+  }
+
+  auto ExpectedEntityName = entityNameFromJSON(*OptEntityNameObject);
+  if (!ExpectedEntityName) {
+    return ErrorBuilder::wrap(ExpectedEntityName.takeError())
+        .context(ErrorMessages::ReadingFromField, "EntityName", "name")
+        .build();
+  }
+
+  const Value *EntityIdIntValue = EntityIdTableEntryObject.get("id");
+  if (!EntityIdIntValue) {
+    return ErrorBuilder::create(std::errc::invalid_argument,
+                                ErrorMessages::FailedToReadObjectAtField,
+                                "EntityId", "id",
+                                "number (unsigned 64-bit integer)")
+        .build();
+  }
+
+  const std::optional<uint64_t> OptEntityIdInt =
+      EntityIdIntValue->getAsUINT64();
+  if (!OptEntityIdInt) {
+    return ErrorBuilder::create(std::errc::invalid_argument,
+                                ErrorMessages::FailedToReadObjectAtField,
+                                "EntityId", "id",
+                                "number (unsigned 64-bit integer)")
+        .build();
+  }
+
+  EntityId EI = entityIdFromJSON(*OptEntityIdInt);
+
+  return std::make_pair(std::move(*ExpectedEntityName), std::move(EI));
+}
+
+Object JSONFormat::entityIdTableEntryToJSON(const EntityName &EN,
+                                            EntityId EI) const {
+  Object Entry;
+  Entry["id"] = entityIdToJSON(EI);
+  Entry["name"] = entityNameToJSON(EN);
+  return Entry;
+}
+
+//----------------------------------------------------------------------------
+// EntityIdTable
+//----------------------------------------------------------------------------
+
+llvm::Expected<EntityIdTable>
+JSONFormat::entityIdTableFromJSON(const Array &EntityIdTableArray) const {
+  EntityIdTable IdTable;
+  std::map<EntityName, EntityId> &Entities = getEntities(IdTable);
+
+  for (const auto &[Index, EntityIdTableEntryValue] :
+       llvm::enumerate(EntityIdTableArray)) {
+
+    const Object *OptEntityIdTableEntryObject =
+        EntityIdTableEntryValue.getAsObject();
+    if (!OptEntityIdTableEntryObject) {
+      return ErrorBuilder::create(std::errc::invalid_argument,
+                                  ErrorMessages::FailedToReadObjectAtIndex,
+                                  "EntityIdTable entry", Index, "object")
+          .build();
+    }
+
+    auto ExpectedEntityIdTableEntry =
+        entityIdTableEntryFromJSON(*OptEntityIdTableEntryObject);
+    if (!ExpectedEntityIdTableEntry)
+      return ErrorBuilder::wrap(ExpectedEntityIdTableEntry.takeError())
+          .context(ErrorMessages::ReadingFromIndex, "EntityIdTable entry",
+                   Index)
+          .build();
+
+    auto [EntityIt, EntityInserted] =
+        Entities.emplace(std::move(*ExpectedEntityIdTableEntry));
+    if (!EntityInserted) {
+      return ErrorBuilder::create(std::errc::invalid_argument,
+                                  ErrorMessages::FailedInsertionOnDuplication,
+                                  "EntityIdTable entry", Index, "EntityId",
+                                  getIndex(EntityIt->second))
+          .build();
+    }
+  }
+
+  return IdTable;
+}
+
+Array JSONFormat::entityIdTableToJSON(const EntityIdTable &IdTable) const {
+  Array EntityIdTableArray;
+  const auto &Entities = getEntities(IdTable);
+  EntityIdTableArray.reserve(Entities.size());
+
+  for (const auto &[EntityName, EntityId] : Entities) {
+    EntityIdTableArray.push_back(
+        entityIdTableEntryToJSON(EntityName, EntityId));
+  }
+
+  return EntityIdTableArray;
+}
+
+//----------------------------------------------------------------------------
+// EntitySummary
+//----------------------------------------------------------------------------
+
+llvm::Expected<std::unique_ptr<EntitySummary>>
+JSONFormat::entitySummaryFromJSON(const SummaryName &SN,
+                                  const Object &EntitySummaryObject,
+                                  EntityIdTable &IdTable) const {
+  auto InfoIt = FormatInfos.find(SN);
+  if (InfoIt == FormatInfos.end()) {
+    return ErrorBuilder::create(std::errc::invalid_argument,
+                                ErrorMessages::FailedToDeserializeEntitySummary,
+                                SN.str())
+        .build();
+  }
+
+  const auto &InfoEntry = InfoIt->second;
+  assert(InfoEntry.ForSummary == SN);
+
+  EntityIdConverter Converter(*this);
+  return InfoEntry.Deserialize(EntitySummaryObject, IdTable, Converter);
+}
+
+llvm::Expected<Object>
+JSONFormat::entitySummaryToJSON(const SummaryName &SN,
+                                const EntitySummary &ES) const {
+  auto InfoIt = FormatInfos.find(SN);
+  if (InfoIt == FormatInfos.end()) {
+    return ErrorBuilder::create(std::errc::invalid_argument,
+                                ErrorMessages::FailedToSerializeEntitySummary,
+                                SN.str())
+        .build();
+  }
+
+  const auto &InfoEntry = InfoIt->second;
+  assert(InfoEntry.ForSummary == SN);
+
+  EntityIdConverter Converter(*this);
+  return InfoEntry.Serialize(ES, Converter);
+}
+
+//----------------------------------------------------------------------------
+// EntityDataMapEntry
+//----------------------------------------------------------------------------
+
+llvm::Expected<std::pair<EntityId, std::unique_ptr<EntitySummary>>>
+JSONFormat::entityDataMapEntryFromJSON(const Object &EntityDataMapEntryObject,
+                                       const SummaryName &SN,
+                                       EntityIdTable &IdTable) const {
+
+  const Value *EntityIdIntValue = EntityDataMapEntryObject.get("entity_id");
+  if (!EntityIdIntValue) {
+    return ErrorBuilder::create(std::errc::invalid_argument,
+                                ErrorMessages::FailedToReadObjectAtField,
+                                "EntityId", "entity_id",
+                                "number (unsigned 64-bit integer)")
+        .build();
+  }
+
+  const std::optional<uint64_t> OptEntityIdInt =
+      EntityIdIntValue->getAsUINT64();
+  if (!OptEntityIdInt) {
+    return ErrorBuilder::create(std::errc::invalid_argument,
+                                ErrorMessages::FailedToReadObjectAtField,
+                                "EntityId", "entity_id", "integer")
+        .build();
+  }
+
+  EntityId EI = entityIdFromJSON(*OptEntityIdInt);
+
+  const Object *OptEntitySummaryObject =
+      EntityDataMapEntryObject.getObject("entity_summary");
+  if (!OptEntitySummaryObject) {
+    return ErrorBuilder::create(std::errc::invalid_argument,
+                                ErrorMessages::FailedToReadObjectAtField,
+                                "EntitySummary", "entity_summary", "object")
+        .build();
+  }
+
+  auto ExpectedEntitySummary =
+      entitySummaryFromJSON(SN, *OptEntitySummaryObject, IdTable);
+  if (!ExpectedEntitySummary) {
+    return ErrorBuilder::wrap(ExpectedEntitySummary.takeError())
+        .context(ErrorMessages::ReadingFromField, "EntitySummary",
+                 "entity_summary")
+        .build();
+  }
+
+  return std::make_pair(std::move(EI), std::move(*ExpectedEntitySummary));
+}
+
+//----------------------------------------------------------------------------
+// EntityDataMap
+//----------------------------------------------------------------------------
+
+llvm::Expected<std::map<EntityId, std::unique_ptr<EntitySummary>>>
+JSONFormat::entityDataMapFromJSON(const SummaryName &SN,
+                                  const Array &EntityDataArray,
+                                  EntityIdTable &IdTable) const {
+  std::map<EntityId, std::unique_ptr<EntitySummary>> EntityDataMap;
+
+  for (const auto &[Index, EntityDataMapEntryValue] :
+       llvm::enumerate(EntityDataArray)) {
+
+    const Object *OptEntityDataMapEntryObject =
+        EntityDataMapEntryValue.getAsObject();
+    if (!OptEntityDataMapEntryObject) {
+      return ErrorBuilder::create(std::errc::invalid_argument,
+                                  ErrorMessages::FailedToReadObjectAtIndex,
+                                  "EntitySummary entry", Index, "object")
+          .build();
+    }
+
+    auto ExpectedEntityDataMapEntry =
+        entityDataMapEntryFromJSON(*OptEntityDataMapEntryObject, SN, IdTable);
+    if (!ExpectedEntityDataMapEntry)
+      return ErrorBuilder::wrap(ExpectedEntityDataMapEntry.takeError())
+          .context(ErrorMessages::ReadingFromIndex, "EntitySummary entry",
+                   Index)
+          .build();
+
+    auto [DataIt, DataInserted] =
+        EntityDataMap.insert(std::move(*ExpectedEntityDataMapEntry));
+    if (!DataInserted) {
+      return ErrorBuilder::create(std::errc::invalid_argument,
+                                  ErrorMessages::FailedInsertionOnDuplication,
+                                  "EntitySummary entry", Index, "EntityId",
+                                  getIndex(DataIt->first))
+          .build();
+    }
+  }
+
+  return EntityDataMap;
+}
+
+llvm::Expected<Array> JSONFormat::entityDataMapToJSON(
+    const SummaryName &SN,
+    const std::map<EntityId, std::unique_ptr<EntitySummary>> &EntityDataMap)
+    const {
+  Array Result;
+  Result.reserve(EntityDataMap.size());
+
+  for (const auto &[Index, EntityDataMapEntry] :
+       llvm::enumerate(EntityDataMap)) {
+    const auto &[EntityId, EntitySummary] = EntityDataMapEntry;
+
+    Object Entry;
+
+    Entry["entity_id"] = entityIdToJSON(EntityId);
+
+    auto ExpectedEntitySummaryObject = entitySummaryToJSON(SN, *EntitySummary);
+    if (!ExpectedEntitySummaryObject) {
+      return ErrorBuilder::wrap(ExpectedEntitySummaryObject.takeError())
+          .context(ErrorMessages::WritingToIndex, "EntitySummary entry", Index)
+          .build();
+    }
+
+    Entry["entity_summary"] = std::move(*ExpectedEntitySummaryObject);
+
+    Result.push_back(std::move(Entry));
+  }
+
+  return Result;
+}
+
+//----------------------------------------------------------------------------
+// SummaryDataMapEntry
+//----------------------------------------------------------------------------
+
+llvm::Expected<
+    std::pair<SummaryName, std::map<EntityId, std::unique_ptr<EntitySummary>>>>
+JSONFormat::summaryDataMapEntryFromJSON(const Object &SummaryDataMapEntryObject,
+                                        EntityIdTable &IdTable) const {
+
+  std::optional<llvm::StringRef> OptSummaryNameStr =
+      SummaryDataMapEntryObject.getString("summary_name");
+  if (!OptSummaryNameStr) {
+    return ErrorBuilder::create(std::errc::invalid_argument,
+                                ErrorMessages::FailedToReadObjectAtField,
+                                "SummaryName", "summary_name", "string")
+        .build();
+  }
+
+  SummaryName SN = summaryNameFromJSON(*OptSummaryNameStr);
+
+  const Array *OptEntityDataArray =
+      SummaryDataMapEntryObject.getArray("summary_data");
+  if (!OptEntityDataArray) {
+    return ErrorBuilder::create(std::errc::invalid_argument,
+                                ErrorMessages::FailedToReadObjectAtField,
+                                "EntitySummary entries", "summary_data",
+                                "array")
+        .build();
+  }
+
+  auto ExpectedEntityDataMap =
+      entityDataMapFromJSON(SN, *OptEntityDataArray, IdTable);
+  if (!ExpectedEntityDataMap)
+    return ErrorBuilder::wrap(ExpectedEntityDataMap.takeError())
+        .context(ErrorMessages::ReadingFromField, "EntitySummary entries",
+                 "summary_data")
+        .build();
+
+  return std::make_pair(std::move(SN), std::move(*ExpectedEntityDataMap));
+}
+
+llvm::Expected<Object> JSONFormat::summaryDataMapEntryToJSON(
+    const SummaryName &SN,
+    const std::map<EntityId, std::unique_ptr<EntitySummary>> &SD) const {
+  Object Result;
+
+  Result["summary_name"] = summaryNameToJSON(SN);
+
+  auto ExpectedSummaryDataArray = entityDataMapToJSON(SN, SD);
+  if (!ExpectedSummaryDataArray) {
+    return ErrorBuilder::wrap(ExpectedSummaryDataArray.takeError())
+        .context(ErrorMessages::WritingToField, "EntitySummary entries",
+                 "summary_data")
+        .build();
+  }
+
+  Result["summary_data"] = std::move(*ExpectedSummaryDataArray);
+
+  return Result;
+}
+
+//----------------------------------------------------------------------------
+// SummaryDataMap
+//----------------------------------------------------------------------------
+
+llvm::Expected<
+    std::map<SummaryName, std::map<EntityId, std::unique_ptr<EntitySummary>>>>
+JSONFormat::summaryDataMapFromJSON(const Array &SummaryDataArray,
+                                   EntityIdTable &IdTable) const {
+  std::map<SummaryName, std::map<EntityId, std::unique_ptr<EntitySummary>>>
+      SummaryDataMap;
+
+  for (const auto &[Index, SummaryDataMapEntryValue] :
+       llvm::enumerate(SummaryDataArray)) {
+
+    const Object *OptSummaryDataMapEntryObject =
+        SummaryDataMapEntryValue.getAsObject();
+    if (!OptSummaryDataMapEntryObject) {
+      return ErrorBuilder::create(std::errc::invalid_argument,
+                                  ErrorMessages::FailedToReadObjectAtIndex,
+                                  "SummaryData entry", Index, "object")
+          .build();
+    }
+
+    auto ExpectedSummaryDataMapEntry =
+        summaryDataMapEntryFromJSON(*OptSummaryDataMapEntryObject, IdTable);
+    if (!ExpectedSummaryDataMapEntry) {
+      return ErrorBuilder::wrap(ExpectedSummaryDataMapEntry.takeError())
+          .context(ErrorMessages::ReadingFromIndex, "SummaryData entry", Index)
+          .build();
+    }
+
+    auto [SummaryIt, SummaryInserted] =
+        SummaryDataMap.emplace(std::move(*ExpectedSummaryDataMapEntry));
+    if (!SummaryInserted) {
+      return ErrorBuilder::create(std::errc::invalid_argument,
+                                  ErrorMessages::FailedInsertionOnDuplication,
+                                  "SummaryData entry", Index, "SummaryName",
+                                  SummaryIt->first.str())
+          .build();
+    }
+  }
+
+  return SummaryDataMap;
+}
+
+llvm::Expected<Array> JSONFormat::summaryDataMapToJSON(
+    const std::map<SummaryName,
+                   std::map<EntityId, std::unique_ptr<EntitySummary>>>
+        &SummaryDataMap) const {
+  Array Result;
+  Result.reserve(SummaryDataMap.size());
+
+  for (const auto &[Index, SummaryDataMapEntry] :
+       llvm::enumerate(SummaryDataMap)) {
+    const auto &[SummaryName, DataMap] = SummaryDataMapEntry;
+
+    auto ExpectedSummaryDataMapObject =
+        summaryDataMapEntryToJSON(SummaryName, DataMap);
+    if (!ExpectedSummaryDataMapObject) {
+      return ErrorBuilder::wrap(ExpectedSummaryDataMapObject.takeError())
+          .context(ErrorMessages::WritingToIndex, "SummaryData entry", Index)
+          .build();
+    }
+
+    Result.push_back(std::move(*ExpectedSummaryDataMapObject));
+  }
+
+  return Result;
+}
+
+//----------------------------------------------------------------------------
+// TUSummary
+//----------------------------------------------------------------------------
+
+llvm::Expected<TUSummary> JSONFormat::readTUSummary(llvm::StringRef Path) {
+  auto ExpectedJSON = readJSON(Path);
+  if (!ExpectedJSON) {
+    return ErrorBuilder::wrap(ExpectedJSON.takeError())
+        .context(ErrorMessages::ReadingFromFile, "TUSummary", Path)
+        .build();
+  }
+
+  Object *RootObjectPtr = ExpectedJSON->getAsObject();
+  if (!RootObjectPtr) {
+    return ErrorBuilder::create(std::errc::invalid_argument,
+                                ErrorMessages::FailedToReadObject, "TUSummary",
+                                "object")
+        .context(ErrorMessages::ReadingFromFile, "TUSummary", Path)
+        .build();
+  }
+
+  const Object &RootObject = *RootObjectPtr;
+
+  const Object *TUNamespaceObject = RootObject.getObject("tu_namespace");
+  if (!TUNamespaceObject) {
+    return ErrorBuilder::create(std::errc::invalid_argument,
+                                ErrorMessages::FailedToReadObjectAtField,
+                                "BuildNamespace", "tu_namespace", "object")
+        .context(ErrorMessages::ReadingFromFile, "TUSummary", Path)
+        .build();
+  }
+
+  auto ExpectedTUNamespace = buildNamespaceFromJSON(*TUNamespaceObject);
+  if (!ExpectedTUNamespace) {
+    return ErrorBuilder::wrap(ExpectedTUNamespace.takeError())
+        .context(ErrorMessages::ReadingFromField, "BuildNamespace",
+                 "tu_namespace")
+        .context(ErrorMessages::ReadingFromFile, "TUSummary", Path)
+        .build();
+  }
+
+  TUSummary Summary(std::move(*ExpectedTUNamespace));
+
+  {
+    const Array *IdTableArray = RootObject.getArray("id_table");
+    if (!IdTableArray) {
+      return ErrorBuilder::create(std::errc::invalid_argument,
+                                  ErrorMessages::FailedToReadObjectAtField,
+                                  "IdTable", "id_table", "array")
+          .context(ErrorMessages::ReadingFromFile, "TUSummary", Path)
+          .build();
+    }
+
+    auto ExpectedIdTable = entityIdTableFromJSON(*IdTableArray);
+    if (!ExpectedIdTable) {
+      return ErrorBuilder::wrap(ExpectedIdTable.takeError())
+          .context(ErrorMessages::ReadingFromField, "IdTable", "id_table")
+          .context(ErrorMessages::ReadingFromFile, "TUSummary", Path)
+          .build();
+    }
+
+    getIdTable(Summary) = std::move(*ExpectedIdTable);
+  }
+
+  {
+    const Array *SummaryDataArray = RootObject.getArray("data");
+    if (!SummaryDataArray) {
+      return ErrorBuilder::create(std::errc::invalid_argument,
+                                  ErrorMessages::FailedToReadObjectAtField,
+                                  "SummaryData entries", "data", "array")
+          .context(ErrorMessages::ReadingFromFile, "TUSummary", Path)
+          .build();
+    }
+
+    auto ExpectedSummaryDataMap =
+        summaryDataMapFromJSON(*SummaryDataArray, getIdTable(Summary));
+    if (!ExpectedSummaryDataMap) {
+      return ErrorBuilder::wrap(ExpectedSummaryDataMap.takeError())
+          .context(ErrorMessages::ReadingFromField, "SummaryData entries",
+                   "data")
+          .context(ErrorMessages::ReadingFromFile, "TUSummary", Path)
+          .build();
+    }
+
+    getData(Summary) = std::move(*ExpectedSummaryDataMap);
+  }
+
+  return Summary;
+}
+
+llvm::Error JSONFormat::writeTUSummary(const TUSummary &S,
+                                       llvm::StringRef Path) {
+  Object RootObject;
+
+  RootObject["tu_namespace"] = buildNamespaceToJSON(getTUNamespace(S));
+
+  RootObject["id_table"] = entityIdTableToJSON(getIdTable(S));
+
+  auto ExpectedDataObject = summaryDataMapToJSON(getData(S));
+  if (!ExpectedDataObject) {
+    return ErrorBuilder::wrap(ExpectedDataObject.takeError())
+        .context(ErrorMessages::WritingToFile, "TUSummary", Path)
+        .build();
+  }
+
+  RootObject["data"] = std::move(*ExpectedDataObject);
+
+  if (auto Error = writeJSON(std::move(RootObject), Path)) {
+    return ErrorBuilder::wrap(std::move(Error))
+        .context(ErrorMessages::WritingToFile, "TUSummary", Path)
+        .build();
+  }
+
+  return llvm::Error::success();
+}

diff  --git a/clang/unittests/Analysis/Scalable/CMakeLists.txt b/clang/unittests/Analysis/Scalable/CMakeLists.txt
index 601845b4ab77a..b3920c2d902ad 100644
--- a/clang/unittests/Analysis/Scalable/CMakeLists.txt
+++ b/clang/unittests/Analysis/Scalable/CMakeLists.txt
@@ -10,6 +10,7 @@ add_distinct_clang_unittest(ClangScalableAnalysisTests
   Registries/MockSummaryExtractor2.cpp
   Registries/SerializationFormatRegistryTest.cpp
   Registries/SummaryExtractorRegistryTest.cpp
+  Serialization/JSONFormatTest.cpp
   SummaryNameTest.cpp
 
   CLANG_LIBS

diff  --git a/clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.cpp b/clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.cpp
index a6d4b51178c94..e42dc2cb4408d 100644
--- a/clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.cpp
+++ b/clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.cpp
@@ -39,12 +39,16 @@ MockSerializationFormat::MockSerializationFormat() {
   }
 }
 
-TUSummary MockSerializationFormat::readTUSummary(llvm::StringRef Path) {
+llvm::Expected<TUSummary>
+MockSerializationFormat::readTUSummary(llvm::StringRef Path) {
   BuildNamespace NS(BuildNamespaceKind::CompilationUnit, "Mock.cpp");
   TUSummary Summary(NS);
 
   auto ManifestFile = llvm::MemoryBuffer::getFile(Path + "/analyses.txt");
-  assert(ManifestFile); // TODO Handle error.
+  if (!ManifestFile) {
+    return llvm::createStringError(ManifestFile.getError(),
+                                   "Failed to read manifest file");
+  }
   llvm::StringRef ManifestFileContent = (*ManifestFile)->getBuffer();
 
   llvm::SmallVector<llvm::StringRef, 5> Analyses;
@@ -55,10 +59,14 @@ TUSummary MockSerializationFormat::readTUSummary(llvm::StringRef Path) {
     SummaryName Name(Analysis.str());
     auto InputFile =
         llvm::MemoryBuffer::getFile(Path + "/" + Name.str() + ".special");
-    assert(InputFile);
+    if (!InputFile) {
+      return llvm::createStringError(InputFile.getError(),
+                                     "Failed to read analysis file");
+    }
     auto InfoIt = FormatInfos.find(Name);
     if (InfoIt == FormatInfos.end()) {
-      llvm::report_fatal_error(
+      return llvm::createStringError(
+          std::make_error_code(std::errc::invalid_argument),
           "No FormatInfo was registered for summary name: " + Name.str());
     }
     const auto &InfoEntry = InfoIt->second;
@@ -68,8 +76,11 @@ TUSummary MockSerializationFormat::readTUSummary(llvm::StringRef Path) {
     auto &Table = getIdTable(Summary);
 
     std::unique_ptr<EntitySummary> Result = InfoEntry.Deserialize(Repr, Table);
-    if (!Result) // TODO: Handle error.
-      continue;
+    if (!Result) {
+      return llvm::createStringError(
+          std::make_error_code(std::errc::invalid_argument),
+          "Failed to deserialize EntitySummary for analysis: " + Name.str());
+    }
 
     EntityId FooId = Table.getId(EntityName{"c:@F at foo", "", /*Namespace=*/{}});
     auto &IdMappings = getData(Summary).try_emplace(Name).first->second;
@@ -81,16 +92,16 @@ TUSummary MockSerializationFormat::readTUSummary(llvm::StringRef Path) {
   return Summary;
 }
 
-void MockSerializationFormat::writeTUSummary(const TUSummary &Summary,
-                                             llvm::StringRef OutputDir) {
+llvm::Error MockSerializationFormat::writeTUSummary(const TUSummary &Summary,
+                                                    llvm::StringRef Path) {
   std::error_code EC;
 
   // Check if output directory exists, create if needed
-  if (!llvm::sys::fs::exists(OutputDir)) {
-    EC = llvm::sys::fs::create_directories(OutputDir);
+  if (!llvm::sys::fs::exists(Path)) {
+    EC = llvm::sys::fs::create_directories(Path);
     if (EC) {
-      llvm::report_fatal_error("Failed to create output directory '" +
-                               OutputDir + "': " + EC.message());
+      return llvm::createStringError(EC, "Failed to create output directory '" +
+                                             Path + "': " + EC.message());
     }
   }
 
@@ -101,9 +112,10 @@ void MockSerializationFormat::writeTUSummary(const TUSummary &Summary,
     for (const auto &Data : llvm::make_second_range(EntityMappings)) {
       auto InfoIt = FormatInfos.find(SummaryName);
       if (InfoIt == FormatInfos.end()) {
-        llvm::report_fatal_error(
+        return llvm::createStringError(
+            std::make_error_code(std::errc::invalid_argument),
             "There was no FormatInfo registered for summary name '" +
-            SummaryName.str() + "'");
+                SummaryName.str() + "'");
       }
       const auto &InfoEntry = InfoIt->second;
       assert(InfoEntry.ForSummary == SummaryName);
@@ -111,27 +123,30 @@ void MockSerializationFormat::writeTUSummary(const TUSummary &Summary,
       auto Output = InfoEntry.Serialize(*Data, *this);
 
       std::string AnalysisFilePath =
-          (OutputDir + "/" + SummaryName.str() + ".special").str();
+          (Path + "/" + SummaryName.str() + ".special").str();
       llvm::raw_fd_ostream AnalysisOutputFile(AnalysisFilePath, EC);
       if (EC) {
-        llvm::report_fatal_error("Failed to create file '" + AnalysisFilePath +
-                                 "': " + llvm::StringRef(EC.message()));
+        return llvm::createStringError(
+            EC, "Failed to create file '" + AnalysisFilePath +
+                    "': " + llvm::StringRef(EC.message()));
       }
       AnalysisOutputFile << Output.MockRepresentation;
     }
   }
 
-  std::string ManifestFilePath = (OutputDir + "/analyses.txt").str();
+  std::string ManifestFilePath = (Path + "/analyses.txt").str();
   llvm::raw_fd_ostream ManifestFile(ManifestFilePath, EC);
   if (EC) {
-    llvm::report_fatal_error("Failed to create manifest file '" +
-                             ManifestFilePath +
-                             "': " + llvm::StringRef(EC.message()));
+    return llvm::createStringError(
+        EC, "Failed to create manifest file '" + ManifestFilePath +
+                "': " + llvm::StringRef(EC.message()));
   }
 
   interleave(map_range(Analyses, std::mem_fn(&SummaryName::str)), ManifestFile,
              "\n");
   ManifestFile << "\n";
+
+  return llvm::Error::success();
 }
 
 static SerializationFormatRegistry::Add<MockSerializationFormat>

diff  --git a/clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.h b/clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.h
index 1bc37840201ea..31aa8211a2ac3 100644
--- a/clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.h
+++ b/clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.h
@@ -20,10 +20,10 @@ class MockSerializationFormat final : public SerializationFormat {
 public:
   MockSerializationFormat();
 
-  TUSummary readTUSummary(llvm::StringRef Path) override;
+  llvm::Expected<TUSummary> readTUSummary(llvm::StringRef Path) override;
 
-  void writeTUSummary(const TUSummary &Summary,
-                      llvm::StringRef OutputDir) override;
+  llvm::Error writeTUSummary(const TUSummary &Summary,
+                             llvm::StringRef Path) override;
 
   struct SpecialFileRepresentation {
     std::string MockRepresentation;

diff  --git a/clang/unittests/Analysis/Scalable/Registries/SerializationFormatRegistryTest.cpp b/clang/unittests/Analysis/Scalable/Registries/SerializationFormatRegistryTest.cpp
index 7f17cdda5a082..8b6d1a5ae15cf 100644
--- a/clang/unittests/Analysis/Scalable/Registries/SerializationFormatRegistryTest.cpp
+++ b/clang/unittests/Analysis/Scalable/Registries/SerializationFormatRegistryTest.cpp
@@ -14,6 +14,7 @@
 #include "llvm/Support/MemoryBuffer.h"
 #include "llvm/Support/Path.h"
 #include "llvm/Support/raw_ostream.h"
+#include "llvm/Testing/Support/Error.h"
 #include "gtest/gtest.h"
 #include <memory>
 
@@ -87,7 +88,9 @@ TEST(SerializationFormatRegistryTest, Roundtrip) {
       makeFormat("MockSerializationFormat");
   ASSERT_TRUE(Format);
 
-  TUSummary LoadedSummary = Format->readTUSummary(InputDir);
+  auto LoadedSummaryOrErr = Format->readTUSummary(InputDir);
+  ASSERT_THAT_EXPECTED(LoadedSummaryOrErr, Succeeded());
+  TUSummary LoadedSummary = std::move(*LoadedSummaryOrErr);
 
   // Create a temporary output directory
   SmallString<128> OutputDir;
@@ -96,7 +99,8 @@ TEST(SerializationFormatRegistryTest, Roundtrip) {
   llvm::scope_exit CleanupOnExit(
       [&] { sys::fs::remove_directories(OutputDir); });
 
-  Format->writeTUSummary(LoadedSummary, OutputDir);
+  auto WriteErr = Format->writeTUSummary(LoadedSummary, OutputDir);
+  ASSERT_THAT_ERROR(std::move(WriteErr), Succeeded());
 
   EXPECT_EQ(readFilesFromDir(OutputDir),
             (std::map<std::string, std::string>{

diff  --git a/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest.cpp b/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest.cpp
new file mode 100644
index 0000000000000..4d28e215c6298
--- /dev/null
+++ b/clang/unittests/Analysis/Scalable/Serialization/JSONFormatTest.cpp
@@ -0,0 +1,1847 @@
+//===- unittests/Analysis/Scalable/JSONFormatTest.cpp --------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// Unit tests for SSAF JSON serialization format reading and writing.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/Analysis/Scalable/Serialization/JSONFormat.h"
+#include "clang/Analysis/Scalable/TUSummary/TUSummary.h"
+#include "llvm/ADT/SmallString.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/MemoryBuffer.h"
+#include "llvm/Support/Path.h"
+#include "llvm/Support/Registry.h"
+#include "llvm/Support/raw_ostream.h"
+#include "llvm/Testing/Support/Error.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include <algorithm>
+#include <memory>
+#include <string>
+#include <vector>
+
+using namespace clang::ssaf;
+using namespace llvm;
+using ::testing::AllOf;
+using ::testing::HasSubstr;
+
+namespace {
+
+// ============================================================================
+// Test Analysis - Simple analysis for testing JSON serialization.
+// ============================================================================
+
+struct PairsEntitySummaryForJSONFormatTest final : EntitySummary {
+
+  SummaryName getSummaryName() const override {
+    return SummaryName("PairsEntitySummaryForJSONFormatTest");
+  }
+
+  std::vector<std::pair<EntityId, EntityId>> Pairs;
+};
+
+static json::Object serializePairsEntitySummaryForJSONFormatTest(
+    const EntitySummary &Summary,
+    const JSONFormat::EntityIdConverter &Converter) {
+  const auto &TA =
+      static_cast<const PairsEntitySummaryForJSONFormatTest &>(Summary);
+  json::Array PairsArray;
+  for (const auto &[First, Second] : TA.Pairs) {
+    PairsArray.push_back(json::Object{
+        {"first", Converter.toJSON(First)},
+        {"second", Converter.toJSON(Second)},
+    });
+  }
+  return json::Object{{"pairs", std::move(PairsArray)}};
+}
+
+static Expected<std::unique_ptr<EntitySummary>>
+deserializePairsEntitySummaryForJSONFormatTest(
+    const json::Object &Obj, EntityIdTable &IdTable,
+    const JSONFormat::EntityIdConverter &Converter) {
+  auto Result = std::make_unique<PairsEntitySummaryForJSONFormatTest>();
+  const json::Array *PairsArray = Obj.getArray("pairs");
+  if (!PairsArray)
+    return createStringError(inconvertibleErrorCode(),
+                             "missing or invalid field 'pairs'");
+  for (const auto &[Index, Value] : llvm::enumerate(*PairsArray)) {
+    const json::Object *Pair = Value.getAsObject();
+    if (!Pair)
+      return createStringError(
+          inconvertibleErrorCode(),
+          "pairs element at index %zu is not a JSON object", Index);
+    auto FirstOpt = Pair->getInteger("first");
+    if (!FirstOpt)
+      return createStringError(
+          inconvertibleErrorCode(),
+          "missing or invalid 'first' field at index '%zu'", Index);
+    auto SecondOpt = Pair->getInteger("second");
+    if (!SecondOpt)
+      return createStringError(
+          inconvertibleErrorCode(),
+          "missing or invalid 'second' field at index '%zu'", Index);
+    Result->Pairs.emplace_back(Converter.fromJSON(*FirstOpt),
+                               Converter.fromJSON(*SecondOpt));
+  }
+  return std::move(Result);
+}
+
+struct PairsEntitySummaryForJSONFormatTestFormatInfo : JSONFormat::FormatInfo {
+  PairsEntitySummaryForJSONFormatTestFormatInfo()
+      : JSONFormat::FormatInfo(
+            SummaryName("PairsEntitySummaryForJSONFormatTest"),
+            serializePairsEntitySummaryForJSONFormatTest,
+            deserializePairsEntitySummaryForJSONFormatTest) {}
+};
+
+static llvm::Registry<JSONFormat::FormatInfo>::Add<
+    PairsEntitySummaryForJSONFormatTestFormatInfo>
+    RegisterPairsEntitySummaryForJSONFormatTest(
+        "PairsEntitySummaryForJSONFormatTest",
+        "Format info for PairsArrayEntitySummary");
+
+// ============================================================================
+// Test Fixture
+// ============================================================================
+
+class JSONFormatTest : public ::testing::Test {
+public:
+  using PathString = SmallString<128>;
+
+protected:
+  SmallString<128> TestDir;
+
+  void SetUp() override {
+    std::error_code EC =
+        sys::fs::createUniqueDirectory("json-format-test", TestDir);
+    ASSERT_FALSE(EC) << "Failed to create temp directory: " << EC.message();
+  }
+
+  void TearDown() override { sys::fs::remove_directories(TestDir); }
+
+  PathString makePath(StringRef FileOrDirectoryName) const {
+    PathString FullPath = TestDir;
+    sys::path::append(FullPath, FileOrDirectoryName);
+
+    return FullPath;
+  }
+
+  PathString makePath(StringRef Dir, StringRef FileName) const {
+    PathString FullPath = TestDir;
+    sys::path::append(FullPath, Dir, FileName);
+
+    return FullPath;
+  }
+
+  Expected<PathString> makeDirectory(StringRef DirectoryName) const {
+    PathString DirPath = makePath(DirectoryName);
+
+    std::error_code EC = sys::fs::create_directory(DirPath);
+    if (EC) {
+      return createStringError(EC, "Failed to create directory '%s': %s",
+                               DirPath.c_str(), EC.message().c_str());
+    }
+
+    return DirPath;
+  }
+
+  Expected<PathString> makeSymlink(StringRef TargetFileName,
+                                   StringRef SymlinkFileName) const {
+    PathString TargetPath = makePath(TargetFileName);
+    PathString SymlinkPath = makePath(SymlinkFileName);
+
+    std::error_code EC = sys::fs::create_link(TargetPath, SymlinkPath);
+    if (EC) {
+      return createStringError(EC, "Failed to create symlink '%s' -> '%s': %s",
+                               SymlinkPath.c_str(), TargetPath.c_str(),
+                               EC.message().c_str());
+    }
+
+    return SymlinkPath;
+  }
+
+  llvm::Error setPermission(StringRef FileName,
+                            const sys::fs::perms Perms) const {
+    PathString Path = makePath(FileName);
+
+    std::error_code EC = sys::fs::setPermissions(Path, Perms);
+    if (EC) {
+      return createStringError(EC, "Failed to set permissions on '%s': %s",
+                               Path.c_str(), EC.message().c_str());
+    }
+
+    return llvm::Error::success();
+  }
+
+  Expected<json::Value> readJSONFromFile(StringRef FileName) const {
+    PathString FilePath = makePath(FileName);
+
+    auto BufferOrError = MemoryBuffer::getFile(FilePath);
+    if (!BufferOrError) {
+      return createStringError(BufferOrError.getError(),
+                               "Failed to read file: %s", FilePath.c_str());
+    }
+
+    Expected<json::Value> ExpectedValue =
+        json::parse(BufferOrError.get()->getBuffer());
+    if (!ExpectedValue)
+      return ExpectedValue.takeError();
+
+    return *ExpectedValue;
+  }
+
+  Expected<PathString> writeJSON(StringRef JSON, StringRef FileName) const {
+    PathString FilePath = makePath(FileName);
+
+    std::error_code EC;
+    llvm::raw_fd_ostream OS(FilePath, EC);
+    if (EC) {
+      return createStringError(EC, "Failed to create file '%s': %s",
+                               FilePath.c_str(), EC.message().c_str());
+    }
+
+    OS << JSON;
+    OS.close();
+
+    if (OS.has_error()) {
+      return createStringError(OS.error(), "Failed to write to file '%s': %s",
+                               FilePath.c_str(), OS.error().message().c_str());
+    }
+
+    return FilePath;
+  }
+
+  llvm::Expected<TUSummary> readTUSummaryFromFile(StringRef FileName) const {
+    PathString FilePath = makePath(FileName);
+
+    return JSONFormat().readTUSummary(FilePath);
+  }
+
+  llvm::Expected<TUSummary>
+  readTUSummaryFromString(StringRef JSON,
+                          StringRef FileName = "test.json") const {
+    auto ExpectedFilePath = writeJSON(JSON, FileName);
+    if (!ExpectedFilePath)
+      return ExpectedFilePath.takeError();
+
+    return readTUSummaryFromFile(FileName);
+  }
+
+  llvm::Error writeTUSummary(const TUSummary &Summary,
+                             StringRef FileName) const {
+    PathString FilePath = makePath(FileName);
+
+    return JSONFormat().writeTUSummary(Summary, FilePath);
+  }
+
+  // Normalize TUSummary JSON by sorting id_table by id field.
+  static Expected<json::Value> normalizeTUSummaryJSON(json::Value Val) {
+    auto *Obj = Val.getAsObject();
+    if (!Obj) {
+      return createStringError(
+          inconvertibleErrorCode(),
+          "Cannot normalize TUSummary JSON: expected an object");
+    }
+
+    auto *IDTable = Obj->getArray("id_table");
+    if (!IDTable) {
+      return createStringError(inconvertibleErrorCode(),
+                               "Cannot normalize TUSummary JSON: 'id_table' "
+                               "field is either missing or has the wrong type");
+    }
+
+    // Validate all id_table entries before sorting.
+    for (const auto &[Index, Entry] : llvm::enumerate(*IDTable)) {
+      const auto *EntryObj = Entry.getAsObject();
+      if (!EntryObj) {
+        return createStringError(
+            inconvertibleErrorCode(),
+            "Cannot normalize TUSummary JSON: id_table entry at index %zu is "
+            "not an object",
+            Index);
+      }
+
+      const auto *IDValue = EntryObj->get("id");
+      if (!IDValue) {
+        return createStringError(
+            inconvertibleErrorCode(),
+            "Cannot normalize TUSummary JSON: id_table entry at index %zu does "
+            "not contain an 'id' field",
+            Index);
+      }
+
+      auto EntryID = IDValue->getAsUINT64();
+      if (!EntryID) {
+        return createStringError(
+            inconvertibleErrorCode(),
+            "Cannot normalize TUSummary JSON: id_table entry at index %zu does "
+            "not contain a valid 'id' uint64_t field",
+            Index);
+      }
+    }
+
+    // Sort id_table entries by the "id" field to ensure deterministic ordering
+    // for comparison. Use projection-based comparison for strict-weak-ordering.
+    llvm::sort(*IDTable, [](const json::Value &A, const json::Value &B) {
+      // Safe to assume these succeed because we validated above.
+      const auto *AObj = A.getAsObject();
+      const auto *BObj = B.getAsObject();
+      uint64_t AID = *AObj->get("id")->getAsUINT64();
+      uint64_t BID = *BObj->get("id")->getAsUINT64();
+      return AID < BID;
+    });
+
+    return Val;
+  }
+
+  // Compare two TUSummary JSON values with normalization.
+  static Expected<bool> compareTUSummaryJSON(json::Value A, json::Value B) {
+    auto ExpectedNormalizedA = normalizeTUSummaryJSON(std::move(A));
+    if (!ExpectedNormalizedA)
+      return ExpectedNormalizedA.takeError();
+
+    auto ExpectedNormalizedB = normalizeTUSummaryJSON(std::move(B));
+    if (!ExpectedNormalizedB)
+      return ExpectedNormalizedB.takeError();
+
+    return *ExpectedNormalizedA == *ExpectedNormalizedB;
+  }
+
+  void readWriteCompareTUSummary(StringRef JSON) const {
+    const PathString InputFileName("input.json");
+    const PathString OutputFileName("output.json");
+
+    auto ExpectedInputFilePath = writeJSON(JSON, InputFileName);
+    ASSERT_THAT_EXPECTED(ExpectedInputFilePath, Succeeded());
+
+    auto ExpectedTUSummary = readTUSummaryFromFile(InputFileName);
+    ASSERT_THAT_EXPECTED(ExpectedTUSummary, Succeeded());
+
+    auto WriteError = writeTUSummary(*ExpectedTUSummary, OutputFileName);
+    ASSERT_THAT_ERROR(std::move(WriteError), Succeeded())
+        << "Failed to write to file: " << OutputFileName;
+
+    auto ExpectedInputJSON = readJSONFromFile(InputFileName);
+    ASSERT_THAT_EXPECTED(ExpectedInputJSON, Succeeded());
+    auto ExpectedOutputJSON = readJSONFromFile(OutputFileName);
+    ASSERT_THAT_EXPECTED(ExpectedOutputJSON, Succeeded());
+
+    auto ExpectedComparisonResult =
+        compareTUSummaryJSON(*ExpectedInputJSON, *ExpectedOutputJSON);
+    ASSERT_THAT_EXPECTED(ExpectedComparisonResult, Succeeded())
+        << "Failed to normalize JSON for comparison";
+
+    if (!*ExpectedComparisonResult) {
+      auto ExpectedNormalizedInput = normalizeTUSummaryJSON(*ExpectedInputJSON);
+      auto ExpectedNormalizedOutput =
+          normalizeTUSummaryJSON(*ExpectedOutputJSON);
+      FAIL() << "Serialization is broken: input JSON is 
diff erent from output "
+                "json\n"
+             << "Input:  "
+             << (ExpectedNormalizedInput
+                     ? llvm::formatv("{0:2}", *ExpectedNormalizedInput).str()
+                     : "normalization failed")
+             << "\n"
+             << "Output: "
+             << (ExpectedNormalizedOutput
+                     ? llvm::formatv("{0:2}", *ExpectedNormalizedOutput).str()
+                     : "normalization failed");
+    }
+  }
+};
+
+// ============================================================================
+// readJSON() Error Tests
+// ============================================================================
+
+TEST_F(JSONFormatTest, NonexistentFile) {
+  auto Result = readTUSummaryFromFile("nonexistent.json");
+
+  EXPECT_THAT_EXPECTED(
+      Result, FailedWithMessage(AllOf(HasSubstr("reading TUSummary from"),
+                                      HasSubstr("file does not exist"))));
+}
+
+TEST_F(JSONFormatTest, PathIsDirectory) {
+  PathString DirName("test_directory.json");
+
+  auto ExpectedDirPath = makeDirectory(DirName);
+  ASSERT_THAT_EXPECTED(ExpectedDirPath, Succeeded());
+
+  auto Result = readTUSummaryFromFile(DirName);
+
+  EXPECT_THAT_EXPECTED(
+      Result,
+      FailedWithMessage(AllOf(HasSubstr("reading TUSummary from"),
+                              HasSubstr("path is a directory, not a file"))));
+}
+
+TEST_F(JSONFormatTest, NotJsonExtension) {
+  PathString FileName("test.txt");
+
+  auto ExpectedFilePath = writeJSON("{}", FileName);
+  ASSERT_THAT_EXPECTED(ExpectedFilePath, Succeeded());
+
+  auto Result = readTUSummaryFromFile(FileName);
+
+  EXPECT_THAT_EXPECTED(
+      Result, FailedWithMessage(AllOf(
+                  HasSubstr("reading TUSummary from file"),
+                  HasSubstr("failed to read file"),
+                  HasSubstr("file does not end with '.json' extension"))));
+}
+
+TEST_F(JSONFormatTest, BrokenSymlink) {
+#ifdef _WIN32
+  GTEST_SKIP() << "Symlink model 
diff ers on Windows";
+#endif
+
+  // Create a symlink pointing to a non-existent file
+  auto ExpectedSymlinkPath =
+      makeSymlink("nonexistent_target.json", "broken_symlink.json");
+  ASSERT_THAT_EXPECTED(ExpectedSymlinkPath, Succeeded());
+
+  auto Result = readTUSummaryFromFile(*ExpectedSymlinkPath);
+
+  EXPECT_THAT_EXPECTED(
+      Result, FailedWithMessage(AllOf(HasSubstr("reading TUSummary from file"),
+                                      HasSubstr("failed to read file"))));
+}
+
+TEST_F(JSONFormatTest, NoReadPermission) {
+#ifdef _WIN32
+  GTEST_SKIP() << "Permission model 
diff ers on Windows";
+#endif
+
+  PathString FileName("no-read-permission.json");
+
+  auto ExpectedFilePath = writeJSON(R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [],
+    "data": []
+  })",
+                                    FileName);
+  ASSERT_THAT_EXPECTED(ExpectedFilePath, Succeeded());
+
+  auto PermError = setPermission(FileName, sys::fs::perms::owner_write |
+                                               sys::fs::perms::owner_exe);
+  ASSERT_THAT_ERROR(std::move(PermError), Succeeded());
+
+  auto Result = readTUSummaryFromFile(FileName);
+
+  EXPECT_THAT_EXPECTED(
+      Result, FailedWithMessage(AllOf(HasSubstr("reading TUSummary from file"),
+                                      HasSubstr("failed to read file"))));
+
+  // Restore permissions for cleanup
+  auto RestoreError = setPermission(FileName, sys::fs::perms::all_all);
+  EXPECT_THAT_ERROR(std::move(RestoreError), Succeeded());
+}
+
+TEST_F(JSONFormatTest, InvalidSyntax) {
+  auto Result = readTUSummaryFromString("{ invalid json }");
+
+  EXPECT_THAT_EXPECTED(
+      Result, FailedWithMessage(AllOf(HasSubstr("reading TUSummary from file"),
+                                      HasSubstr("Expected object key"))));
+}
+
+TEST_F(JSONFormatTest, NotObject) {
+  auto Result = readTUSummaryFromString("[]");
+
+  EXPECT_THAT_EXPECTED(
+      Result, FailedWithMessage(AllOf(HasSubstr("reading TUSummary from file"),
+                                      HasSubstr("failed to read TUSummary"),
+                                      HasSubstr("expected JSON object"))));
+}
+
+// ============================================================================
+// JSONFormat::buildNamespaceKindFromJSON() Error Tests
+// ============================================================================
+
+TEST_F(JSONFormatTest, InvalidKind) {
+  auto Result = readTUSummaryFromString(R"({
+    "tu_namespace": {
+      "kind": "invalid_kind",
+      "name": "test.cpp"
+    },
+    "id_table": [],
+    "data": []
+  })");
+
+  EXPECT_THAT_EXPECTED(
+      Result,
+      FailedWithMessage(AllOf(
+          HasSubstr("reading TUSummary from file"),
+          HasSubstr("reading BuildNamespace from field 'tu_namespace'"),
+          HasSubstr("reading BuildNamespaceKind from field 'kind'"),
+          HasSubstr(
+              "invalid 'kind' BuildNamespaceKind value 'invalid_kind'"))));
+}
+
+// ============================================================================
+// JSONFormat::buildNamespaceFromJSON() Error Tests
+// ============================================================================
+
+TEST_F(JSONFormatTest, MissingKind) {
+  auto Result = readTUSummaryFromString(R"({
+    "tu_namespace": {
+      "name": "test.cpp"
+    },
+    "id_table": [],
+    "data": []
+  })");
+
+  EXPECT_THAT_EXPECTED(
+      Result,
+      FailedWithMessage(AllOf(
+          HasSubstr("reading TUSummary from file"),
+          HasSubstr("reading BuildNamespace from field 'tu_namespace'"),
+          HasSubstr("failed to read BuildNamespaceKind from field 'kind'"),
+          HasSubstr("expected JSON string"))));
+}
+
+TEST_F(JSONFormatTest, MissingName) {
+  auto Result = readTUSummaryFromString(R"({
+    "tu_namespace": {
+      "kind": "compilation_unit"
+    },
+    "id_table": [],
+    "data": []
+  })");
+
+  EXPECT_THAT_EXPECTED(
+      Result,
+      FailedWithMessage(AllOf(
+          HasSubstr("reading TUSummary from file"),
+          HasSubstr("reading BuildNamespace from field 'tu_namespace'"),
+          HasSubstr("failed to read BuildNamespaceName from field 'name'"),
+          HasSubstr("expected JSON string"))));
+}
+
+// ============================================================================
+// JSONFormat::nestedBuildNamespaceFromJSON() Error Tests
+// ============================================================================
+
+TEST_F(JSONFormatTest, NamespaceElementNotObject) {
+  auto Result = readTUSummaryFromString(R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [
+      {
+        "id": 0,
+        "name": {
+          "usr": "c:@F at foo",
+          "suffix": "",
+          "namespace": ["invalid"]
+        }
+      }
+    ],
+    "data": []
+  })");
+
+  EXPECT_THAT_EXPECTED(
+      Result,
+      FailedWithMessage(AllOf(
+          HasSubstr("reading TUSummary from file"),
+          HasSubstr("reading IdTable from field 'id_table'"),
+          HasSubstr("reading EntityIdTable entry from index '0'"),
+          HasSubstr("reading EntityName from field 'name'"),
+          HasSubstr("reading NestedBuildNamespace from field 'namespace'"),
+          HasSubstr("failed to read BuildNamespace from index '0'"),
+          HasSubstr("expected JSON object"))));
+}
+
+TEST_F(JSONFormatTest, NamespaceElementMissingKind) {
+  auto Result = readTUSummaryFromString(R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [
+      {
+        "id": 0,
+        "name": {
+          "usr": "c:@F at foo",
+          "suffix": "",
+          "namespace": [
+            {
+              "name": "test.cpp"
+            }
+          ]
+        }
+      }
+    ],
+    "data": []
+  })");
+
+  EXPECT_THAT_EXPECTED(
+      Result,
+      FailedWithMessage(AllOf(
+          HasSubstr("reading TUSummary from file"),
+          HasSubstr("reading IdTable from field 'id_table'"),
+          HasSubstr("reading EntityIdTable entry from index '0'"),
+          HasSubstr("reading EntityName from field 'name'"),
+          HasSubstr("reading NestedBuildNamespace from field 'namespace'"),
+          HasSubstr("reading BuildNamespace from index '0'"),
+          HasSubstr("failed to read BuildNamespaceKind from field 'kind'"),
+          HasSubstr("expected JSON string"))));
+}
+
+TEST_F(JSONFormatTest, NamespaceElementInvalidKind) {
+  auto Result = readTUSummaryFromString(R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [
+      {
+        "id": 0,
+        "name": {
+          "usr": "c:@F at foo",
+          "suffix": "",
+          "namespace": [
+            {
+              "kind": "invalid_kind",
+              "name": "test.cpp"
+            }
+          ]
+        }
+      }
+    ],
+    "data": []
+  })");
+
+  EXPECT_THAT_EXPECTED(
+      Result,
+      FailedWithMessage(AllOf(
+          HasSubstr("reading TUSummary from file"),
+          HasSubstr("reading IdTable from field 'id_table'"),
+          HasSubstr("reading EntityIdTable entry from index '0'"),
+          HasSubstr("reading EntityName from field 'name'"),
+          HasSubstr("reading NestedBuildNamespace from field 'namespace'"),
+          HasSubstr("reading BuildNamespace from index '0'"),
+          HasSubstr("reading BuildNamespaceKind from field 'kind'"),
+          HasSubstr(
+              "invalid 'kind' BuildNamespaceKind value 'invalid_kind'"))));
+}
+
+TEST_F(JSONFormatTest, NamespaceElementMissingName) {
+  auto Result = readTUSummaryFromString(R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [
+      {
+        "id": 0,
+        "name": {
+          "usr": "c:@F at foo",
+          "suffix": "",
+          "namespace": [
+            {
+              "kind": "compilation_unit"
+            }
+          ]
+        }
+      }
+    ],
+    "data": []
+  })");
+
+  EXPECT_THAT_EXPECTED(
+      Result,
+      FailedWithMessage(AllOf(
+          HasSubstr("reading TUSummary from file"),
+          HasSubstr("reading IdTable from field 'id_table'"),
+          HasSubstr("reading EntityIdTable entry from index '0'"),
+          HasSubstr("reading EntityName from field 'name'"),
+          HasSubstr("reading NestedBuildNamespace from field 'namespace'"),
+          HasSubstr("reading BuildNamespace from index '0'"),
+          HasSubstr("failed to read BuildNamespaceName from field 'name'"),
+          HasSubstr("expected JSON string"))));
+}
+
+// ============================================================================
+// JSONFormat::entityNameFromJSON() Error Tests
+// ============================================================================
+
+TEST_F(JSONFormatTest, EntityNameMissingUSR) {
+  auto Result = readTUSummaryFromString(R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [
+      {
+        "id": 0,
+        "name": {
+          "suffix": "",
+          "namespace": []
+        }
+      }
+    ],
+    "data": []
+  })");
+
+  EXPECT_THAT_EXPECTED(
+      Result, FailedWithMessage(
+                  AllOf(HasSubstr("reading TUSummary from file"),
+                        HasSubstr("reading IdTable from field 'id_table'"),
+                        HasSubstr("reading EntityIdTable entry from index '0'"),
+                        HasSubstr("reading EntityName from field 'name'"),
+                        HasSubstr("failed to read USR from field 'usr'"),
+                        HasSubstr("expected JSON string"))));
+}
+
+TEST_F(JSONFormatTest, EntityNameMissingSuffix) {
+  auto Result = readTUSummaryFromString(R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [
+      {
+        "id": 0,
+        "name": {
+          "usr": "c:@F at foo",
+          "namespace": []
+        }
+      }
+    ],
+    "data": []
+  })");
+
+  EXPECT_THAT_EXPECTED(
+      Result, FailedWithMessage(
+                  AllOf(HasSubstr("reading TUSummary from file"),
+                        HasSubstr("reading IdTable from field 'id_table'"),
+                        HasSubstr("reading EntityIdTable entry from index '0'"),
+                        HasSubstr("reading EntityName from field 'name'"),
+                        HasSubstr("failed to read Suffix from field 'suffix'"),
+                        HasSubstr("expected JSON string"))));
+}
+
+TEST_F(JSONFormatTest, EntityNameMissingNamespace) {
+  auto Result = readTUSummaryFromString(R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [
+      {
+        "id": 0,
+        "name": {
+          "usr": "c:@F at foo",
+          "suffix": ""
+        }
+      }
+    ],
+    "data": []
+  })");
+
+  EXPECT_THAT_EXPECTED(
+      Result,
+      FailedWithMessage(AllOf(
+          HasSubstr("reading TUSummary from file"),
+          HasSubstr("reading IdTable from field 'id_table'"),
+          HasSubstr("reading EntityIdTable entry from index '0'"),
+          HasSubstr("reading EntityName from field 'name'"),
+          HasSubstr(
+              "failed to read NestedBuildNamespace from field 'namespace'"),
+          HasSubstr("expected JSON array"))));
+}
+
+// ============================================================================
+// JSONFormat::entityIdTableEntryFromJSON() Error Tests
+// ============================================================================
+
+TEST_F(JSONFormatTest, IDTableEntryMissingID) {
+  auto Result = readTUSummaryFromString(R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [
+      {
+        "name": {
+          "usr": "c:@F at foo",
+          "suffix": "",
+          "namespace": []
+        }
+      }
+    ],
+    "data": []
+  })");
+
+  EXPECT_THAT_EXPECTED(
+      Result,
+      FailedWithMessage(
+          AllOf(HasSubstr("reading TUSummary from file"),
+                HasSubstr("reading IdTable from field 'id_table'"),
+                HasSubstr("reading EntityIdTable entry from index '0'"),
+                HasSubstr("failed to read EntityId from field 'id'"),
+                HasSubstr("expected JSON number (unsigned 64-bit integer)"))));
+}
+
+TEST_F(JSONFormatTest, IDTableEntryMissingName) {
+  auto Result = readTUSummaryFromString(R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [
+      {
+        "id": 0
+      }
+    ],
+    "data": []
+  })");
+
+  EXPECT_THAT_EXPECTED(
+      Result, FailedWithMessage(AllOf(
+                  HasSubstr("reading TUSummary from file"),
+                  HasSubstr("reading IdTable from field 'id_table'"),
+                  HasSubstr("reading EntityIdTable entry from index '0'"),
+                  HasSubstr("failed to read EntityName from field 'name'"),
+                  HasSubstr("expected JSON object"))));
+}
+
+TEST_F(JSONFormatTest, IDTableEntryIDNotUInt64) {
+  auto Result = readTUSummaryFromString(R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [
+      {
+        "id": "not_a_number",
+        "name": {
+          "usr": "c:@F at foo",
+          "suffix": "",
+          "namespace": []
+        }
+      }
+    ],
+    "data": []
+  })");
+
+  EXPECT_THAT_EXPECTED(
+      Result,
+      FailedWithMessage(
+          AllOf(HasSubstr("reading TUSummary from file"),
+                HasSubstr("reading IdTable from field 'id_table'"),
+                HasSubstr("reading EntityIdTable entry from index '0'"),
+                HasSubstr("failed to read EntityId from field 'id'"),
+                HasSubstr("expected JSON number (unsigned 64-bit integer)"))));
+}
+
+// ============================================================================
+// JSONFormat::entityIdTableFromJSON() Error Tests
+// ============================================================================
+
+TEST_F(JSONFormatTest, IDTableNotArray) {
+  auto Result = readTUSummaryFromString(R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": {},
+    "data": []
+  })");
+
+  EXPECT_THAT_EXPECTED(
+      Result, FailedWithMessage(AllOf(
+                  HasSubstr("reading TUSummary from file"),
+                  HasSubstr("failed to read IdTable from field 'id_table'"),
+                  HasSubstr("expected JSON array"))));
+}
+
+TEST_F(JSONFormatTest, IDTableElementNotObject) {
+  auto Result = readTUSummaryFromString(R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [123],
+    "data": []
+  })");
+
+  EXPECT_THAT_EXPECTED(
+      Result,
+      FailedWithMessage(
+          AllOf(HasSubstr("reading TUSummary from file"),
+                HasSubstr("reading IdTable from field 'id_table'"),
+                HasSubstr("failed to read EntityIdTable entry from index '0'"),
+                HasSubstr("expected JSON object"))));
+}
+
+TEST_F(JSONFormatTest, DuplicateEntity) {
+  auto Result = readTUSummaryFromString(R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [
+      {
+        "id": 0,
+        "name": {
+          "usr": "c:@F at foo",
+          "suffix": "",
+          "namespace": [
+            {
+              "kind": "compilation_unit",
+              "name": "test.cpp"
+            }
+          ]
+        }
+      },
+      {
+        "id": 1,
+        "name": {
+          "usr": "c:@F at foo",
+          "suffix": "",
+          "namespace": [
+            {
+              "kind": "compilation_unit",
+              "name": "test.cpp"
+            }
+          ]
+        }
+      }
+    ],
+    "data": []
+  })");
+
+  EXPECT_THAT_EXPECTED(
+      Result,
+      FailedWithMessage(
+          AllOf(HasSubstr("reading TUSummary from file"),
+                HasSubstr("reading IdTable from field 'id_table'"),
+                HasSubstr("failed to insert EntityIdTable entry at index '1'"),
+                HasSubstr("encountered duplicate EntityId '0'"))));
+}
+
+// ============================================================================
+// JSONFormat::entitySummaryFromJSON() Error Tests
+// ============================================================================
+
+TEST_F(JSONFormatTest, EntitySummaryNoFormatInfo) {
+  auto Result = readTUSummaryFromString(R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [],
+    "data": [
+      {
+        "summary_name": "unknown_summary_type",
+        "summary_data": [
+          {
+            "entity_id": 0,
+            "entity_summary": {}
+          }
+        ]
+      }
+    ]
+  })");
+
+  EXPECT_THAT_EXPECTED(
+      Result,
+      FailedWithMessage(AllOf(
+          HasSubstr("reading TUSummary from file"),
+          HasSubstr("reading SummaryData entries from field 'data'"),
+          HasSubstr("reading SummaryData entry from index '0'"),
+          HasSubstr("reading EntitySummary entries from field 'summary_data'"),
+          HasSubstr("reading EntitySummary entry from index '0'"),
+          HasSubstr("reading EntitySummary from field 'entity_summary'"),
+          HasSubstr("failed to deserialize EntitySummary"),
+          HasSubstr(
+              "no FormatInfo registered for summary 'unknown_summary_type'"))));
+}
+
+// ============================================================================
+// PairsEntitySummaryForJSONFormatTest Deserialization Error Tests
+// ============================================================================
+
+TEST_F(JSONFormatTest, PairsEntitySummaryForJSONFormatTestMissingPairsField) {
+  auto Result = readTUSummaryFromString(R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [],
+    "data": [
+      {
+        "summary_name": "PairsEntitySummaryForJSONFormatTest",
+        "summary_data": [
+          {
+            "entity_id": 0,
+            "entity_summary": {}
+          }
+        ]
+      }
+    ]
+  })");
+
+  EXPECT_THAT_EXPECTED(
+      Result,
+      FailedWithMessage(AllOf(
+          HasSubstr("reading TUSummary from file"),
+          HasSubstr("reading SummaryData entries from field 'data'"),
+          HasSubstr("reading SummaryData entry from index '0'"),
+          HasSubstr("reading EntitySummary entries from field 'summary_data'"),
+          HasSubstr("reading EntitySummary entry from index '0'"),
+          HasSubstr("reading EntitySummary from field 'entity_summary'"),
+          HasSubstr("missing or invalid field 'pairs'"))));
+}
+
+TEST_F(JSONFormatTest,
+       PairsEntitySummaryForJSONFormatTestInvalidPairsFieldType) {
+  auto Result = readTUSummaryFromString(R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [],
+    "data": [
+      {
+        "summary_name": "PairsEntitySummaryForJSONFormatTest",
+        "summary_data": [
+          {
+            "entity_id": 0,
+            "entity_summary": {
+              "pairs": "not_an_array"
+            }
+          }
+        ]
+      }
+    ]
+  })");
+
+  EXPECT_THAT_EXPECTED(
+      Result,
+      FailedWithMessage(AllOf(
+          HasSubstr("reading TUSummary from file"),
+          HasSubstr("reading SummaryData entries from field 'data'"),
+          HasSubstr("reading SummaryData entry from index '0'"),
+          HasSubstr("reading EntitySummary entries from field 'summary_data'"),
+          HasSubstr("reading EntitySummary entry from index '0'"),
+          HasSubstr("reading EntitySummary from field 'entity_summary'"),
+          HasSubstr("missing or invalid field 'pairs'"))));
+}
+
+TEST_F(JSONFormatTest,
+       PairsEntitySummaryForJSONFormatTestPairsElementNotObject) {
+  auto Result = readTUSummaryFromString(R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [],
+    "data": [
+      {
+        "summary_name": "PairsEntitySummaryForJSONFormatTest",
+        "summary_data": [
+          {
+            "entity_id": 0,
+            "entity_summary": {
+              "pairs": ["not_an_object"]
+            }
+          }
+        ]
+      }
+    ]
+  })");
+
+  EXPECT_THAT_EXPECTED(
+      Result,
+      FailedWithMessage(AllOf(
+          HasSubstr("reading TUSummary from file"),
+          HasSubstr("reading SummaryData entries from field 'data'"),
+          HasSubstr("reading SummaryData entry from index '0'"),
+          HasSubstr("reading EntitySummary entries from field 'summary_data'"),
+          HasSubstr("reading EntitySummary entry from index '0'"),
+          HasSubstr("reading EntitySummary from field 'entity_summary'"),
+          HasSubstr("pairs element at index 0 is not a JSON object"))));
+}
+
+TEST_F(JSONFormatTest, PairsEntitySummaryForJSONFormatTestMissingFirstField) {
+  auto Result = readTUSummaryFromString(R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [],
+    "data": [
+      {
+        "summary_name": "PairsEntitySummaryForJSONFormatTest",
+        "summary_data": [
+          {
+            "entity_id": 0,
+            "entity_summary": {
+              "pairs": [
+                {
+                  "second": 1
+                }
+              ]
+            }
+          }
+        ]
+      }
+    ]
+  })");
+
+  EXPECT_THAT_EXPECTED(
+      Result,
+      FailedWithMessage(AllOf(
+          HasSubstr("reading TUSummary from file"),
+          HasSubstr("reading SummaryData entries from field 'data'"),
+          HasSubstr("reading SummaryData entry from index '0'"),
+          HasSubstr("reading EntitySummary entries from field 'summary_data'"),
+          HasSubstr("reading EntitySummary entry from index '0'"),
+          HasSubstr("reading EntitySummary from field 'entity_summary'"),
+          HasSubstr("missing or invalid 'first' field at index '0'"))));
+}
+
+TEST_F(JSONFormatTest, PairsEntitySummaryForJSONFormatTestInvalidFirstField) {
+  auto Result = readTUSummaryFromString(R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [],
+    "data": [
+      {
+        "summary_name": "PairsEntitySummaryForJSONFormatTest",
+        "summary_data": [
+          {
+            "entity_id": 0,
+            "entity_summary": {
+              "pairs": [
+                {
+                  "first": "not_a_number",
+                  "second": 1
+                }
+              ]
+            }
+          }
+        ]
+      }
+    ]
+  })");
+
+  EXPECT_THAT_EXPECTED(
+      Result,
+      FailedWithMessage(AllOf(
+          HasSubstr("reading TUSummary from file"),
+          HasSubstr("reading SummaryData entries from field 'data'"),
+          HasSubstr("reading SummaryData entry from index '0'"),
+          HasSubstr("reading EntitySummary entries from field 'summary_data'"),
+          HasSubstr("reading EntitySummary entry from index '0'"),
+          HasSubstr("reading EntitySummary from field 'entity_summary'"),
+          HasSubstr("missing or invalid 'first' field at index '0'"))));
+}
+
+TEST_F(JSONFormatTest, PairsEntitySummaryForJSONFormatTestMissingSecondField) {
+  auto Result = readTUSummaryFromString(R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [],
+    "data": [
+      {
+        "summary_name": "PairsEntitySummaryForJSONFormatTest",
+        "summary_data": [
+          {
+            "entity_id": 0,
+            "entity_summary": {
+              "pairs": [
+                {
+                  "first": 0
+                }
+              ]
+            }
+          }
+        ]
+      }
+    ]
+  })");
+
+  EXPECT_THAT_EXPECTED(
+      Result,
+      FailedWithMessage(AllOf(
+          HasSubstr("reading TUSummary from file"),
+          HasSubstr("reading SummaryData entries from field 'data'"),
+          HasSubstr("reading SummaryData entry from index '0'"),
+          HasSubstr("reading EntitySummary entries from field 'summary_data'"),
+          HasSubstr("reading EntitySummary entry from index '0'"),
+          HasSubstr("reading EntitySummary from field 'entity_summary'"),
+          HasSubstr("missing or invalid 'second' field at index '0'"))));
+}
+
+TEST_F(JSONFormatTest, PairsEntitySummaryForJSONFormatTestInvalidSecondField) {
+  auto Result = readTUSummaryFromString(R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [],
+    "data": [
+      {
+        "summary_name": "PairsEntitySummaryForJSONFormatTest",
+        "summary_data": [
+          {
+            "entity_id": 0,
+            "entity_summary": {
+              "pairs": [
+                {
+                  "first": 0,
+                  "second": "not_a_number"
+                }
+              ]
+            }
+          }
+        ]
+      }
+    ]
+  })");
+
+  EXPECT_THAT_EXPECTED(
+      Result,
+      FailedWithMessage(AllOf(
+          HasSubstr("reading TUSummary from file"),
+          HasSubstr("reading SummaryData entries from field 'data'"),
+          HasSubstr("reading SummaryData entry from index '0'"),
+          HasSubstr("reading EntitySummary entries from field 'summary_data'"),
+          HasSubstr("reading EntitySummary entry from index '0'"),
+          HasSubstr("reading EntitySummary from field 'entity_summary'"),
+          HasSubstr("missing or invalid 'second' field at index '0'"))));
+}
+
+// ============================================================================
+// JSONFormat::entityDataMapEntryFromJSON() Error Tests
+// ============================================================================
+
+TEST_F(JSONFormatTest, EntityDataMissingEntityID) {
+  auto Result = readTUSummaryFromString(R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [],
+    "data": [
+      {
+        "summary_name": "PairsEntitySummaryForJSONFormatTest",
+        "summary_data": [
+          {
+            "entity_summary": {}
+          }
+        ]
+      }
+    ]
+  })");
+
+  EXPECT_THAT_EXPECTED(
+      Result,
+      FailedWithMessage(AllOf(
+          HasSubstr("reading TUSummary from file"),
+          HasSubstr("reading SummaryData entries from field 'data'"),
+          HasSubstr("reading SummaryData entry from index '0'"),
+          HasSubstr("reading EntitySummary entries from field 'summary_data'"),
+          HasSubstr("reading EntitySummary entry from index '0'"),
+          HasSubstr("failed to read EntityId from field 'entity_id'"),
+          HasSubstr("expected JSON number (unsigned 64-bit integer)"))));
+}
+
+TEST_F(JSONFormatTest, EntityDataMissingEntitySummary) {
+  auto Result = readTUSummaryFromString(R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [],
+    "data": [
+      {
+        "summary_name": "PairsEntitySummaryForJSONFormatTest",
+        "summary_data": [
+          {
+            "entity_id": 0
+          }
+        ]
+      }
+    ]
+  })");
+
+  EXPECT_THAT_EXPECTED(
+      Result,
+      FailedWithMessage(AllOf(
+          HasSubstr("reading TUSummary from file"),
+          HasSubstr("reading SummaryData entries from field 'data'"),
+          HasSubstr("reading SummaryData entry from index '0'"),
+          HasSubstr("reading EntitySummary entries from field 'summary_data'"),
+          HasSubstr("reading EntitySummary entry from index '0'"),
+          HasSubstr("failed to read EntitySummary from field 'entity_summary'"),
+          HasSubstr("expected JSON object"))));
+}
+
+TEST_F(JSONFormatTest, EntityIDNotUInt64) {
+  auto Result = readTUSummaryFromString(R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [],
+    "data": [
+      {
+        "summary_name": "PairsEntitySummaryForJSONFormatTest",
+        "summary_data": [
+          {
+            "entity_id": "not_a_number",
+            "entity_summary": {}
+          }
+        ]
+      }
+    ]
+  })");
+
+  EXPECT_THAT_EXPECTED(
+      Result,
+      FailedWithMessage(AllOf(
+          HasSubstr("reading TUSummary from file"),
+          HasSubstr("reading SummaryData entries from field 'data'"),
+          HasSubstr("reading SummaryData entry from index '0'"),
+          HasSubstr("reading EntitySummary entries from field 'summary_data'"),
+          HasSubstr("reading EntitySummary entry from index '0'"),
+          HasSubstr("failed to read EntityId from field 'entity_id'"),
+          HasSubstr("expected JSON integer"))));
+}
+
+// ============================================================================
+// JSONFormat::entityDataMapFromJSON() Error Tests
+// ============================================================================
+
+TEST_F(JSONFormatTest, EntityDataElementNotObject) {
+  auto Result = readTUSummaryFromString(R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [],
+    "data": [
+      {
+        "summary_name": "PairsEntitySummaryForJSONFormatTest",
+        "summary_data": ["invalid"]
+      }
+    ]
+  })");
+
+  EXPECT_THAT_EXPECTED(
+      Result,
+      FailedWithMessage(AllOf(
+          HasSubstr("reading TUSummary from file"),
+          HasSubstr("reading SummaryData entries from field 'data'"),
+          HasSubstr("reading SummaryData entry from index '0'"),
+          HasSubstr("reading EntitySummary entries from field 'summary_data'"),
+          HasSubstr("failed to read EntitySummary entry from index '0'"),
+          HasSubstr("expected JSON object"))));
+}
+
+TEST_F(JSONFormatTest, DuplicateEntityIdInDataMap) {
+  auto Result = readTUSummaryFromString(R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [
+      {
+        "id": 0,
+        "name": {
+          "usr": "c:@F at foo",
+          "suffix": "",
+          "namespace": []
+        }
+      }
+    ],
+    "data": [
+      {
+        "summary_name": "PairsEntitySummaryForJSONFormatTest",
+        "summary_data": [
+          {
+            "entity_id": 0,
+            "entity_summary": {
+              "pairs": []
+            }
+          },
+          {
+            "entity_id": 0,
+            "entity_summary": {
+              "pairs": []
+            }
+          }
+        ]
+      }
+    ]
+  })");
+
+  EXPECT_THAT_EXPECTED(
+      Result,
+      FailedWithMessage(AllOf(
+          HasSubstr("reading TUSummary from file"),
+          HasSubstr("reading SummaryData entries from field 'data'"),
+          HasSubstr("reading SummaryData entry from index '0'"),
+          HasSubstr("reading EntitySummary entries from field 'summary_data'"),
+          HasSubstr("failed to insert EntitySummary entry at index '1'"),
+          HasSubstr("encountered duplicate EntityId '0'"))));
+}
+
+// ============================================================================
+// JSONFormat::summaryDataMapEntryFromJSON() Error Tests
+// ============================================================================
+
+TEST_F(JSONFormatTest, DataEntryMissingSummaryName) {
+  auto Result = readTUSummaryFromString(R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [],
+    "data": [
+      {
+        "summary_data": []
+      }
+    ]
+  })");
+
+  EXPECT_THAT_EXPECTED(
+      Result,
+      FailedWithMessage(AllOf(
+          HasSubstr("reading TUSummary from file"),
+          HasSubstr("reading SummaryData entries from field 'data'"),
+          HasSubstr("reading SummaryData entry from index '0'"),
+          HasSubstr("failed to read SummaryName from field 'summary_name'"),
+          HasSubstr("expected JSON string"))));
+}
+
+TEST_F(JSONFormatTest, DataEntryMissingData) {
+  auto Result = readTUSummaryFromString(R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [],
+    "data": [
+      {
+        "summary_name": "PairsEntitySummaryForJSONFormatTest"
+      }
+    ]
+  })");
+
+  EXPECT_THAT_EXPECTED(
+      Result,
+      FailedWithMessage(AllOf(
+          HasSubstr("reading TUSummary from file"),
+          HasSubstr("reading SummaryData entries from field 'data'"),
+          HasSubstr("reading SummaryData entry from index '0'"),
+          HasSubstr(
+              "failed to read EntitySummary entries from field 'summary_data'"),
+          HasSubstr("expected JSON array"))));
+}
+
+// ============================================================================
+// JSONFormat::summaryDataMapFromJSON() Error Tests
+// ============================================================================
+
+TEST_F(JSONFormatTest, DataNotArray) {
+  auto Result = readTUSummaryFromString(R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [],
+    "data": {}
+  })");
+
+  EXPECT_THAT_EXPECTED(
+      Result,
+      FailedWithMessage(AllOf(
+          HasSubstr("reading TUSummary from file"),
+          HasSubstr("failed to read SummaryData entries from field 'data'"),
+          HasSubstr("expected JSON array"))));
+}
+
+TEST_F(JSONFormatTest, DataElementNotObject) {
+  auto Result = readTUSummaryFromString(R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [],
+    "data": ["invalid"]
+  })");
+
+  EXPECT_THAT_EXPECTED(
+      Result, FailedWithMessage(AllOf(
+                  HasSubstr("reading TUSummary from file"),
+                  HasSubstr("reading SummaryData entries from field 'data'"),
+                  HasSubstr("failed to read SummaryData entry from index '0'"),
+                  HasSubstr("expected JSON object"))));
+}
+
+TEST_F(JSONFormatTest, DuplicateSummaryName) {
+  auto Result = readTUSummaryFromString(R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [],
+    "data": [
+      {
+        "summary_name": "PairsEntitySummaryForJSONFormatTest",
+        "summary_data": []
+      },
+      {
+        "summary_name": "PairsEntitySummaryForJSONFormatTest",
+        "summary_data": []
+      }
+    ]
+  })");
+
+  EXPECT_THAT_EXPECTED(
+      Result, FailedWithMessage(AllOf(
+                  HasSubstr("reading TUSummary from file"),
+                  HasSubstr("reading SummaryData entries from field 'data'"),
+                  HasSubstr("failed to insert SummaryData entry at index '1'"),
+                  HasSubstr("encountered duplicate SummaryName "
+                            "'PairsEntitySummaryForJSONFormatTest'"))));
+}
+
+// ============================================================================
+// JSONFormat::readTUSummary() Error Tests
+// ============================================================================
+
+TEST_F(JSONFormatTest, MissingTUNamespace) {
+  auto Result = readTUSummaryFromString(R"({
+    "id_table": [],
+    "data": []
+  })");
+
+  EXPECT_THAT_EXPECTED(
+      Result,
+      FailedWithMessage(AllOf(
+          HasSubstr("reading TUSummary from file"),
+          HasSubstr("failed to read BuildNamespace from field 'tu_namespace'"),
+          HasSubstr("expected JSON object"))));
+}
+
+TEST_F(JSONFormatTest, MissingIDTable) {
+  auto Result = readTUSummaryFromString(R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "data": []
+  })");
+
+  EXPECT_THAT_EXPECTED(
+      Result, FailedWithMessage(AllOf(
+                  HasSubstr("reading TUSummary from file"),
+                  HasSubstr("failed to read IdTable from field 'id_table'"),
+                  HasSubstr("expected JSON array"))));
+}
+
+TEST_F(JSONFormatTest, MissingData) {
+  auto Result = readTUSummaryFromString(R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": []
+  })");
+
+  EXPECT_THAT_EXPECTED(
+      Result,
+      FailedWithMessage(AllOf(
+          HasSubstr("reading TUSummary from file"),
+          HasSubstr("failed to read SummaryData entries from field 'data'"),
+          HasSubstr("expected JSON array"))));
+}
+
+// ============================================================================
+// JSONFormat::writeJSON() Error Tests
+// ============================================================================
+
+TEST_F(JSONFormatTest, WriteFileAlreadyExists) {
+  PathString FileName("existing.json");
+
+  auto ExpectedFilePath = writeJSON("{}", FileName);
+  ASSERT_THAT_EXPECTED(ExpectedFilePath, Succeeded());
+
+  TUSummary Summary(
+      BuildNamespace(BuildNamespaceKind::CompilationUnit, "test.cpp"));
+
+  // Try to write to the same path
+  auto Result = writeTUSummary(Summary, FileName);
+
+  EXPECT_THAT_ERROR(
+      std::move(Result),
+      FailedWithMessage(AllOf(HasSubstr("writing TUSummary to file"),
+                              HasSubstr("failed to write file"),
+                              HasSubstr("file already exists"))));
+}
+
+TEST_F(JSONFormatTest, WriteParentDirectoryNotFound) {
+  PathString FilePath = makePath("nonexistent-dir", "test.json");
+
+  TUSummary Summary(
+      BuildNamespace(BuildNamespaceKind::CompilationUnit, "test.cpp"));
+
+  auto Result = JSONFormat().writeTUSummary(Summary, FilePath);
+
+  EXPECT_THAT_ERROR(
+      std::move(Result),
+      FailedWithMessage(AllOf(HasSubstr("writing TUSummary to file"),
+                              HasSubstr("failed to write file"),
+                              HasSubstr("parent directory does not exist"))));
+}
+
+TEST_F(JSONFormatTest, WriteNotJsonExtension) {
+  TUSummary Summary(
+      BuildNamespace(BuildNamespaceKind::CompilationUnit, "test.cpp"));
+
+  auto Result = writeTUSummary(Summary, "test.txt");
+
+  EXPECT_THAT_ERROR(
+      std::move(Result),
+      FailedWithMessage(
+          AllOf(HasSubstr("writing TUSummary to file"),
+                HasSubstr("failed to write file"),
+                HasSubstr("file does not end with '.json' extension"))));
+}
+
+TEST_F(JSONFormatTest, WriteStreamOpenFailure) {
+#ifdef _WIN32
+  GTEST_SKIP() << "Permission model 
diff ers on Windows";
+#endif
+
+  const PathString DirName("write-protected-dir");
+
+  auto ExpectedDirPath = makeDirectory(DirName);
+  ASSERT_THAT_EXPECTED(ExpectedDirPath, Succeeded());
+
+  auto PermError = setPermission(DirName, sys::fs::perms::owner_read |
+                                              sys::fs::perms::owner_exe);
+  ASSERT_THAT_ERROR(std::move(PermError), Succeeded());
+
+  PathString FilePath = makePath(DirName, "test.json");
+
+  TUSummary Summary(
+      BuildNamespace(BuildNamespaceKind::CompilationUnit, "test.cpp"));
+
+  auto Result = JSONFormat().writeTUSummary(Summary, FilePath);
+
+  EXPECT_THAT_ERROR(
+      std::move(Result),
+      FailedWithMessage(AllOf(HasSubstr("writing TUSummary to file"),
+                              HasSubstr("failed to write file"))));
+
+  // Restore permissions for cleanup
+  auto RestoreError = setPermission(DirName, sys::fs::perms::all_all);
+  EXPECT_THAT_ERROR(std::move(RestoreError), Succeeded());
+}
+
+// ============================================================================
+// Round-Trip Tests - Serialization Verification
+// ============================================================================
+
+TEST_F(JSONFormatTest, Empty) {
+  readWriteCompareTUSummary(R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [],
+    "data": []
+  })");
+}
+
+TEST_F(JSONFormatTest, LinkUnit) {
+  readWriteCompareTUSummary(R"({
+    "tu_namespace": {
+      "kind": "link_unit",
+      "name": "libtest.so"
+    },
+    "id_table": [],
+    "data": []
+  })");
+}
+
+TEST_F(JSONFormatTest, WithIDTable) {
+  readWriteCompareTUSummary(R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [
+      {
+        "id": 0,
+        "name": {
+          "usr": "c:@F at foo",
+          "suffix": "",
+          "namespace": [
+            {
+              "kind": "compilation_unit",
+              "name": "test.cpp"
+            }
+          ]
+        }
+      },
+      {
+        "id": 1,
+        "name": {
+          "usr": "c:@F at bar",
+          "suffix": "1",
+          "namespace": [
+            {
+              "kind": "compilation_unit",
+              "name": "test.cpp"
+            },
+            {
+              "kind": "link_unit",
+              "name": "libtest.so"
+            }
+          ]
+        }
+      }
+    ],
+    "data": []
+  })");
+}
+
+TEST_F(JSONFormatTest, WithEmptyDataEntry) {
+  readWriteCompareTUSummary(R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [],
+    "data": [
+      {
+        "summary_name": "PairsEntitySummaryForJSONFormatTest",
+        "summary_data": []
+      }
+    ]
+  })");
+}
+
+TEST_F(JSONFormatTest, RoundTripWithIDTable) {
+  readWriteCompareTUSummary(R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [
+      {
+        "id": 0,
+        "name": {
+          "usr": "c:@F at foo",
+          "suffix": "",
+          "namespace": [
+            {
+              "kind": "compilation_unit",
+              "name": "test.cpp"
+            }
+          ]
+        }
+      }
+    ],
+    "data": []
+  })");
+}
+
+TEST_F(JSONFormatTest, RoundTripPairsEntitySummaryForJSONFormatTest) {
+  readWriteCompareTUSummary(R"({
+    "tu_namespace": {
+      "kind": "compilation_unit",
+      "name": "test.cpp"
+    },
+    "id_table": [
+      {
+        "id": 0,
+        "name": {
+          "usr": "c:@F at main",
+          "suffix": "",
+          "namespace": [
+            {
+              "kind": "compilation_unit",
+              "name": "test.cpp"
+            }
+          ]
+        }
+      },
+      {
+        "id": 1,
+        "name": {
+          "usr": "c:@F at foo",
+          "suffix": "",
+          "namespace": [
+            {
+              "kind": "compilation_unit",
+              "name": "test.cpp"
+            }
+          ]
+        }
+      },
+      {
+        "id": 2,
+        "name": {
+          "usr": "c:@F at bar",
+          "suffix": "",
+          "namespace": [
+            {
+              "kind": "compilation_unit",
+              "name": "test.cpp"
+            }
+          ]
+        }
+      }
+    ],
+    "data": [
+      {
+        "summary_name": "PairsEntitySummaryForJSONFormatTest",
+        "summary_data": [
+          {
+            "entity_id": 0,
+            "entity_summary": {
+              "pairs": [
+                {
+                  "first": 0,
+                  "second": 1
+                },
+                {
+                  "first": 1,
+                  "second": 2
+                }
+              ]
+            }
+          }
+        ]
+      }
+    ]
+  })");
+}
+
+} // anonymous namespace


        


More information about the cfe-commits mailing list