blob: ae402fec1e2338a4e0cb9c145ed610d19e6a57ad [file] [log] [blame]
#include <cmath>
#include <iostream>
#include <sstream>
#include "aos/json_to_flatbuffer.h"
namespace aos {
namespace {
using reflection::BaseType;
void IntToString(int64_t val, reflection::BaseType type,
std::stringstream *out) {
switch (type) {
case BaseType::Bool:
*out << (val ? "true" : "false");
break;
case BaseType::UByte:
*out << std::to_string(static_cast<uint8_t>(val));
break;
case BaseType::Byte:
*out << std::to_string(static_cast<int8_t>(val));
break;
case BaseType::Short:
*out << static_cast<int16_t>(val);
break;
case BaseType::UShort:
*out << static_cast<uint16_t>(val);
break;
case BaseType::Int:
*out << static_cast<int32_t>(val);
break;
case BaseType::UInt:
*out << static_cast<uint32_t>(val);
break;
case BaseType::Long:
*out << static_cast<int64_t>(val);
break;
case BaseType::ULong:
*out << static_cast<uint64_t>(val);
break;
default:
*out << "null";
}
}
void FloatToString(double val, reflection::BaseType type,
std::stringstream *out) {
if (std::isnan(val)) {
*out << "null";
return;
}
switch (type) {
case BaseType::Float:
out->precision(std::numeric_limits<float>::digits10);
*out << static_cast<float>(val);
break;
case BaseType::Double:
out->precision(std::numeric_limits<double>::digits10);
*out << val;
break;
default:
*out << "null";
}
}
template <typename ObjT>
void ObjectToString(
const reflection::Object *obj,
const flatbuffers::Vector<flatbuffers::Offset<reflection::Object>> *objects,
const flatbuffers::Vector<flatbuffers::Offset<reflection::Enum>> *enums,
const ObjT *object, size_t max_vector_size, std::stringstream *out,
bool multi_line = false, int tree_depth = 0);
// Get enum value name
const char *EnumToString(
int64_t enum_value,
const flatbuffers::Vector<flatbuffers::Offset<reflection::EnumVal>>
*values) {
// Replace with binary search? Enum values are pre-sorted.
for (auto iter = values->begin(); iter != values->end(); iter++) {
if (enum_value == iter->value()) {
return iter->name()->c_str();
}
}
return nullptr;
}
// Convert integer to string, checking if it is an enum.
void IntOrEnumToString(
int64_t val, const reflection::Type *type,
const flatbuffers::Vector<flatbuffers::Offset<reflection::Enum>> *enums,
std::stringstream *out) {
// Check if integer is an enum and print string, otherwise fallback to
// printing as int.
if (type->index() > -1 && type->index() < (int32_t)enums->size()) {
const reflection::Enum *enum_props = enums->Get(type->index());
if (!enum_props->is_union()) {
const char *value_string = EnumToString(val, enum_props->values());
if (value_string != nullptr) {
*out << '"' << value_string << '"';
} else {
*out << val;
}
}
} else {
if (type->base_type() == BaseType::Vector ||
type->base_type() == BaseType::Array) {
IntToString(val, type->element(), out);
} else {
IntToString(val, type->base_type(), out);
}
}
}
// Adds a newline and indents
// Every increment in tree depth is two spaces
void AddWrapping(std::stringstream *out, int tree_depth) {
*out << "\n";
for (int i = 0; i < tree_depth; i++) {
*out << " ";
}
}
// Detects if a field should trigger wrapping of the parent object.
bool ShouldCauseWrapping(reflection::BaseType type) {
switch (type) {
case BaseType::Vector:
case BaseType::Obj:
return true;
default:
return false;
}
}
// Print field in flatbuffer table. Field must be populated.
template <typename ObjT>
void FieldToString(
const ObjT *table, const reflection::Field *field,
const flatbuffers::Vector<flatbuffers::Offset<reflection::Object>> *objects,
const flatbuffers::Vector<flatbuffers::Offset<reflection::Enum>> *enums,
size_t max_vector_size, std::stringstream *out, bool multi_line,
int tree_depth) {
const reflection::Type *type = field->type();
switch (type->base_type()) {
case BaseType::Bool:
case BaseType::UByte:
case BaseType::Byte:
case BaseType::Short:
case BaseType::UShort:
case BaseType::Int:
case BaseType::UInt:
case BaseType::Long:
case BaseType::ULong:
IntOrEnumToString(GetAnyFieldI(*table, *field), type, enums, out);
break;
case BaseType::Float:
case BaseType::Double:
FloatToString(GetAnyFieldF(*table, *field), type->base_type(), out);
break;
case BaseType::String:
if constexpr (std::is_same<flatbuffers::Table, ObjT>()) {
std::string str = flatbuffers::GetFieldS(*table, *field)->str();
std::string out_str;
out_str.reserve(str.size());
for (char c : str) {
// out_str += c;
switch (c) {
case '"':
out_str += "\\\"";
break;
case '\\':
out_str += "\\\\";
break;
case '\b':
out_str += "\\b";
break;
case '\f':
out_str += "\\f";
break;
case '\n':
out_str += "\\n";
break;
case '\r':
out_str += "\\r";
break;
case '\t':
out_str += "\\t";
break;
default:
out_str += c;
}
}
*out << '"' << out_str << '"';
} else {
*out << "null";
}
break;
case BaseType::Vector: {
if constexpr (std::is_same<flatbuffers::Table, ObjT>()) {
const flatbuffers::VectorOfAny *vector =
flatbuffers::GetFieldAnyV(*table, *field);
reflection::BaseType elem_type = type->element();
if (vector->size() > max_vector_size) {
*out << "[ ... " << vector->size() << " elements ... ]";
break;
}
bool wrap = false;
const int child_tree_depth = tree_depth + 1;
if (multi_line) {
wrap = ShouldCauseWrapping(elem_type);
}
*out << '[';
for (flatbuffers::uoffset_t i = 0; i < vector->size(); ++i) {
if (i != 0) {
if (wrap) {
*out << ",";
} else {
*out << ", ";
}
}
if (wrap) {
AddWrapping(out, child_tree_depth);
}
if (flatbuffers::IsInteger(elem_type)) {
IntOrEnumToString(
flatbuffers::GetAnyVectorElemI(vector, elem_type, i), type,
enums, out);
} else if (flatbuffers::IsFloat(elem_type)) {
FloatToString(flatbuffers::GetAnyVectorElemF(vector, elem_type, i),
elem_type, out);
} else if (elem_type == BaseType::String) {
*out << '"' << flatbuffers::GetAnyVectorElemS(vector, elem_type, i)
<< '"';
} else if (elem_type == BaseType::Obj) {
if (type->index() > -1 &&
type->index() < (int32_t)objects->size()) {
if (objects->Get(type->index())->is_struct()) {
ObjectToString(
objects->Get(type->index()), objects, enums,
flatbuffers::GetAnyVectorElemAddressOf<
const flatbuffers::Struct>(
vector, i, objects->Get(type->index())->bytesize()),
max_vector_size, out, multi_line, child_tree_depth);
} else {
ObjectToString(objects->Get(type->index()), objects, enums,
flatbuffers::GetAnyVectorElemPointer<
const flatbuffers::Table>(vector, i),
max_vector_size, out, multi_line,
child_tree_depth);
}
}
}
}
if (wrap) {
AddWrapping(out, tree_depth);
}
*out << ']';
} else {
*out << "null";
}
} break;
case BaseType::Obj: {
if (type->index() > -1 && type->index() < (int32_t)objects->size()) {
if (objects->Get(type->index())->is_struct()) {
ObjectToString(objects->Get(type->index()), objects, enums,
flatbuffers::GetFieldStruct(*table, *field),
max_vector_size, out, multi_line, tree_depth);
} else if constexpr (std::is_same<flatbuffers::Table, ObjT>()) {
ObjectToString(objects->Get(type->index()), objects, enums,
flatbuffers::GetFieldT(*table, *field),
max_vector_size, out, multi_line, tree_depth);
}
} else {
*out << "null";
}
} break;
default:
*out << "null";
}
}
// Prints flatbuffer table or struct given list of possible child objects and
// enums. Prints "null" if the child object type is not found.
template <typename ObjT>
void ObjectToString(
const reflection::Object *obj,
const flatbuffers::Vector<flatbuffers::Offset<reflection::Object>> *objects,
const flatbuffers::Vector<flatbuffers::Offset<reflection::Enum>> *enums,
const ObjT *object, size_t max_vector_size, std::stringstream *out,
bool multi_line, int tree_depth) {
static_assert(std::is_same<flatbuffers::Table, ObjT>() ||
std::is_same<flatbuffers::Struct, ObjT>(),
"Type must be either flatbuffer table or struct");
bool print_sep = false;
const int child_tree_depth = tree_depth + 1;
bool wrap = false;
if (multi_line) {
// Check whether this object has objects, vectors, or floats inside of it
for (const reflection::Field *field : *obj->fields()) {
if (ShouldCauseWrapping(field->type()->base_type())) {
wrap = true;
break;
}
}
}
*out << '{';
for (const reflection::Field *field : *obj->fields()) {
// Check whether this object has the field populated (even for structs,
// which should have all fields populated)
if (object->GetAddressOf(field->offset())) {
if (print_sep) {
if (wrap) {
*out << ",";
} else {
*out << ", ";
}
} else {
print_sep = true;
}
if (wrap) {
AddWrapping(out, child_tree_depth);
}
*out << '"' << field->name()->c_str() << "\": ";
FieldToString(object, field, objects, enums, max_vector_size, out,
multi_line, child_tree_depth);
}
}
if (wrap) {
AddWrapping(out, tree_depth);
}
*out << '}';
}
} // namespace
std::string FlatbufferToJson(const reflection::Schema *schema,
const uint8_t *data, bool multi_line,
size_t max_vector_size) {
CHECK(schema != nullptr) << ": Need to provide a schema";
// It is pretty common to get passed in a nullptr when a test fails. Rather
// than CHECK, return a more user friendly result.
if (data == nullptr) {
return "null";
}
const flatbuffers::Table *table = flatbuffers::GetAnyRoot(data);
const reflection::Object *obj = schema->root_table();
std::stringstream out;
ObjectToString(obj, schema->objects(), schema->enums(), table,
max_vector_size, &out, multi_line);
return out.str();
}
} // namespace aos