| /* |
| * 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. |
| */ |
| |
| #include "src/trace_processor/util/json_value.h" |
| |
| #include <cstddef> |
| #include <cstdint> |
| #include <string> |
| #include <string_view> |
| #include <utility> |
| #include <variant> |
| #include <vector> |
| |
| #include "perfetto/base/status.h" |
| #include "perfetto/ext/base/no_destructor.h" |
| #include "perfetto/ext/base/status_macros.h" |
| #include "perfetto/ext/base/status_or.h" |
| #include "src/trace_processor/util/json_parser.h" |
| #include "src/trace_processor/util/json_serializer.h" |
| |
| namespace perfetto::trace_processor::json { |
| |
| const Dom& NullDom() { |
| static base::NoDestructor<Dom> kNull; |
| return kNull.ref(); |
| } |
| |
| // Type constructor. |
| Dom::Dom(Type type) { |
| switch (type) { |
| case Type::kNull: |
| data_ = std::monostate{}; |
| break; |
| case Type::kBool: |
| data_ = false; |
| break; |
| case Type::kInt: |
| data_ = int64_t{0}; |
| break; |
| case Type::kUint: |
| data_ = uint64_t{0}; |
| break; |
| case Type::kReal: |
| data_ = 0.0; |
| break; |
| case Type::kString: |
| data_ = std::string{}; |
| break; |
| case Type::kArray: |
| data_ = Array{}; |
| break; |
| case Type::kObject: |
| data_ = Object{}; |
| break; |
| } |
| } |
| |
| Type Dom::type() const { |
| if (std::holds_alternative<std::monostate>(data_)) |
| return Type::kNull; |
| if (std::holds_alternative<bool>(data_)) |
| return Type::kBool; |
| if (std::holds_alternative<int64_t>(data_)) |
| return Type::kInt; |
| if (std::holds_alternative<uint64_t>(data_)) |
| return Type::kUint; |
| if (std::holds_alternative<double>(data_)) |
| return Type::kReal; |
| if (std::holds_alternative<std::string>(data_)) |
| return Type::kString; |
| if (std::holds_alternative<Array>(data_)) |
| return Type::kArray; |
| return Type::kObject; |
| } |
| |
| bool Dom::AsBool() const { |
| if (const auto* v = std::get_if<bool>(&data_)) |
| return *v; |
| if (const auto* v = std::get_if<int64_t>(&data_)) |
| return *v != 0; |
| if (const auto* v = std::get_if<uint64_t>(&data_)) |
| return *v != 0; |
| return false; |
| } |
| |
| int Dom::AsInt() const { |
| return static_cast<int>(AsInt64()); |
| } |
| |
| int64_t Dom::AsInt64() const { |
| if (const auto* v = std::get_if<int64_t>(&data_)) |
| return *v; |
| if (const auto* v = std::get_if<uint64_t>(&data_)) |
| return static_cast<int64_t>(*v); |
| if (const auto* v = std::get_if<double>(&data_)) |
| return static_cast<int64_t>(*v); |
| if (const auto* v = std::get_if<bool>(&data_)) |
| return *v ? 1 : 0; |
| return 0; |
| } |
| |
| uint64_t Dom::AsUint() const { |
| return AsUint64(); |
| } |
| |
| uint64_t Dom::AsUint64() const { |
| if (const auto* v = std::get_if<uint64_t>(&data_)) |
| return *v; |
| if (const auto* v = std::get_if<int64_t>(&data_)) |
| return static_cast<uint64_t>(*v); |
| if (const auto* v = std::get_if<double>(&data_)) |
| return static_cast<uint64_t>(*v); |
| if (const auto* v = std::get_if<bool>(&data_)) |
| return *v ? 1 : 0; |
| return 0; |
| } |
| |
| double Dom::AsDouble() const { |
| if (const auto* v = std::get_if<double>(&data_)) |
| return *v; |
| if (const auto* v = std::get_if<int64_t>(&data_)) |
| return static_cast<double>(*v); |
| if (const auto* v = std::get_if<uint64_t>(&data_)) |
| return static_cast<double>(*v); |
| if (const auto* v = std::get_if<bool>(&data_)) |
| return *v ? 1.0 : 0.0; |
| return 0.0; |
| } |
| |
| std::string Dom::AsString() const { |
| if (const auto* v = std::get_if<std::string>(&data_)) |
| return *v; |
| return std::string{}; |
| } |
| |
| const char* Dom::AsCString() const { |
| if (const auto* v = std::get_if<std::string>(&data_)) |
| return v->c_str(); |
| return ""; |
| } |
| |
| Dom& Dom::operator[](const char* key) { |
| return (*this)[std::string(key)]; |
| } |
| |
| Dom& Dom::operator[](const std::string& key) { |
| if (!IsObject()) { |
| data_ = Object{}; |
| } |
| auto& obj = std::get<Object>(data_); |
| auto it = obj.find(key); |
| if (it == obj.end()) { |
| obj.insert({key, Dom{}}); |
| it = obj.find(key); |
| } |
| return it->second; |
| } |
| |
| const Dom& Dom::operator[](const char* key) const { |
| return (*this)[std::string(key)]; |
| } |
| |
| const Dom& Dom::operator[](const std::string& key) const { |
| if (const auto* obj = std::get_if<Object>(&data_)) { |
| auto it = obj->find(key); |
| return it != obj->end() ? it->second : NullDom(); |
| } |
| return NullDom(); |
| } |
| |
| bool Dom::HasMember(const char* key) const { |
| return HasMember(std::string(key)); |
| } |
| |
| bool Dom::HasMember(const std::string& key) const { |
| if (const auto* obj = std::get_if<Object>(&data_)) { |
| return obj->find(key) != obj->end(); |
| } |
| return false; |
| } |
| |
| std::vector<std::string> Dom::GetMemberNames() const { |
| std::vector<std::string> names; |
| if (const auto* obj = std::get_if<Object>(&data_)) { |
| for (const auto& it : *obj) { |
| names.push_back(it.first); |
| } |
| } |
| return names; |
| } |
| |
| void Dom::RemoveMember(const char* key) { |
| RemoveMember(std::string(key)); |
| } |
| |
| void Dom::RemoveMember(const std::string& key) { |
| if (auto* obj = std::get_if<Object>(&data_)) { |
| obj->erase(key); |
| } |
| } |
| |
| Dom& Dom::operator[](size_t index) { |
| if (!IsArray()) { |
| data_ = Array{}; |
| } |
| auto& arr = std::get<Array>(data_); |
| if (index >= arr.size()) { |
| arr.resize(index + 1); |
| } |
| return arr[index]; |
| } |
| |
| const Dom& Dom::operator[](size_t index) const { |
| if (const auto* arr = std::get_if<Array>(&data_)) { |
| if (index < arr->size()) { |
| return (*arr)[index]; |
| } |
| } |
| return NullDom(); |
| } |
| |
| Dom& Dom::operator[](int index) { |
| return (*this)[static_cast<size_t>(index)]; |
| } |
| |
| const Dom& Dom::operator[](int index) const { |
| return (*this)[static_cast<size_t>(index)]; |
| } |
| |
| void Dom::Append(Dom&& value) { |
| if (!IsArray()) { |
| data_ = Array{}; |
| } |
| std::get<Array>(data_).push_back(std::move(value)); |
| } |
| |
| size_t Dom::size() const { |
| if (const auto* arr = std::get_if<Array>(&data_)) |
| return arr->size(); |
| if (const auto* obj = std::get_if<Object>(&data_)) |
| return obj->size(); |
| return 0; |
| } |
| |
| bool Dom::empty() const { |
| if (IsNull()) |
| return true; |
| if (const auto* arr = std::get_if<Array>(&data_)) |
| return arr->empty(); |
| if (const auto* obj = std::get_if<Object>(&data_)) |
| return obj->size() == 0; |
| if (const auto* str = std::get_if<std::string>(&data_)) |
| return str->empty(); |
| return false; |
| } |
| |
| void Dom::Clear() { |
| if (auto* arr = std::get_if<Array>(&data_)) |
| arr->clear(); |
| else if (auto* obj = std::get_if<Object>(&data_)) |
| obj->clear(); |
| } |
| |
| Dom::ArrayIterator Dom::begin() const { |
| if (const auto* arr = std::get_if<Array>(&data_)) |
| return ArrayIterator(arr->begin()); |
| return {}; |
| } |
| |
| Dom::ArrayIterator Dom::end() const { |
| if (const auto* arr = std::get_if<Array>(&data_)) |
| return ArrayIterator(arr->end()); |
| return {}; |
| } |
| |
| Dom Dom::Copy() const { |
| switch (type()) { |
| case Type::kNull: |
| return Dom{}; |
| case Type::kBool: |
| return Dom{AsBool()}; |
| case Type::kInt: |
| return Dom{AsInt64()}; |
| case Type::kUint: |
| return Dom{AsUint64()}; |
| case Type::kReal: |
| return Dom{AsDouble()}; |
| case Type::kString: |
| return Dom{AsString()}; |
| case Type::kArray: { |
| Dom result(Type::kArray); |
| if (const auto* arr = GetArray()) { |
| for (const auto& elem : *arr) { |
| result.Append(elem.Copy()); |
| } |
| } |
| return result; |
| } |
| case Type::kObject: { |
| Dom result(Type::kObject); |
| if (const auto* obj = GetObject()) { |
| for (const auto& it : *obj) { |
| result[it.first] = it.second.Copy(); |
| } |
| } |
| return result; |
| } |
| } |
| return Dom{}; |
| } |
| |
| namespace { |
| |
| void SerializeValue(const Dom& value, JsonSerializer& s) { |
| switch (value.type()) { |
| case Type::kNull: |
| s.NullValue(); |
| break; |
| case Type::kBool: |
| s.BoolValue(value.AsBool()); |
| break; |
| case Type::kInt: |
| s.NumberValue(value.AsInt64()); |
| break; |
| case Type::kUint: |
| s.NumberValue(value.AsUint64()); |
| break; |
| case Type::kReal: |
| s.DoubleValue(value.AsDouble()); |
| break; |
| case Type::kString: |
| s.StringValue(value.AsString()); |
| break; |
| case Type::kArray: |
| s.OpenArray(); |
| if (const auto* arr = value.GetArray()) { |
| for (const auto& elem : *arr) { |
| SerializeValue(elem, s); |
| } |
| } |
| s.CloseArray(); |
| break; |
| case Type::kObject: |
| s.OpenObject(); |
| if (const auto* obj = value.GetObject()) { |
| for (const auto& it : *obj) { |
| s.Key(it.first); |
| SerializeValue(it.second, s); |
| } |
| } |
| s.CloseObject(); |
| break; |
| } |
| } |
| |
| // Recursively parses JSON using the Iterator and builds a Dom. |
| base::StatusOr<Dom> ParseRecursive(Iterator& iter) { |
| const auto& val = iter.value(); |
| |
| // Handle primitive types. |
| if (std::holds_alternative<Null>(val)) { |
| return Dom{}; |
| } |
| if (const auto* b = std::get_if<bool>(&val)) { |
| return Dom{*b}; |
| } |
| if (const auto* i = std::get_if<int64_t>(&val)) { |
| return Dom{*i}; |
| } |
| if (const auto* d = std::get_if<double>(&val)) { |
| return Dom{*d}; |
| } |
| if (const auto* sv = std::get_if<std::string_view>(&val)) { |
| return Dom{std::string(*sv)}; |
| } |
| |
| // Handle objects. |
| if (std::holds_alternative<Object>(val)) { |
| Dom result(Type::kObject); |
| while (true) { |
| auto rc = iter.ParseAndRecurse(); |
| if (rc == ReturnCode::kEndOfScope) { |
| break; |
| } |
| if (rc != ReturnCode::kOk) { |
| return base::ErrStatus("Failed to parse object: %s", |
| iter.status().c_message()); |
| } |
| std::string key(iter.key()); |
| ASSIGN_OR_RETURN(Dom child, ParseRecursive(iter)); |
| result[key] = std::move(child); |
| } |
| return std::move(result); |
| } |
| |
| // Handle arrays. |
| if (std::holds_alternative<Array>(val)) { |
| Dom result(Type::kArray); |
| while (true) { |
| auto rc = iter.ParseAndRecurse(); |
| if (rc == ReturnCode::kEndOfScope) { |
| break; |
| } |
| if (rc != ReturnCode::kOk) { |
| return base::ErrStatus("Failed to parse array: %s", |
| iter.status().c_message()); |
| } |
| ASSIGN_OR_RETURN(Dom child, ParseRecursive(iter)); |
| result.Append(std::move(child)); |
| } |
| return std::move(result); |
| } |
| |
| return base::ErrStatus("Unknown JSON value type"); |
| } |
| |
| } // namespace |
| |
| std::string Serialize(const Dom& value) { |
| JsonSerializer s; |
| SerializeValue(value, s); |
| return s.ToString(); |
| } |
| |
| base::StatusOr<Dom> Parse(std::string_view json) { |
| if (json.empty()) { |
| return base::ErrStatus("Empty JSON input"); |
| } |
| |
| Iterator iter; |
| iter.Reset(json.data(), json.data() + json.size()); |
| |
| if (!iter.ParseStart()) { |
| return base::ErrStatus("Failed to start parsing: %s", |
| iter.status().c_message()); |
| } |
| |
| // Determine if we're parsing an object or array at the root. |
| const auto& parse_stack = iter.parse_stack(); |
| if (parse_stack.empty()) { |
| return base::ErrStatus("Empty parse stack after ParseStart"); |
| } |
| |
| Dom result; |
| if (parse_stack.back() == Iterator::ParseType::kObject) { |
| result = Dom(Type::kObject); |
| while (true) { |
| auto rc = iter.ParseAndRecurse(); |
| if (rc == ReturnCode::kEndOfScope) { |
| break; |
| } |
| if (rc != ReturnCode::kOk) { |
| return base::ErrStatus("Failed to parse object: %s", |
| iter.status().c_message()); |
| } |
| std::string key(iter.key()); |
| ASSIGN_OR_RETURN(Dom child, ParseRecursive(iter)); |
| result[key] = std::move(child); |
| } |
| } else { |
| result = Dom(Type::kArray); |
| while (true) { |
| auto rc = iter.ParseAndRecurse(); |
| if (rc == ReturnCode::kEndOfScope) { |
| break; |
| } |
| if (rc != ReturnCode::kOk) { |
| return base::ErrStatus("Failed to parse array: %s", |
| iter.status().c_message()); |
| } |
| ASSIGN_OR_RETURN(Dom child, ParseRecursive(iter)); |
| result.Append(std::move(child)); |
| } |
| } |
| |
| return std::move(result); |
| } |
| |
| } // namespace perfetto::trace_processor::json |