TracedValue.

Add a class for writing JSON-like structured data traces, composed from
primitive types like integers and strings as well as complex types like
dicts and arrays.

R=primiano@google.com,eseckler@google.com,skyostil@google.com

Change-Id: I85b011fb828f60ff570185d51ebf47b7dc074b7a
diff --git a/Android.bp b/Android.bp
index 221cbb3..db620dd 100644
--- a/Android.bp
+++ b/Android.bp
@@ -8162,6 +8162,7 @@
     "src/tracing/internal/tracing_muxer_impl.cc",
     "src/tracing/internal/track_event_internal.cc",
     "src/tracing/platform.cc",
+    "src/tracing/traced_value.cc",
     "src/tracing/tracing.cc",
     "src/tracing/track.cc",
     "src/tracing/track_event_category_registry.cc",
@@ -8344,6 +8345,14 @@
   ],
 }
 
+// GN: //src/tracing:unittests
+filegroup {
+  name: "perfetto_src_tracing_unittests",
+  srcs: [
+    "src/tracing/traced_value_unittest.cc",
+  ],
+}
+
 // GN: //test:end_to_end_integrationtests
 filegroup {
   name: "perfetto_test_end_to_end_integrationtests",
@@ -8543,63 +8552,90 @@
     ":perfetto_include_perfetto_tracing_core_forward_decls",
     ":perfetto_include_perfetto_tracing_tracing",
     ":perfetto_protos_perfetto_common_cpp_gen",
+    ":perfetto_protos_perfetto_common_lite_gen",
     ":perfetto_protos_perfetto_common_zero_gen",
     ":perfetto_protos_perfetto_config_android_cpp_gen",
+    ":perfetto_protos_perfetto_config_android_lite_gen",
     ":perfetto_protos_perfetto_config_android_zero_gen",
     ":perfetto_protos_perfetto_config_cpp_gen",
     ":perfetto_protos_perfetto_config_ftrace_cpp_gen",
+    ":perfetto_protos_perfetto_config_ftrace_lite_gen",
     ":perfetto_protos_perfetto_config_ftrace_zero_gen",
     ":perfetto_protos_perfetto_config_gpu_cpp_gen",
+    ":perfetto_protos_perfetto_config_gpu_lite_gen",
     ":perfetto_protos_perfetto_config_gpu_zero_gen",
     ":perfetto_protos_perfetto_config_inode_file_cpp_gen",
+    ":perfetto_protos_perfetto_config_inode_file_lite_gen",
     ":perfetto_protos_perfetto_config_inode_file_zero_gen",
     ":perfetto_protos_perfetto_config_interceptors_cpp_gen",
+    ":perfetto_protos_perfetto_config_interceptors_lite_gen",
     ":perfetto_protos_perfetto_config_interceptors_zero_gen",
+    ":perfetto_protos_perfetto_config_lite_gen",
     ":perfetto_protos_perfetto_config_power_cpp_gen",
+    ":perfetto_protos_perfetto_config_power_lite_gen",
     ":perfetto_protos_perfetto_config_power_zero_gen",
     ":perfetto_protos_perfetto_config_process_stats_cpp_gen",
+    ":perfetto_protos_perfetto_config_process_stats_lite_gen",
     ":perfetto_protos_perfetto_config_process_stats_zero_gen",
     ":perfetto_protos_perfetto_config_profiling_cpp_gen",
+    ":perfetto_protos_perfetto_config_profiling_lite_gen",
     ":perfetto_protos_perfetto_config_profiling_zero_gen",
     ":perfetto_protos_perfetto_config_sys_stats_cpp_gen",
+    ":perfetto_protos_perfetto_config_sys_stats_lite_gen",
     ":perfetto_protos_perfetto_config_sys_stats_zero_gen",
     ":perfetto_protos_perfetto_config_track_event_cpp_gen",
+    ":perfetto_protos_perfetto_config_track_event_lite_gen",
     ":perfetto_protos_perfetto_config_track_event_zero_gen",
     ":perfetto_protos_perfetto_config_zero_gen",
     ":perfetto_protos_perfetto_ipc_cpp_gen",
     ":perfetto_protos_perfetto_ipc_ipc_gen",
     ":perfetto_protos_perfetto_ipc_wire_protocol_cpp_gen",
     ":perfetto_protos_perfetto_trace_android_cpp_gen",
+    ":perfetto_protos_perfetto_trace_android_lite_gen",
     ":perfetto_protos_perfetto_trace_android_zero_gen",
     ":perfetto_protos_perfetto_trace_chrome_cpp_gen",
+    ":perfetto_protos_perfetto_trace_chrome_lite_gen",
     ":perfetto_protos_perfetto_trace_chrome_zero_gen",
     ":perfetto_protos_perfetto_trace_filesystem_cpp_gen",
+    ":perfetto_protos_perfetto_trace_filesystem_lite_gen",
     ":perfetto_protos_perfetto_trace_filesystem_zero_gen",
     ":perfetto_protos_perfetto_trace_ftrace_cpp_gen",
+    ":perfetto_protos_perfetto_trace_ftrace_lite_gen",
     ":perfetto_protos_perfetto_trace_ftrace_zero_gen",
     ":perfetto_protos_perfetto_trace_gpu_cpp_gen",
+    ":perfetto_protos_perfetto_trace_gpu_lite_gen",
     ":perfetto_protos_perfetto_trace_gpu_zero_gen",
     ":perfetto_protos_perfetto_trace_interned_data_cpp_gen",
+    ":perfetto_protos_perfetto_trace_interned_data_lite_gen",
     ":perfetto_protos_perfetto_trace_interned_data_zero_gen",
     ":perfetto_protos_perfetto_trace_minimal_cpp_gen",
+    ":perfetto_protos_perfetto_trace_minimal_lite_gen",
     ":perfetto_protos_perfetto_trace_minimal_zero_gen",
     ":perfetto_protos_perfetto_trace_non_minimal_cpp_gen",
+    ":perfetto_protos_perfetto_trace_non_minimal_lite_gen",
     ":perfetto_protos_perfetto_trace_non_minimal_zero_gen",
     ":perfetto_protos_perfetto_trace_perfetto_cpp_gen",
+    ":perfetto_protos_perfetto_trace_perfetto_lite_gen",
     ":perfetto_protos_perfetto_trace_perfetto_zero_gen",
     ":perfetto_protos_perfetto_trace_power_cpp_gen",
+    ":perfetto_protos_perfetto_trace_power_lite_gen",
     ":perfetto_protos_perfetto_trace_power_zero_gen",
     ":perfetto_protos_perfetto_trace_processor_metrics_impl_zero_gen",
     ":perfetto_protos_perfetto_trace_processor_zero_gen",
     ":perfetto_protos_perfetto_trace_profiling_cpp_gen",
+    ":perfetto_protos_perfetto_trace_profiling_lite_gen",
     ":perfetto_protos_perfetto_trace_profiling_zero_gen",
     ":perfetto_protos_perfetto_trace_ps_cpp_gen",
+    ":perfetto_protos_perfetto_trace_ps_lite_gen",
     ":perfetto_protos_perfetto_trace_ps_zero_gen",
     ":perfetto_protos_perfetto_trace_sys_stats_cpp_gen",
+    ":perfetto_protos_perfetto_trace_sys_stats_lite_gen",
     ":perfetto_protos_perfetto_trace_sys_stats_zero_gen",
     ":perfetto_protos_perfetto_trace_system_info_cpp_gen",
+    ":perfetto_protos_perfetto_trace_system_info_lite_gen",
     ":perfetto_protos_perfetto_trace_system_info_zero_gen",
     ":perfetto_protos_perfetto_trace_track_event_cpp_gen",
+    ":perfetto_protos_perfetto_trace_track_event_lite_gen",
     ":perfetto_protos_perfetto_trace_track_event_zero_gen",
     ":perfetto_src_android_internal_headers",
     ":perfetto_src_android_internal_lazy_library_loader",
@@ -8728,6 +8764,7 @@
     ":perfetto_src_tracing_platform_impl",
     ":perfetto_src_tracing_test_test_support",
     ":perfetto_src_tracing_test_tracing_integration_test",
+    ":perfetto_src_tracing_unittests",
     ":perfetto_tools_sanitizers_unittests_sanitizers_unittests",
   ],
   shared_libs: [
@@ -8750,63 +8787,90 @@
   ],
   generated_headers: [
     "perfetto_protos_perfetto_common_cpp_gen_headers",
+    "perfetto_protos_perfetto_common_lite_gen_headers",
     "perfetto_protos_perfetto_common_zero_gen_headers",
     "perfetto_protos_perfetto_config_android_cpp_gen_headers",
+    "perfetto_protos_perfetto_config_android_lite_gen_headers",
     "perfetto_protos_perfetto_config_android_zero_gen_headers",
     "perfetto_protos_perfetto_config_cpp_gen_headers",
     "perfetto_protos_perfetto_config_ftrace_cpp_gen_headers",
+    "perfetto_protos_perfetto_config_ftrace_lite_gen_headers",
     "perfetto_protos_perfetto_config_ftrace_zero_gen_headers",
     "perfetto_protos_perfetto_config_gpu_cpp_gen_headers",
+    "perfetto_protos_perfetto_config_gpu_lite_gen_headers",
     "perfetto_protos_perfetto_config_gpu_zero_gen_headers",
     "perfetto_protos_perfetto_config_inode_file_cpp_gen_headers",
+    "perfetto_protos_perfetto_config_inode_file_lite_gen_headers",
     "perfetto_protos_perfetto_config_inode_file_zero_gen_headers",
     "perfetto_protos_perfetto_config_interceptors_cpp_gen_headers",
+    "perfetto_protos_perfetto_config_interceptors_lite_gen_headers",
     "perfetto_protos_perfetto_config_interceptors_zero_gen_headers",
+    "perfetto_protos_perfetto_config_lite_gen_headers",
     "perfetto_protos_perfetto_config_power_cpp_gen_headers",
+    "perfetto_protos_perfetto_config_power_lite_gen_headers",
     "perfetto_protos_perfetto_config_power_zero_gen_headers",
     "perfetto_protos_perfetto_config_process_stats_cpp_gen_headers",
+    "perfetto_protos_perfetto_config_process_stats_lite_gen_headers",
     "perfetto_protos_perfetto_config_process_stats_zero_gen_headers",
     "perfetto_protos_perfetto_config_profiling_cpp_gen_headers",
+    "perfetto_protos_perfetto_config_profiling_lite_gen_headers",
     "perfetto_protos_perfetto_config_profiling_zero_gen_headers",
     "perfetto_protos_perfetto_config_sys_stats_cpp_gen_headers",
+    "perfetto_protos_perfetto_config_sys_stats_lite_gen_headers",
     "perfetto_protos_perfetto_config_sys_stats_zero_gen_headers",
     "perfetto_protos_perfetto_config_track_event_cpp_gen_headers",
+    "perfetto_protos_perfetto_config_track_event_lite_gen_headers",
     "perfetto_protos_perfetto_config_track_event_zero_gen_headers",
     "perfetto_protos_perfetto_config_zero_gen_headers",
     "perfetto_protos_perfetto_ipc_cpp_gen_headers",
     "perfetto_protos_perfetto_ipc_ipc_gen_headers",
     "perfetto_protos_perfetto_ipc_wire_protocol_cpp_gen_headers",
     "perfetto_protos_perfetto_trace_android_cpp_gen_headers",
+    "perfetto_protos_perfetto_trace_android_lite_gen_headers",
     "perfetto_protos_perfetto_trace_android_zero_gen_headers",
     "perfetto_protos_perfetto_trace_chrome_cpp_gen_headers",
+    "perfetto_protos_perfetto_trace_chrome_lite_gen_headers",
     "perfetto_protos_perfetto_trace_chrome_zero_gen_headers",
     "perfetto_protos_perfetto_trace_filesystem_cpp_gen_headers",
+    "perfetto_protos_perfetto_trace_filesystem_lite_gen_headers",
     "perfetto_protos_perfetto_trace_filesystem_zero_gen_headers",
     "perfetto_protos_perfetto_trace_ftrace_cpp_gen_headers",
+    "perfetto_protos_perfetto_trace_ftrace_lite_gen_headers",
     "perfetto_protos_perfetto_trace_ftrace_zero_gen_headers",
     "perfetto_protos_perfetto_trace_gpu_cpp_gen_headers",
+    "perfetto_protos_perfetto_trace_gpu_lite_gen_headers",
     "perfetto_protos_perfetto_trace_gpu_zero_gen_headers",
     "perfetto_protos_perfetto_trace_interned_data_cpp_gen_headers",
+    "perfetto_protos_perfetto_trace_interned_data_lite_gen_headers",
     "perfetto_protos_perfetto_trace_interned_data_zero_gen_headers",
     "perfetto_protos_perfetto_trace_minimal_cpp_gen_headers",
+    "perfetto_protos_perfetto_trace_minimal_lite_gen_headers",
     "perfetto_protos_perfetto_trace_minimal_zero_gen_headers",
     "perfetto_protos_perfetto_trace_non_minimal_cpp_gen_headers",
+    "perfetto_protos_perfetto_trace_non_minimal_lite_gen_headers",
     "perfetto_protos_perfetto_trace_non_minimal_zero_gen_headers",
     "perfetto_protos_perfetto_trace_perfetto_cpp_gen_headers",
+    "perfetto_protos_perfetto_trace_perfetto_lite_gen_headers",
     "perfetto_protos_perfetto_trace_perfetto_zero_gen_headers",
     "perfetto_protos_perfetto_trace_power_cpp_gen_headers",
+    "perfetto_protos_perfetto_trace_power_lite_gen_headers",
     "perfetto_protos_perfetto_trace_power_zero_gen_headers",
     "perfetto_protos_perfetto_trace_processor_metrics_impl_zero_gen_headers",
     "perfetto_protos_perfetto_trace_processor_zero_gen_headers",
     "perfetto_protos_perfetto_trace_profiling_cpp_gen_headers",
+    "perfetto_protos_perfetto_trace_profiling_lite_gen_headers",
     "perfetto_protos_perfetto_trace_profiling_zero_gen_headers",
     "perfetto_protos_perfetto_trace_ps_cpp_gen_headers",
+    "perfetto_protos_perfetto_trace_ps_lite_gen_headers",
     "perfetto_protos_perfetto_trace_ps_zero_gen_headers",
     "perfetto_protos_perfetto_trace_sys_stats_cpp_gen_headers",
+    "perfetto_protos_perfetto_trace_sys_stats_lite_gen_headers",
     "perfetto_protos_perfetto_trace_sys_stats_zero_gen_headers",
     "perfetto_protos_perfetto_trace_system_info_cpp_gen_headers",
+    "perfetto_protos_perfetto_trace_system_info_lite_gen_headers",
     "perfetto_protos_perfetto_trace_system_info_zero_gen_headers",
     "perfetto_protos_perfetto_trace_track_event_cpp_gen_headers",
+    "perfetto_protos_perfetto_trace_track_event_lite_gen_headers",
     "perfetto_protos_perfetto_trace_track_event_zero_gen_headers",
     "perfetto_src_base_version_gen_h",
     "perfetto_src_ipc_test_messages_cpp_gen_headers",
diff --git a/BUILD b/BUILD
index 2579427..22940c9 100644
--- a/BUILD
+++ b/BUILD
@@ -524,6 +524,7 @@
         "include/perfetto/tracing/locked_handle.h",
         "include/perfetto/tracing/platform.h",
         "include/perfetto/tracing/trace_writer_base.h",
+        "include/perfetto/tracing/traced_value.h",
         "include/perfetto/tracing/tracing.h",
         "include/perfetto/tracing/tracing_backend.h",
         "include/perfetto/tracing/track.h",
@@ -1570,6 +1571,7 @@
         "src/tracing/internal/tracing_muxer_impl.h",
         "src/tracing/internal/track_event_internal.cc",
         "src/tracing/platform.cc",
+        "src/tracing/traced_value.cc",
         "src/tracing/tracing.cc",
         "src/tracing/track.cc",
         "src/tracing/track_event_category_registry.cc",
diff --git a/gn/perfetto_unittests.gni b/gn/perfetto_unittests.gni
index 44721ce..1a2b482 100644
--- a/gn/perfetto_unittests.gni
+++ b/gn/perfetto_unittests.gni
@@ -20,6 +20,7 @@
   "src/base:unittests",
   "src/protozero:unittests",
   "src/tracing/core:unittests",
+  "src/tracing:unittests",
   "src/profiling:unittests",
   "src/profiling/symbolizer:unittests",
 ]
