blob: 7616b0516b33e06d44b0848fc9f20e6ef5ba6f96 [file]
/*
* 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_