| /* |
| * Copyright (C) 2025 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 SRC_TRACE_PROCESSOR_UTIL_JSON_SERIALIZER_H_ |
| #define SRC_TRACE_PROCESSOR_UTIL_JSON_SERIALIZER_H_ |
| |
| #include <algorithm> |
| #include <cmath> |
| #include <cstddef> |
| #include <cstdint> |
| #include <string> |
| #include <string_view> |
| #include <type_traits> |
| #include <vector> |
| |
| #include "perfetto/ext/base/dynamic_string_writer.h" |
| #include "perfetto/ext/base/string_view.h" |
| |
| namespace perfetto::trace_processor::json { |
| |
| // Low-level JSON serializer with state tracking. |
| // Handles comma insertion, nesting, and optional pretty-printing. |
| // |
| // For a higher-level callback-based API, see simple_json_serializer.h. |
| // |
| // Usage: |
| // JsonSerializer s; |
| // s.OpenObject(); |
| // s.Key("name"); |
| // s.StringValue("hello"); |
| // s.Key("values"); |
| // s.OpenArray(); |
| // s.NumberValue(1); |
| // s.NumberValue(2); |
| // s.CloseArray(); |
| // s.CloseObject(); |
| // std::string json = s.ToString(); |
| class JsonSerializer { |
| public: |
| enum Flags { |
| kNone = 0, |
| kPretty = 1 << 0, |
| }; |
| |
| explicit JsonSerializer(int flags = kNone) : flags_(flags) {} |
| |
| void OpenObject() { |
| if (is_array_scope()) { |
| if (!is_empty_scope()) { |
| writer_.AppendChar(','); |
| } |
| MaybeAppendNewline(); |
| MaybeAppendIndent(); |
| } |
| writer_.AppendChar('{'); |
| stack_.push_back(Scope{ScopeContext::kObject}); |
| } |
| |
| void CloseObject() { |
| bool needs_newline = !is_empty_scope(); |
| stack_.pop_back(); |
| if (needs_newline) { |
| MaybeAppendNewline(); |
| MaybeAppendIndent(); |
| } |
| MarkScopeAsNonEmpty(); |
| writer_.AppendChar('}'); |
| } |
| |
| void OpenArray() { |
| if (is_array_scope()) { |
| if (!is_empty_scope()) { |
| writer_.AppendChar(','); |
| } |
| MaybeAppendNewline(); |
| MaybeAppendIndent(); |
| } |
| writer_.AppendChar('['); |
| stack_.push_back(Scope{ScopeContext::kArray}); |
| } |
| |
| void CloseArray() { |
| bool needs_newline = !is_empty_scope(); |
| stack_.pop_back(); |
| if (needs_newline) { |
| MaybeAppendNewline(); |
| MaybeAppendIndent(); |
| } |
| writer_.AppendChar(']'); |
| MarkScopeAsNonEmpty(); |
| } |
| |
| void Key(std::string_view key) { |
| if (is_object_scope() && !is_empty_scope()) { |
| writer_.AppendChar(','); |
| } |
| MaybeAppendNewline(); |
| MaybeAppendIndent(); |
| AppendEscapedString(key); |
| writer_.AppendChar(':'); |
| MaybeAppendSpace(); |
| MarkScopeAsNonEmpty(); |
| } |
| |
| template <typename T> |
| void NumberValue(T v) { |
| if (is_array_scope() && !is_empty_scope()) { |
| writer_.AppendChar(','); |
| } |
| if (is_array_scope()) { |
| MaybeAppendNewline(); |
| MaybeAppendIndent(); |
| } |
| if constexpr (std::is_floating_point_v<T>) { |
| writer_.AppendDouble(static_cast<double>(v)); |
| } else if constexpr (std::is_signed_v<T>) { |
| writer_.AppendInt(static_cast<int64_t>(v)); |
| } else { |
| writer_.AppendUnsignedInt(static_cast<uint64_t>(v)); |
| } |
| MarkScopeAsNonEmpty(); |
| } |
| |
| void BoolValue(bool v) { |
| if (is_array_scope() && !is_empty_scope()) { |
| writer_.AppendChar(','); |
| } |
| if (is_array_scope()) { |
| MaybeAppendNewline(); |
| MaybeAppendIndent(); |
| } |
| writer_.AppendBool(v); |
| MarkScopeAsNonEmpty(); |
| } |
| |
| void FloatValue(float v) { DoubleValue(static_cast<double>(v)); } |
| |
| void DoubleValue(double v) { |
| if (is_array_scope() && !is_empty_scope()) { |
| writer_.AppendChar(','); |
| } |
| if (is_array_scope()) { |
| MaybeAppendNewline(); |
| MaybeAppendIndent(); |
| } |
| // Handle special values that JSON doesn't support - output as strings |
| if (std::isnan(v)) { |
| writer_.AppendLiteral("\"NaN\""); |
| } else if (std::isinf(v)) { |
| if (v > 0) { |
| writer_.AppendLiteral("\"Infinity\""); |
| } else { |
| writer_.AppendLiteral("\"-Infinity\""); |
| } |
| } else { |
| writer_.AppendDouble(v); |
| } |
| MarkScopeAsNonEmpty(); |
| } |
| |
| void StringValue(std::string_view v) { |
| if (is_array_scope() && !is_empty_scope()) { |
| writer_.AppendChar(','); |
| } |
| if (is_array_scope()) { |
| MaybeAppendNewline(); |
| MaybeAppendIndent(); |
| } |
| AppendEscapedString(v); |
| MarkScopeAsNonEmpty(); |
| } |
| |
| void NullValue() { |
| if (is_array_scope() && !is_empty_scope()) { |
| writer_.AppendChar(','); |
| } |
| if (is_array_scope()) { |
| MaybeAppendNewline(); |
| MaybeAppendIndent(); |
| } |
| writer_.AppendLiteral("null"); |
| MarkScopeAsNonEmpty(); |
| } |
| |
| std::string ToString() const { |
| auto sv = writer_.GetStringView(); |
| return std::string(sv.data(), sv.size()); |
| } |
| |
| base::StringView GetStringView() const { return writer_.GetStringView(); } |
| |
| // Clears internal state for reuse, preserving allocated memory. |
| void Clear() { |
| writer_.Clear(); |
| stack_.clear(); |
| } |
| |
| bool is_empty_scope() const { |
| return !stack_.empty() && stack_.back().is_empty; |
| } |
| |
| bool is_pretty() const { return flags_ & Flags::kPretty; } |
| |
| private: |
| enum class ScopeContext { |
| kObject, |
| kArray, |
| }; |
| |
| struct Scope { |
| ScopeContext ctx; |
| bool is_empty = true; |
| }; |
| |
| bool is_object_scope() const { |
| return !stack_.empty() && stack_.back().ctx == ScopeContext::kObject; |
| } |
| |
| bool is_array_scope() const { |
| return !stack_.empty() && stack_.back().ctx == ScopeContext::kArray; |
| } |
| |
| void MarkScopeAsNonEmpty() { |
| if (!stack_.empty()) { |
| stack_.back().is_empty = false; |
| } |
| } |
| |
| void MaybeAppendSpace() { |
| if (is_pretty()) { |
| writer_.AppendChar(' '); |
| } |
| } |
| |
| void MaybeAppendIndent() { |
| if (is_pretty()) { |
| writer_.AppendChar(' ', stack_.size() * 2); |
| } |
| } |
| |
| void MaybeAppendNewline() { |
| if (is_pretty()) { |
| writer_.AppendChar('\n'); |
| } |
| } |
| |
| // Escapes a string for JSON output and appends it directly to the writer. |
| // Includes the surrounding quotes and proper UTF-8 handling. |
| void AppendEscapedString(std::string_view raw) { |
| static const char hex_chars[] = "0123456789abcdef"; |
| writer_.AppendChar('"'); |
| for (size_t i = 0; i < raw.size(); ++i) { |
| char c = raw[i]; |
| switch (c) { |
| case '"': |
| case '\\': |
| writer_.AppendChar('\\'); |
| writer_.AppendChar(c); |
| break; |
| case '\n': |
| writer_.AppendLiteral("\\n"); |
| break; |
| case '\b': |
| writer_.AppendLiteral("\\b"); |
| break; |
| case '\f': |
| writer_.AppendLiteral("\\f"); |
| break; |
| case '\r': |
| writer_.AppendLiteral("\\r"); |
| break; |
| case '\t': |
| writer_.AppendLiteral("\\t"); |
| break; |
| default: |
| // ASCII characters between 0x20 (space) and 0x7e (tilde) are |
| // inserted directly. All others are escaped. |
| if (c >= 0x20 && c <= 0x7e) { |
| writer_.AppendChar(c); |
| } else { |
| unsigned char uc = static_cast<unsigned char>(c); |
| uint32_t codepoint = 0; |
| |
| // Compute the number of bytes in this UTF-8 sequence. |
| size_t extra = 1 + (uc >= 0xc0u) + (uc >= 0xe0u) + (uc >= 0xf0u); |
| |
| // Consume up to |extra| bytes but don't read out of bounds. |
| size_t stop = std::min(raw.size(), i + extra); |
| |
| // Extract bits from the first byte. |
| codepoint |= uc & (0xff >> (extra + 1)); |
| |
| // Extract remaining bits from continuation bytes. |
| for (size_t j = i + 1; j < stop; ++j) { |
| uc = static_cast<unsigned char>(raw[j]); |
| codepoint = (codepoint << 6) | (uc & 0x3f); |
| } |
| |
| // Update i to account for consumed bytes. |
| i = stop - 1; |
| |
| // JSON uses UTF-16 escapes. For BMP codepoints use \uXXXX. |
| // For supplementary codepoints use a surrogate pair. |
| if (codepoint <= 0xffff) { |
| writer_.AppendLiteral("\\u"); |
| writer_.AppendChar(hex_chars[(codepoint >> 12) & 0xf]); |
| writer_.AppendChar(hex_chars[(codepoint >> 8) & 0xf]); |
| writer_.AppendChar(hex_chars[(codepoint >> 4) & 0xf]); |
| writer_.AppendChar(hex_chars[(codepoint >> 0) & 0xf]); |
| } else { |
| uint32_t high = ((codepoint - 0x10000) >> 10) + 0xD800; |
| uint32_t low = (codepoint & 0x3ff) + 0xDC00; |
| writer_.AppendLiteral("\\u"); |
| writer_.AppendChar(hex_chars[(high >> 12) & 0xf]); |
| writer_.AppendChar(hex_chars[(high >> 8) & 0xf]); |
| writer_.AppendChar(hex_chars[(high >> 4) & 0xf]); |
| writer_.AppendChar(hex_chars[(high >> 0) & 0xf]); |
| writer_.AppendLiteral("\\u"); |
| writer_.AppendChar(hex_chars[(low >> 12) & 0xf]); |
| writer_.AppendChar(hex_chars[(low >> 8) & 0xf]); |
| writer_.AppendChar(hex_chars[(low >> 4) & 0xf]); |
| writer_.AppendChar(hex_chars[(low >> 0) & 0xf]); |
| } |
| } |
| break; |
| } |
| } |
| writer_.AppendChar('"'); |
| } |
| |
| int flags_; |
| mutable base::DynamicStringWriter writer_; |
| std::vector<Scope> stack_; |
| }; |
| |
| } // namespace perfetto::trace_processor::json |
| |
| #endif // SRC_TRACE_PROCESSOR_UTIL_JSON_SERIALIZER_H_ |