diff --git a/include/perfetto/tracing/BUILD.gn b/include/perfetto/tracing/BUILD.gn
index 01f0f71..566636b 100644
--- a/include/perfetto/tracing/BUILD.gn
+++ b/include/perfetto/tracing/BUILD.gn
@@ -46,6 +46,7 @@
     "locked_handle.h",
     "platform.h",
     "trace_writer_base.h",
+    "traced_value.h",
     "tracing.h",
     "tracing_backend.h",
     "track.h",
diff --git a/include/perfetto/tracing/traced_value.h b/include/perfetto/tracing/traced_value.h
new file mode 100644
index 0000000..9369640
--- /dev/null
+++ b/include/perfetto/tracing/traced_value.h
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef INCLUDE_PERFETTO_TRACING_TRACED_VALUE_H_
+#define INCLUDE_PERFETTO_TRACING_TRACED_VALUE_H_
+
+#include "perfetto/base/compiler.h"
+#include "perfetto/base/export.h"
+#include "protos/perfetto/trace/track_event/debug_annotation.pbzero.h"
+
+#include <type_traits>
+#include <utility>
+
+namespace perfetto {
+
+class DebugAnnotation;
+
+// *** NOTE ***
+// This is work-in-progress and the examples below do not work yet.
+//
+//
+// These classes provide a JSON-inspired way to write structed data into traces.
+//
+// Each TracedValue can be consumed exactly once to write a value into a trace
+// using one of the Write* methods.
+//
+// Write* methods fall into two categories:
+// - Primitive types (int, string, bool, double, etc): they just write the
+//   provided value, consuming the TracedValue in the process.
+// - Complex types (arrays and dicts): they consume the TracedValue and
+//   return a corresponding scoped object (TracedArray or TracedDictionary).
+//   This scope then can be used to write multiple items into the container:
+//   TracedArray::AppendItem and TracedDictionary::AddItem return a new
+//   TracedValue which then can be used to write an element of the
+//   dictionary or array.
+//
+// To define how a custom class should be written into the trace, users should
+// define one of the two following functions:
+// - Foo::WriteIntoTrace(TracedValue) const
+//   (preferred for code which depends on perfetto directly)
+// - perfetto::TraceFormatTraits<T>::WriteIntoTrace(TracedValue, const T&);
+//   (should be used if T is defined in a library which doesn't know anything
+//   about tracing).
+//
+// After definiting a conversion method, the object can be used directly as a
+// TRACE_EVENT argument:
+//
+// Foo foo;
+// TRACE_EVENT("cat", "Event", "arg", foo);
+//
+// Examples:
+//
+// TRACE_EVENT("cat", "event", "params", [&](perfetto::TracedValue writer)
+// {
+//   auto dict = std::move(writer).WriteDictionary();
+//   dict->Add("param1", param1);
+//   dict->Add("param2", param2);
+//   ...
+//   dict->Add("paramN", paramN);
+//
+//   {
+//     auto inner_array = dict->AddArray("inner");
+//     inner_array->Append(value1);
+//     inner_array->Append(value2);
+//   }
+// });
+//
+// template <class T>
+// TraceFormatTraits<std::optional<T>>::WriteIntoTrace(
+//    TracedValue writer, const std::optional<T>& value) {
+//  if (!value) {
+//    std::move(writer).WritePointer(nullptr);
+//    return;
+//  }
+//  perfetto::Write(std::move(writer), *value);
+// }
+//
+// template <class T>
+// TraceFormatTraits<std::vector<T>>::WriteIntoTrace(
+//    TracedValue writer, const std::array<T>& value) {
+//  auto array = std::move(writer).WriteArray();
+//  for (const auto& item: value) {
+//    array_scope.Append(item);
+//  }
+// }
+//
+// class Foo {
+//   void WriteIntoTrace(TracedValue writer) const {
+//     auto dict = std::move(writer).WriteDictionary();
+//     dict->Set("key", 42);
+//     dict->Set("foo", "bar");
+//     dict->Set("member", member_);
+//   }
+// }
+class TracedArray;
+class TracedDictionary;
+
+class PERFETTO_EXPORT TracedValue {
+ public:
+  TracedValue(const TracedValue&) = delete;
+  TracedValue& operator=(const TracedValue&) = delete;
+  TracedValue& operator=(TracedValue&&) = delete;
+  TracedValue(TracedValue&&) = default;
+  ~TracedValue() = default;
+
+  void WriteInt64(int64_t value) &&;
+  void WriteUInt64(uint64_t value) &&;
+  void WriteDouble(double value) &&;
+  void WriteBoolean(bool value) &&;
+  void WriteString(const char*) &&;
+  void WriteString(const char*, size_t len) &&;
+  void WriteString(const std::string&) &&;
+  void WritePointer(const void* value) &&;
+
+  // Rules for writing nested dictionaries and arrays:
+  // - Only one scope (TracedArray, TracedDictionary or TracedValue) can be
+  // active at the same time. It's only allowed to call methods on the active
+  // scope.
+  // - When a scope creates a nested scope, the new scope becomes active.
+  // - When a scope is destroyed, it's parent scope becomes active again.
+  //
+  // Typically users will have to create a scope only at the beginning of a
+  // conversion function and this scope should be destroyed at the end of it.
+  // TracedArray::Append and TracedDictionary::Add create, write and complete
+  // inner scopes automatically.
+
+  // Scope which allows multiple values to be appended.
+  TracedArray WriteArray() && PERFETTO_WARN_UNUSED_RESULT;
+
+  // Scope which allows multiple key-value pairs to be added.
+  TracedDictionary WriteDictionary() && PERFETTO_WARN_UNUSED_RESULT;
+
+  static TracedValue CreateForTest(protos::pbzero::DebugAnnotation*);
+
+ private:
+  friend class TracedArray;
+  friend class TracedDictionary;
+
+  inline explicit TracedValue(protos::pbzero::DebugAnnotation* root_context)
+      : root_context_(root_context) {}
+  inline explicit TracedValue(
+      protos::pbzero::DebugAnnotation::NestedValue* nested_context)
+      : nested_context_(nested_context) {}
+
+  // Temporary support for perfetto::DebugAnnotation C++ class before it's going
+  // to be replaced by TracedValue.
+  // TODO(altimin): Convert v8 to use TracedValue directly and delete it.
+  friend class DebugAnnotation;
+
+  // Only one of them can be null.
+  // TODO(altimin): replace DebugAnnotation with something that doesn't require
+  // this duplication.
+  protos::pbzero::DebugAnnotation* root_context_ = nullptr;
+  protos::pbzero::DebugAnnotation::NestedValue* nested_context_ = nullptr;
+};
+
+class TracedArray {
+ public:
+  TracedArray(const TracedArray&) = delete;
+  TracedArray& operator=(const TracedArray&) = delete;
+  TracedArray& operator=(TracedArray&&) = delete;
+  TracedArray(TracedArray&&) = default;
+  ~TracedArray() { value_->Finalize(); }
+
+  TracedValue AppendItem();
+
+  TracedDictionary AppendDictionary() PERFETTO_WARN_UNUSED_RESULT;
+  TracedArray AppendArray();
+
+ private:
+  friend class TracedValue;
+
+  inline explicit TracedArray(
+      protos::pbzero::DebugAnnotation::NestedValue* value)
+      : value_(value) {}
+
+  protos::pbzero::DebugAnnotation::NestedValue* value_;
+};
+
+class TracedDictionary {
+ public:
+  TracedDictionary(const TracedDictionary&) = delete;
+  TracedDictionary& operator=(const TracedDictionary&) = delete;
+  TracedDictionary& operator=(TracedDictionary&&) = delete;
+  TracedDictionary(TracedDictionary&&) = default;
+  ~TracedDictionary() {}
+
+  TracedValue AddItem(const char* key);
+
+  TracedDictionary AddDictionary(const char* key);
+  TracedArray AddArray(const char* key);
+
+ private:
+  friend class TracedValue;
+
+  inline explicit TracedDictionary(
+      protos::pbzero::DebugAnnotation::NestedValue* value)
+      : value_(value) {}
+
+  protos::pbzero::DebugAnnotation::NestedValue* value_;
+};
+
+}  // namespace perfetto
+
+#endif  // INCLUDE_PERFETTO_TRACING_TRACED_VALUE_H_
diff --git a/src/tracing/BUILD.gn b/src/tracing/BUILD.gn
index b43466f..bfbac75 100644
--- a/src/tracing/BUILD.gn
+++ b/src/tracing/BUILD.gn
@@ -108,6 +108,7 @@
     "internal/tracing_muxer_impl.h",
     "internal/track_event_internal.cc",
     "platform.cc",
