blob: 48200b797f10ed5db78c10caa862c70750fa37c4 [file] [log] [blame]
Austin Schuhd3936202020-04-07 20:11:07 -07001#include <cmath>
Tyler Chatow5e369a42019-11-23 11:57:31 -08002#include <iostream>
3#include <sstream>
4
5#include "aos/json_to_flatbuffer.h"
6
7namespace aos {
8
9namespace {
10
11using reflection::BaseType;
12
13void IntToString(int64_t val, reflection::BaseType type,
14 std::stringstream *out) {
15 switch (type) {
16 case BaseType::Bool:
17 *out << (val ? "true" : "false");
18 break;
19 case BaseType::UByte:
20 *out << std::to_string(static_cast<uint8_t>(val));
21 break;
22 case BaseType::Byte:
23 *out << std::to_string(static_cast<int8_t>(val));
24 break;
25 case BaseType::Short:
26 *out << static_cast<int16_t>(val);
27 break;
28 case BaseType::UShort:
29 *out << static_cast<uint16_t>(val);
30 break;
31 case BaseType::Int:
32 *out << static_cast<int32_t>(val);
33 break;
34 case BaseType::UInt:
35 *out << static_cast<uint32_t>(val);
36 break;
37 case BaseType::Long:
38 *out << static_cast<int64_t>(val);
39 break;
40 case BaseType::ULong:
41 *out << static_cast<uint64_t>(val);
42 break;
43 default:
44 *out << "null";
45 }
46}
47
48void FloatToString(double val, reflection::BaseType type,
49 std::stringstream *out) {
Austin Schuhd3936202020-04-07 20:11:07 -070050 if (std::isnan(val)) {
51 *out << "null";
52 return;
53 }
Tyler Chatow5e369a42019-11-23 11:57:31 -080054 switch (type) {
55 case BaseType::Float:
56 out->precision(std::numeric_limits<float>::digits10);
57 *out << static_cast<float>(val);
58 break;
59 case BaseType::Double:
60 out->precision(std::numeric_limits<double>::digits10);
61 *out << val;
62 break;
63 default:
64 *out << "null";
65 }
66}
67
68template <typename ObjT>
69void ObjectToString(
70 const reflection::Object *obj,
71 const flatbuffers::Vector<flatbuffers::Offset<reflection::Object>> *objects,
72 const flatbuffers::Vector<flatbuffers::Offset<reflection::Enum>> *enums,
Ravago Jonescf453ab2020-05-06 21:14:53 -070073 const ObjT *object, std::stringstream *out, JsonOptions json_options,
74 int tree_depth = 0);
Tyler Chatow5e369a42019-11-23 11:57:31 -080075
76// Get enum value name
77const char *EnumToString(
78 int64_t enum_value,
79 const flatbuffers::Vector<flatbuffers::Offset<reflection::EnumVal>>
80 *values) {
81 // Replace with binary search? Enum values are pre-sorted.
82 for (auto iter = values->begin(); iter != values->end(); iter++) {
83 if (enum_value == iter->value()) {
84 return iter->name()->c_str();
85 }
86 }
87 return nullptr;
88}
89
90// Convert integer to string, checking if it is an enum.
91void IntOrEnumToString(
92 int64_t val, const reflection::Type *type,
93 const flatbuffers::Vector<flatbuffers::Offset<reflection::Enum>> *enums,
94 std::stringstream *out) {
95 // Check if integer is an enum and print string, otherwise fallback to
96 // printing as int.
97 if (type->index() > -1 && type->index() < (int32_t)enums->size()) {
98 const reflection::Enum *enum_props = enums->Get(type->index());
99 if (!enum_props->is_union()) {
100 const char *value_string = EnumToString(val, enum_props->values());
101
102 if (value_string != nullptr) {
103 *out << '"' << value_string << '"';
Austin Schuhd3936202020-04-07 20:11:07 -0700104 } else {
105 *out << val;
Tyler Chatow5e369a42019-11-23 11:57:31 -0800106 }
107 }
108 } else {
109 if (type->base_type() == BaseType::Vector ||
110 type->base_type() == BaseType::Array) {
111 IntToString(val, type->element(), out);
112 } else {
113 IntToString(val, type->base_type(), out);
114 }
115 }
116}
117
Ravago Jonese8338b02020-04-16 15:43:00 -0700118// Adds a newline and indents
119// Every increment in tree depth is two spaces
120void AddWrapping(std::stringstream *out, int tree_depth) {
121 *out << "\n";
122 for (int i = 0; i < tree_depth; i++) {
123 *out << " ";
124 }
125}
126
127// Detects if a field should trigger wrapping of the parent object.
128bool ShouldCauseWrapping(reflection::BaseType type) {
129 switch (type) {
130 case BaseType::Vector:
131 case BaseType::Obj:
132 return true;
133 default:
134 return false;
135 }
136}
137
Tyler Chatow5e369a42019-11-23 11:57:31 -0800138// Print field in flatbuffer table. Field must be populated.
139template <typename ObjT>
140void FieldToString(
141 const ObjT *table, const reflection::Field *field,
142 const flatbuffers::Vector<flatbuffers::Offset<reflection::Object>> *objects,
143 const flatbuffers::Vector<flatbuffers::Offset<reflection::Enum>> *enums,
Ravago Jonescf453ab2020-05-06 21:14:53 -0700144 std::stringstream *out, JsonOptions json_options, int tree_depth) {
Tyler Chatow5e369a42019-11-23 11:57:31 -0800145 const reflection::Type *type = field->type();
146
147 switch (type->base_type()) {
148 case BaseType::Bool:
149 case BaseType::UByte:
150 case BaseType::Byte:
151 case BaseType::Short:
152 case BaseType::UShort:
153 case BaseType::Int:
154 case BaseType::UInt:
155 case BaseType::Long:
156 case BaseType::ULong:
157 IntOrEnumToString(GetAnyFieldI(*table, *field), type, enums, out);
158 break;
159 case BaseType::Float:
160 case BaseType::Double:
161 FloatToString(GetAnyFieldF(*table, *field), type->base_type(), out);
162 break;
163 case BaseType::String:
164 if constexpr (std::is_same<flatbuffers::Table, ObjT>()) {
165 std::string str = flatbuffers::GetFieldS(*table, *field)->str();
166 std::string out_str;
167 out_str.reserve(str.size());
168 for (char c : str) {
169 // out_str += c;
170 switch (c) {
171 case '"':
172 out_str += "\\\"";
173 break;
174 case '\\':
175 out_str += "\\\\";
176 break;
177 case '\b':
178 out_str += "\\b";
179 break;
180 case '\f':
181 out_str += "\\f";
182 break;
183 case '\n':
184 out_str += "\\n";
185 break;
186 case '\r':
187 out_str += "\\r";
188 break;
189 case '\t':
190 out_str += "\\t";
191 break;
192 default:
193 out_str += c;
194 }
195 }
196 *out << '"' << out_str << '"';
197 } else {
198 *out << "null";
199 }
200 break;
201 case BaseType::Vector: {
202 if constexpr (std::is_same<flatbuffers::Table, ObjT>()) {
203 const flatbuffers::VectorOfAny *vector =
204 flatbuffers::GetFieldAnyV(*table, *field);
205 reflection::BaseType elem_type = type->element();
206
Ravago Jonescf453ab2020-05-06 21:14:53 -0700207 if (vector->size() > json_options.max_vector_size) {
Austin Schuhd3936202020-04-07 20:11:07 -0700208 *out << "[ ... " << vector->size() << " elements ... ]";
209 break;
210 }
Ravago Jonese8338b02020-04-16 15:43:00 -0700211
212 bool wrap = false;
213 const int child_tree_depth = tree_depth + 1;
214
Ravago Jonescf453ab2020-05-06 21:14:53 -0700215 if (json_options.multi_line) {
Ravago Jonese8338b02020-04-16 15:43:00 -0700216 wrap = ShouldCauseWrapping(elem_type);
217 }
218
Tyler Chatow5e369a42019-11-23 11:57:31 -0800219 *out << '[';
220 for (flatbuffers::uoffset_t i = 0; i < vector->size(); ++i) {
221 if (i != 0) {
Ravago Jonese8338b02020-04-16 15:43:00 -0700222 if (wrap) {
223 *out << ",";
224 } else {
225 *out << ", ";
226 }
227 }
228 if (wrap) {
229 AddWrapping(out, child_tree_depth);
Tyler Chatow5e369a42019-11-23 11:57:31 -0800230 }
231 if (flatbuffers::IsInteger(elem_type)) {
232 IntOrEnumToString(
233 flatbuffers::GetAnyVectorElemI(vector, elem_type, i), type,
234 enums, out);
235 } else if (flatbuffers::IsFloat(elem_type)) {
236 FloatToString(flatbuffers::GetAnyVectorElemF(vector, elem_type, i),
237 elem_type, out);
238 } else if (elem_type == BaseType::String) {
239 *out << '"' << flatbuffers::GetAnyVectorElemS(vector, elem_type, i)
240 << '"';
241 } else if (elem_type == BaseType::Obj) {
242 if (type->index() > -1 &&
243 type->index() < (int32_t)objects->size()) {
244 if (objects->Get(type->index())->is_struct()) {
245 ObjectToString(
246 objects->Get(type->index()), objects, enums,
247 flatbuffers::GetAnyVectorElemAddressOf<
248 const flatbuffers::Struct>(
249 vector, i, objects->Get(type->index())->bytesize()),
Ravago Jonescf453ab2020-05-06 21:14:53 -0700250 out, json_options, child_tree_depth);
Tyler Chatow5e369a42019-11-23 11:57:31 -0800251 } else {
252 ObjectToString(objects->Get(type->index()), objects, enums,
253 flatbuffers::GetAnyVectorElemPointer<
254 const flatbuffers::Table>(vector, i),
Ravago Jonescf453ab2020-05-06 21:14:53 -0700255 out, json_options, child_tree_depth);
Tyler Chatow5e369a42019-11-23 11:57:31 -0800256 }
257 }
258 }
259 }
Ravago Jonese8338b02020-04-16 15:43:00 -0700260 if (wrap) {
261 AddWrapping(out, tree_depth);
262 }
Tyler Chatow5e369a42019-11-23 11:57:31 -0800263 *out << ']';
264 } else {
265 *out << "null";
266 }
267 } break;
268 case BaseType::Obj: {
269 if (type->index() > -1 && type->index() < (int32_t)objects->size()) {
270 if (objects->Get(type->index())->is_struct()) {
271 ObjectToString(objects->Get(type->index()), objects, enums,
Ravago Jonescf453ab2020-05-06 21:14:53 -0700272 flatbuffers::GetFieldStruct(*table, *field), out,
273 json_options, tree_depth);
Tyler Chatow5e369a42019-11-23 11:57:31 -0800274 } else if constexpr (std::is_same<flatbuffers::Table, ObjT>()) {
275 ObjectToString(objects->Get(type->index()), objects, enums,
Ravago Jonescf453ab2020-05-06 21:14:53 -0700276 flatbuffers::GetFieldT(*table, *field), out,
277 json_options, tree_depth);
Tyler Chatow5e369a42019-11-23 11:57:31 -0800278 }
279 } else {
280 *out << "null";
281 }
282 } break;
283 default:
284 *out << "null";
285 }
286}
287
288// Prints flatbuffer table or struct given list of possible child objects and
289// enums. Prints "null" if the child object type is not found.
290template <typename ObjT>
291void ObjectToString(
292 const reflection::Object *obj,
293 const flatbuffers::Vector<flatbuffers::Offset<reflection::Object>> *objects,
294 const flatbuffers::Vector<flatbuffers::Offset<reflection::Enum>> *enums,
Ravago Jonescf453ab2020-05-06 21:14:53 -0700295 const ObjT *object, std::stringstream *out, JsonOptions json_options,
296 int tree_depth) {
Tyler Chatow5e369a42019-11-23 11:57:31 -0800297 static_assert(std::is_same<flatbuffers::Table, ObjT>() ||
298 std::is_same<flatbuffers::Struct, ObjT>(),
299 "Type must be either flatbuffer table or struct");
300 bool print_sep = false;
Ravago Jonese8338b02020-04-16 15:43:00 -0700301
302 const int child_tree_depth = tree_depth + 1;
303
304 bool wrap = false;
Ravago Jonescf453ab2020-05-06 21:14:53 -0700305 if (json_options.multi_line) {
Ravago Jonese8338b02020-04-16 15:43:00 -0700306 // Check whether this object has objects, vectors, or floats inside of it
307 for (const reflection::Field *field : *obj->fields()) {
308 if (ShouldCauseWrapping(field->type()->base_type())) {
309 wrap = true;
310 break;
311 }
312 }
313 }
314
Tyler Chatow5e369a42019-11-23 11:57:31 -0800315 *out << '{';
316 for (const reflection::Field *field : *obj->fields()) {
317 // Check whether this object has the field populated (even for structs,
318 // which should have all fields populated)
319 if (object->GetAddressOf(field->offset())) {
320 if (print_sep) {
Ravago Jonese8338b02020-04-16 15:43:00 -0700321 if (wrap) {
322 *out << ",";
323 } else {
324 *out << ", ";
325 }
Tyler Chatow5e369a42019-11-23 11:57:31 -0800326 } else {
327 print_sep = true;
328 }
Ravago Jonese8338b02020-04-16 15:43:00 -0700329
330 if (wrap) {
331 AddWrapping(out, child_tree_depth);
332 }
333
Tyler Chatow5e369a42019-11-23 11:57:31 -0800334 *out << '"' << field->name()->c_str() << "\": ";
Ravago Jonescf453ab2020-05-06 21:14:53 -0700335 FieldToString(object, field, objects, enums, out, json_options,
336 child_tree_depth);
Tyler Chatow5e369a42019-11-23 11:57:31 -0800337 }
338 }
Ravago Jonese8338b02020-04-16 15:43:00 -0700339
340 if (wrap) {
341 AddWrapping(out, tree_depth);
342 }
343
Tyler Chatow5e369a42019-11-23 11:57:31 -0800344 *out << '}';
345}
346
347} // namespace
348
349std::string FlatbufferToJson(const reflection::Schema *schema,
Ravago Jonescf453ab2020-05-06 21:14:53 -0700350 const uint8_t *data, JsonOptions json_options) {
Austin Schuh7f99e472020-06-17 20:38:17 -0700351 CHECK(schema != nullptr) << ": Need to provide a schema";
352
353 // It is pretty common to get passed in a nullptr when a test fails. Rather
354 // than CHECK, return a more user friendly result.
355 if (data == nullptr) {
356 return "null";
357 }
358
Tyler Chatow5e369a42019-11-23 11:57:31 -0800359 const flatbuffers::Table *table = flatbuffers::GetAnyRoot(data);
360
361 const reflection::Object *obj = schema->root_table();
362
363 std::stringstream out;
364
Ravago Jonescf453ab2020-05-06 21:14:53 -0700365 ObjectToString(obj, schema->objects(), schema->enums(), table, &out,
366 json_options);
Tyler Chatow5e369a42019-11-23 11:57:31 -0800367
368 return out.str();
369}
370} // namespace aos