+    "traced_value.cc",
     "tracing.cc",
     "track.cc",
     "track_event_category_registry.cc",
@@ -124,6 +125,34 @@
   }
 }
 
+perfetto_unittest_source_set("unittests") {
+  testonly = true
+  deps = [
+    "../../protos/perfetto/trace:lite",
+    "../../protos/perfetto/trace/track_event:lite",
+    "../base",
+    "../base:test_support",
+    "test:test_support",
+  ]
+
+  sources = []
+
+  # TODO(primiano): remove the build_with_chromium conditional once the root
+  # //BUILD.gn:libperfetto (in chromium) stops adding tracing:platform_fake.
+  # The problem is the following: in chrome builds we end up with duplicate
+  # symbol definitions in the test because both platorm (impl and fake) are
+  # present: impl added here and fake coming from chromium's base (full path:
+  # perfetto_unittests -> //(chromium)base:test_support -> //(chromium)base
+  # -> libperfetto -> platform_fake.
+  if (!build_with_chromium) {
+    deps += [
+      ":client_api_without_backends",
+      ":platform_impl",
+    ]
+    sources += [ "traced_value_unittest.cc" ]
+  }
+}
+
 # System backend: connects to an external "traced" instance via a UNIX socket.
 # Requires the IPC layer and is supported only on posix systems.
 if (enable_perfetto_ipc) {
diff --git a/src/tracing/traced_value.cc b/src/tracing/traced_value.cc
new file mode 100644
index 0000000..c113388
--- /dev/null
+++ b/src/tracing/traced_value.cc
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "perfetto/tracing/traced_value.h"
+
+#include "perfetto/base/logging.h"
+#include "perfetto/tracing/debug_annotation.h"
+#include "protos/perfetto/trace/track_event/debug_annotation.pbzero.h"
+
+namespace perfetto {
+
+// static
+TracedValue TracedValue::CreateForTest(
+    protos::pbzero::DebugAnnotation* context) {
+  return TracedValue(context);
+}
+
+void TracedValue::WriteInt64(int64_t value) && {
+  if (nested_context_) {
+    nested_context_->set_int_value(value);
+  } else {
+    root_context_->set_int_value(value);
+  }
+}
+
+void TracedValue::WriteUInt64(uint64_t value) && {
+  if (nested_context_) {
+    nested_context_->set_int_value(static_cast<int64_t>(value));
+  } else {
+    root_context_->set_uint_value(value);
+  }
+}
+
+void TracedValue::WriteDouble(double value) && {
+  if (nested_context_) {
+    nested_context_->set_double_value(value);
+  } else {
+    root_context_->set_double_value(value);
+  }
+}
+
+void TracedValue::WriteBoolean(bool value) && {
+  if (nested_context_) {
+    nested_context_->set_bool_value(value);
+  } else {
+    root_context_->set_bool_value(value);
+  }
+}
+
+void TracedValue::WriteString(const char* value) && {
+  if (nested_context_) {
+    nested_context_->set_string_value(value);
+  } else {
+    root_context_->set_string_value(value);
+  }
+}
+
+void TracedValue::WriteString(const std::string& value) && {
+  if (nested_context_) {
+    nested_context_->set_string_value(value);
+  } else {
+    root_context_->set_string_value(value);
+  }
+}
+
+void TracedValue::WritePointer(const void* value) && {
+  if (nested_context_) {
+    nested_context_->set_int_value(reinterpret_cast<int64_t>(value));
+  } else {
+    root_context_->set_uint_value(reinterpret_cast<uint64_t>(value));
+  }
+}
+
+TracedDictionary TracedValue::WriteDictionary() && {
+  if (nested_context_) {
+    PERFETTO_DCHECK(!nested_context_->is_finalized());
+    nested_context_->set_nested_type(
+        protos::pbzero::DebugAnnotation_NestedValue_NestedType_DICT);
+    return TracedDictionary(nested_context_);
+  } else {
+    PERFETTO_DCHECK(!root_context_->is_finalized());
+    protos::pbzero::DebugAnnotation::NestedValue* value =
+        root_context_->set_nested_value();
+    value->set_nested_type(
+        protos::pbzero::DebugAnnotation_NestedValue_NestedType_DICT);
+    return TracedDictionary(value);
+  }
+}
+
+TracedArray TracedValue::WriteArray() && {
+  if (nested_context_) {
+    PERFETTO_DCHECK(!nested_context_->is_finalized());
+    nested_context_->set_nested_type(
+        protos::pbzero::DebugAnnotation_NestedValue_NestedType_ARRAY);
+    return TracedArray(nested_context_);
+  } else {
+    PERFETTO_DCHECK(!root_context_->is_finalized());
+    protos::pbzero::DebugAnnotation::NestedValue* value =
+        root_context_->set_nested_value();
+    value->set_nested_type(
+        protos::pbzero::DebugAnnotation_NestedValue_NestedType_ARRAY);
+    return TracedArray(value);
+  }
+}
+
+TracedValue TracedArray::AppendItem() {
+  return TracedValue(value_->add_array_values());
+}
+
+TracedDictionary TracedArray::AppendDictionary() {
+  return AppendItem().WriteDictionary();
+}
+
+TracedArray TracedArray::AppendArray() {
+  return AppendItem().WriteArray();
+}
+
+TracedValue TracedDictionary::AddItem(const char* key) {
+  value_->add_dict_keys(key);
+  return TracedValue(value_->add_dict_values());
+}
+
+TracedDictionary TracedDictionary::AddDictionary(const char* key) {
+  return AddItem(key).WriteDictionary();
+}
+
+TracedArray TracedDictionary::AddArray(const char* key) {
+  return AddItem(key).WriteArray();
+}
+
+}  // namespace perfetto
diff --git a/src/tracing/traced_value_unittest.cc b/src/tracing/traced_value_unittest.cc
new file mode 100644
index 0000000..0fe358b
--- /dev/null
+++ b/src/tracing/traced_value_unittest.cc
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "perfetto/tracing/traced_value.h"
+
+#include <array>
+#include <deque>
+#include <forward_list>
+#include <map>
+#include <queue>
+#include <set>
+#include <sstream>
+#include <stack>
+#include <unordered_map>
+#include <unordered_set>
+
+#include "perfetto/protozero/scattered_heap_buffer.h"
+#include "perfetto/tracing/debug_annotation.h"
+#include "perfetto/tracing/track_event.h"
+#include "protos/perfetto/trace/track_event/debug_annotation.gen.h"
+#include "protos/perfetto/trace/track_event/debug_annotation.pb.h"
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto {
+
+namespace {
+
+void WriteAsJSON(const protos::DebugAnnotation::NestedValue& value,
+                 std::stringstream& ss) {
+  if (value.nested_type() ==
+      protos::DebugAnnotation_NestedValue_NestedType_DICT) {
+    ss << "{";
+    for (int i = 0; i < value.dict_keys_size() && i < value.dict_values_size();
+         ++i) {
+      if (i > 0)
+        ss << ",";
+      ss << value.dict_keys(i);
+      ss << ":";
+      WriteAsJSON(value.dict_values(i), ss);
+    }
+    ss << "}";
+    return;
+  } else if (value.nested_type() ==
+             protos::DebugAnnotation_NestedValue_NestedType_ARRAY) {
+    ss << "[";
+    for (int i = 0; i < value.array_values_size(); ++i) {
+      if (i > 0)
+        ss << ",";
+      WriteAsJSON(value.array_values(i), ss);
+    }
+    ss << "]";
+    return;
+  } else if (value.has_int_value()) {
+    ss << value.int_value();
+    return;
+  } else if (value.has_double_value()) {
+    ss << value.double_value();
+    return;
+  } else if (value.has_bool_value()) {
+    ss << static_cast<bool>(value.bool_value());
+    return;
+  } else if (value.has_string_value()) {
+    ss << value.string_value();
+    return;
+  }
+}
+
+void WriteAsJSON(const protos::DebugAnnotation& value, std::stringstream& ss) {
+  if (value.has_bool_value()) {
+    ss << static_cast<bool>(value.bool_value());
+    return;
+  } else if (value.has_uint_value()) {
+    ss << value.uint_value();
+    return;
+  } else if (value.has_int_value()) {
+    ss << value.int_value();
+    return;
+  } else if (value.has_double_value()) {
+    ss << value.double_value();
+    return;
+  } else if (value.has_string_value()) {
+    ss << value.string_value();
+    return;
+  } else if (value.has_pointer_value()) {
+    ss << value.pointer_value();
+    return;
+  } else if (value.has_nested_value()) {
+    WriteAsJSON(value.nested_value(), ss);
+    return;
+  } else if (value.has_legacy_json_value()) {
+    ss << value.legacy_json_value();
+    return;
+  }
+}
+
+std::string MessageToJSON(const std::string& data) {
+  std::stringstream ss;
+  protos::DebugAnnotation result;
+  result.ParseFromString(data);
+  WriteAsJSON(result, ss);
+  return ss.str();
+}
+
+}  // namespace
+
+TEST(TracedValueTest, FlatDictionary_Explicit) {
+  protozero::HeapBuffered<protos::pbzero::DebugAnnotation> message;
+  {
+    auto dict = TracedValue::CreateForTest(message.get()).WriteDictionary();
+    dict.AddItem("bool").WriteBoolean(true);
+    dict.AddItem("double").WriteDouble(0.0);
+    dict.AddItem("int").WriteInt64(2014);
+    dict.AddItem("string").WriteString("string");
+    dict.AddItem("ptr").WritePointer(reinterpret_cast<void*>(0x1234));
+  }
+  EXPECT_EQ("{bool:1,double:0,int:2014,string:string,ptr:4660}",
+            MessageToJSON(message.SerializeAsString()));
+}
+
+TEST(TracedValueTest, Hierarchy_Explicit) {
+  protozero::HeapBuffered<protos::pbzero::DebugAnnotation> message;
+  {
+    auto root_dict =
+        TracedValue::CreateForTest(message.get()).WriteDictionary();
+    {
+      auto array = root_dict.AddItem("a1").WriteArray();
+      array.AppendItem().WriteInt64(1);
+      array.AppendItem().WriteBoolean(true);
+      {
+        auto dict = array.AppendItem().WriteDictionary();
+        dict.AddItem("i2").WriteInt64(3);
+      }
+    }
+    root_dict.AddItem("b0").WriteBoolean(true);
+    root_dict.AddItem("d0").WriteDouble(0.0);
+    {
+      auto dict1 = root_dict.AddItem("dict1").WriteDictionary();
+      {
+        auto dict2 = dict1.AddItem("dict2").WriteDictionary();
+        dict2.AddItem("b2").WriteBoolean(false);
+      }
+      dict1.AddItem("i1").WriteInt64(2014);
+      dict1.AddItem("s1").WriteString("foo");
+    }
+    root_dict.AddItem("i0").WriteInt64(2014);
+    root_dict.AddItem("s0").WriteString("foo");
+  }
+
+  EXPECT_EQ(
+      "{"
+      "a1:[1,1,{i2:3}],"
+      "b0:1,"
+      "d0:0,"
+      "dict1:{dict2:{b2:0},i1:2014,s1:foo},"
+      "i0:2014,"
+      "s0:foo}",
+      MessageToJSON(message.SerializeAsString()));
+}
+
+}  // namespace perfetto