Add a minireflect based json parser.
This parser takes decent json and parses it into a flatbuffer. The
standard library for flatbuffers needs the full fbs definitions for all
the flatbuffers to do this job.
And add a flatbuffer to JSON function.
Change-Id: Ibc6dcd3fcbd7ac9cf9121d8258d1613d8d20661c
diff --git a/aos/BUILD b/aos/BUILD
index 2bd5791..83b8a18 100644
--- a/aos/BUILD
+++ b/aos/BUILD
@@ -1,4 +1,5 @@
load("//tools:environments.bzl", "mcu_cpus")
+load("@com_github_google_flatbuffers//:build_defs.bzl", "flatbuffer_cc_library")
load("//aos/build:queues.bzl", "queue_library")
filegroup(
@@ -409,3 +410,30 @@
],
visibility = ["//visibility:public"],
)
+
+flatbuffer_cc_library(
+ name = "json_to_flatbuffer_flatbuffer",
+ srcs = ["json_to_flatbuffer.fbs"],
+)
+
+cc_library(
+ name = "json_to_flatbuffer",
+ srcs = ["json_to_flatbuffer.cc"],
+ hdrs = ["json_to_flatbuffer.h"],
+ deps = [
+ "//aos/logging",
+ "@com_github_google_flatbuffers//:flatbuffers",
+ ],
+)
+
+cc_test(
+ name = "json_to_flatbuffer_test",
+ srcs = [
+ "json_to_flatbuffer_test.cc",
+ ],
+ deps = [
+ ":json_to_flatbuffer",
+ ":json_to_flatbuffer_flatbuffer",
+ "//aos/testing:googletest",
+ ],
+)
diff --git a/aos/json_to_flatbuffer.cc b/aos/json_to_flatbuffer.cc
new file mode 100644
index 0000000..d73089b
--- /dev/null
+++ b/aos/json_to_flatbuffer.cc
@@ -0,0 +1,1112 @@
+#include "aos/json_to_flatbuffer.h"
+
+#include <cstddef>
+#include "stdio.h"
+
+#include "aos/logging/logging.h"
+#include "flatbuffers/flatbuffers.h"
+#include "flatbuffers/minireflect.h"
+
+// TODO(austin): Can we just do an Offset<void> ? It doesn't matter, so maybe
+// just say that.
+//
+// TODO(austin): I've yet to see how to create an ET_UTYPE, so I don't know what
+// one is and how to test it. So everything rejects it.
+
+namespace aos {
+
+// Finds the field index in the table given the name.
+int FieldIndex(const flatbuffers::TypeTable *typetable,
+ const char *field_name) {
+ CHECK(typetable->values == nullptr);
+ for (size_t i = 0; i < typetable->num_elems; ++i) {
+ if (strcmp(field_name, typetable->names[i]) == 0) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+namespace {
+
+// Class to hold one of the 3 json types for an array.
+struct Element {
+ // The type.
+ enum class ElementType { INT, DOUBLE, OFFSET };
+
+ // Constructs an Element holding an integer.
+ Element(int64_t new_int_element)
+ : int_element(new_int_element), type(ElementType::INT) {}
+ // Constructs an Element holding an double.
+ Element(double new_double_element)
+ : double_element(new_double_element), type(ElementType::DOUBLE) {}
+ // Constructs an Element holding an Offset.
+ Element(flatbuffers::Offset<flatbuffers::String> new_offset_element)
+ : offset_element(new_offset_element), type(ElementType::OFFSET) {}
+
+ // Union for the various datatypes.
+ union {
+ int64_t int_element;
+ double double_element;
+ flatbuffers::Offset<flatbuffers::String> offset_element;
+ };
+
+ // And an enum signaling which one is in use.
+ ElementType type;
+};
+
+// Structure to represent a field element.
+struct FieldElement {
+ FieldElement(int new_field_index, int64_t int_element)
+ : element(int_element), field_index(new_field_index) {}
+ FieldElement(int new_field_index, double double_element)
+ : element(double_element), field_index(new_field_index) {}
+ FieldElement(int new_field_index,
+ flatbuffers::Offset<flatbuffers::String> offset_element)
+ : element(offset_element), field_index(new_field_index) {}
+
+ // Data to write.
+ Element element;
+ // Field index. The type table which this index is for is stored outside this
+ // object.
+ int field_index;
+};
+
+// Class to parse JSON into a flatbuffer.
+//
+// The basic strategy is that we need to do everything backwards. So we need to
+// build up what we need to do fully in memory, then do it.
+//
+// The driver for this is that strings need to be fully created before the
+// tables that use them. Same for sub messages. But, we only know we have them
+// all when the structure ends. So, store each sub message in a FieldElement
+// and put them in the table at the end when we finish up each message. Same
+// goes for vectors.
+class JsonParser {
+ public:
+ JsonParser() { fbb_.ForceDefaults(1); }
+ ~JsonParser() {}
+
+ // Parses the json into a flatbuffer. Returns either an empty vector on
+ // error, or a vector with the flatbuffer data in it.
+ ::std::vector<uint8_t> Parse(const char *data,
+ const flatbuffers::TypeTable *typetable) {
+ flatbuffers::uoffset_t end;
+ bool result = DoParse(typetable, data, &end);
+
+ if (result) {
+ // On success, finish the table and build the vector.
+ auto o = flatbuffers::Offset<flatbuffers::Table>(end);
+ fbb_.Finish(o);
+
+ const uint8_t *buf = fbb_.GetBufferPointer();
+ const int size = fbb_.GetSize();
+ return ::std::vector<uint8_t>(buf, buf + size);
+ } else {
+ // Otherwise return an empty vector.
+ return ::std::vector<uint8_t>();
+ }
+ }
+
+ private:
+ // Setters and getters for in_vector (at the current level of the stack)
+ bool in_vector() const { return stack_.back().in_vector; }
+ void set_in_vector(bool in_vector) { stack_.back().in_vector = in_vector; }
+
+ // Parses the flatbuffer. This is a second method so we can do easier
+ // cleanup at the top level. Returns true on success.
+ bool DoParse(const flatbuffers::TypeTable *typetable, const char *data,
+ flatbuffers::uoffset_t *table_end);
+
+ // Adds *_value for the provided field. If we are in a vector, queues the
+ // data up in vector_elements_. Returns true on success.
+ bool AddElement(int field_index, int64_t int_value);
+ bool AddElement(int field_index, double double_value);
+ bool AddElement(int field_index, const ::std::string &data);
+
+ // Adds a single element. This assumes that vectors have been dealt with
+ // already. Returns true on success.
+ bool AddSingleElement(const FieldElement &field_element,
+ ::std::vector<bool> *fields_in_use);
+ bool AddSingleElement(int field_index, int64_t int_value);
+ bool AddSingleElement(int field_index, double double_value);
+ bool AddSingleElement(
+ int field_index, flatbuffers::Offset<flatbuffers::String> offset_element);
+
+ const char *ElementaryTypeName(
+ const flatbuffers::ElementaryType elementary_type) {
+ return flatbuffers::ElementaryTypeNames()[elementary_type] + 3;
+ }
+
+ // Finishes a vector for the provided field index. Returns true on success.
+ bool FinishVector(int field_index);
+
+ // Pushes an element as part of a vector. Returns true on success.
+ bool PushElement(flatbuffers::ElementaryType elementary_type,
+ int64_t int_value);
+ bool PushElement(flatbuffers::ElementaryType elementary_type,
+ double double_value);
+ bool PushElement(flatbuffers::ElementaryType elementary_type,
+ flatbuffers::Offset<flatbuffers::String> offset_value);
+
+ flatbuffers::FlatBufferBuilder fbb_;
+
+ // This holds the state information that is needed as you recurse into
+ // nested structures.
+ struct FlatBufferContext {
+ // Type of the current type.
+ const flatbuffers::TypeTable *typetable;
+ // If true, we are parsing a vector.
+ bool in_vector;
+ // The field index of the current field.
+ int field_index;
+ // Name of the current field.
+ ::std::string field_name;
+
+ // Field elements that need to be inserted.
+ ::std::vector<FieldElement> elements;
+ };
+ ::std::vector<FlatBufferContext> stack_;
+
+ // For scalar types (not strings, and not nested tables), the vector ends
+ // up being implemented as a start and end, and a block of data. So we
+ // can't just push offsets in as we go. We either need to reproduce the
+ // logic inside flatbuffers, or build up vectors of the data. Vectors will
+ // be a bit of extra stack space, but whatever.
+ //
+ // Strings and nested structures are vectors of offsets.
+ // into the vector. Once you get to the end, you build up a vector and
+ // push that into the field.
+ ::std::vector<Element> vector_elements_;
+};
+
+bool JsonParser::DoParse(const flatbuffers::TypeTable *typetable,
+ const char *data, flatbuffers::uoffset_t *table_end) {
+ ::std::vector<const flatbuffers::TypeTable *> stack;
+
+ Tokenizer t(data);
+
+ // Main loop. Run until we get an end.
+ while (true) {
+ Tokenizer::TokenType token = t.Next();
+
+ switch (token) {
+ case Tokenizer::TokenType::kEnd:
+ if (stack_.size() != 0) {
+ printf("Failed to unwind stack all the way\n");
+ return false;
+ } else {
+ return true;
+ }
+ break;
+ case Tokenizer::TokenType::kError:
+ return false;
+ break;
+
+ case Tokenizer::TokenType::kStartObject: // {
+ if (stack_.size() == 0) {
+ stack_.push_back({typetable, false, -1, "", {}});
+ } else {
+ int field_index = stack_.back().field_index;
+
+ const flatbuffers::TypeCode &type_code =
+ stack_.back().typetable->type_codes[field_index];
+
+ if (type_code.base_type != flatbuffers::ET_SEQUENCE) {
+ printf("Field '%s' is not a sequence\n",
+ stack_.back().field_name.c_str());
+ return false;
+ }
+
+ flatbuffers::TypeFunction type_function =
+ stack_.back().typetable->type_refs[type_code.sequence_ref];
+
+ stack_.push_back({type_function(), false, -1, "", {}});
+ }
+ break;
+ case Tokenizer::TokenType::kEndObject: // }
+ if (stack_.size() == 0) {
+ // Somehow we popped more than we pushed. Error.
+ printf("Empty stack\n");
+ return false;
+ } else {
+ // End of a nested struct! Add it.
+ const flatbuffers::uoffset_t start = fbb_.StartTable();
+
+ ::std::vector<bool> fields_in_use(stack_.back().typetable->num_elems,
+ false);
+
+ for (const FieldElement &field_element : stack_.back().elements) {
+ AddSingleElement(field_element, &fields_in_use);
+ }
+
+ const flatbuffers::uoffset_t end = fbb_.EndTable(start);
+
+ // We now want to talk about the parent structure. Pop the child.
+ stack_.pop_back();
+
+ if (stack_.size() == 0) {
+ // Instead of queueing it up in the stack, return it through the
+ // passed in variable.
+ *table_end = end;
+ } else {
+ // And now we can add it.
+ const int field_index = stack_.back().field_index;
+
+ // Do the right thing if we are in a vector.
+ if (in_vector()) {
+ vector_elements_.emplace_back(
+ flatbuffers::Offset<flatbuffers::String>(end));
+ } else {
+ stack_.back().elements.emplace_back(
+ field_index, flatbuffers::Offset<flatbuffers::String>(end));
+ }
+ }
+ }
+ break;
+
+ case Tokenizer::TokenType::kStartArray: // [
+ if (stack_.size() == 0) {
+ // We don't support an array of structs at the root level.
+ return false;
+ }
+ // Sanity check that we aren't trying to make a vector of vectors.
+ if (in_vector()) {
+ return false;
+ }
+ set_in_vector(true);
+
+ break;
+ case Tokenizer::TokenType::kEndArray: { // ]
+ if (!in_vector()) {
+ return false;
+ }
+
+ const int field_index = stack_.back().field_index;
+
+ if (!FinishVector(field_index)) return false;
+
+ set_in_vector(false);
+ } break;
+
+ case Tokenizer::TokenType::kTrueValue: // true
+ case Tokenizer::TokenType::kFalseValue: // false
+ case Tokenizer::TokenType::kNumberValue: {
+ bool is_int = true;
+ double double_value;
+ long long int_value;
+ if (token == Tokenizer::TokenType::kTrueValue) {
+ int_value = 1;
+ } else if (token == Tokenizer::TokenType::kFalseValue) {
+ int_value = 0;
+ } else if (!t.FieldAsInt(&int_value)) {
+ if (t.FieldAsDouble(&double_value)) {
+ is_int = false;
+ } else {
+ fprintf(stderr, "Got a invalid number '%s'\n",
+ t.field_value().c_str());
+ return false;
+ }
+ }
+
+ const int field_index = stack_.back().field_index;
+
+ if (is_int) {
+ // No need to get too stressed about bool vs int. Convert them all.
+ int64_t val = int_value;
+ if (!AddElement(field_index, val)) return false;
+ } else {
+ if (!AddElement(field_index, double_value)) return false;
+ }
+ } break;
+ // TODO(austin): Need to detect int vs float.
+ /*
+ asdf
+ {
+ const int field_index = stack_.back().field_index;
+
+ } break;
+ */
+ case Tokenizer::TokenType::kStringValue: // string value
+ {
+ const int field_index = stack_.back().field_index;
+
+ if (!AddElement(field_index, t.field_value())) return false;
+ } break;
+ case Tokenizer::TokenType::kField: // field name
+ {
+ stack_.back().field_name = t.field_name();
+ stack_.back().field_index = FieldIndex(
+ stack_.back().typetable, stack_.back().field_name.c_str());
+
+ if (stack_.back().field_index == -1) {
+ printf("Invalid field name '%s'\n", stack_.back().field_name.c_str());
+ return false;
+ }
+ } break;
+ }
+ }
+ return false;
+}
+
+bool JsonParser::AddElement(int field_index, int64_t int_value) {
+ flatbuffers::TypeCode type_code =
+ stack_.back().typetable->type_codes[field_index];
+
+ if (type_code.is_vector != in_vector()) {
+ printf("Type and json disagree on if we are in a vector or not\n");
+ return false;
+ }
+
+ if (in_vector()) {
+ vector_elements_.emplace_back(int_value);
+ } else {
+ stack_.back().elements.emplace_back(field_index, int_value);
+ }
+ return true;
+}
+
+bool JsonParser::AddElement(int field_index, double double_value) {
+ flatbuffers::TypeCode type_code =
+ stack_.back().typetable->type_codes[field_index];
+
+ if (type_code.is_vector != in_vector()) {
+ printf("Type and json disagree on if we are in a vector or not\n");
+ return false;
+ }
+
+ if (in_vector()) {
+ vector_elements_.emplace_back(double_value);
+ } else {
+ stack_.back().elements.emplace_back(field_index, double_value);
+ }
+ return true;
+}
+
+bool JsonParser::AddElement(int field_index, const ::std::string &data) {
+ flatbuffers::TypeCode type_code =
+ stack_.back().typetable->type_codes[field_index];
+
+ if (type_code.is_vector != in_vector()) {
+ printf("Type and json disagree on if we are in a vector or not\n");
+ return false;
+ }
+
+ if (in_vector()) {
+ vector_elements_.emplace_back(fbb_.CreateString(data));
+
+ } else {
+ stack_.back().elements.emplace_back(field_index, fbb_.CreateString(data));
+ }
+ return true;
+}
+
+bool JsonParser::AddSingleElement(const FieldElement &field_element,
+ ::std::vector<bool> *fields_in_use) {
+ if ((*fields_in_use)[field_element.field_index]) {
+ printf("Duplicate field: '%s'\n",
+ stack_.back().typetable->names[field_element.field_index]);
+ return false;
+ }
+
+ (*fields_in_use)[field_element.field_index] = true;
+
+ switch (field_element.element.type) {
+ case Element::ElementType::INT:
+ return AddSingleElement(field_element.field_index,
+ field_element.element.int_element);
+ case Element::ElementType::DOUBLE:
+ return AddSingleElement(field_element.field_index,
+ field_element.element.double_element);
+ case Element::ElementType::OFFSET:
+ return AddSingleElement(field_element.field_index,
+ field_element.element.offset_element);
+ }
+ return false;
+}
+
+bool JsonParser::AddSingleElement(int field_index, int64_t int_value) {
+ flatbuffers::voffset_t field_offset = flatbuffers::FieldIndexToOffset(
+ static_cast<flatbuffers::voffset_t>(field_index));
+
+ flatbuffers::TypeCode type_code =
+ stack_.back().typetable->type_codes[field_index];
+
+ const flatbuffers::ElementaryType elementary_type =
+ static_cast<flatbuffers::ElementaryType>(type_code.base_type);
+ switch (elementary_type) {
+ case flatbuffers::ET_BOOL:
+ fbb_.AddElement<bool>(field_offset, int_value, 0);
+ return true;
+ case flatbuffers::ET_CHAR:
+ fbb_.AddElement<int8_t>(field_offset, int_value, 0);
+ return true;
+ case flatbuffers::ET_UCHAR:
+ fbb_.AddElement<uint8_t>(field_offset, int_value, 0);
+ return true;
+ case flatbuffers::ET_SHORT:
+ fbb_.AddElement<int16_t>(field_offset, int_value, 0);
+ return true;
+ case flatbuffers::ET_USHORT:
+ fbb_.AddElement<uint16_t>(field_offset, int_value, 0);
+ return true;
+ case flatbuffers::ET_INT:
+ fbb_.AddElement<int32_t>(field_offset, int_value, 0);
+ return true;
+ case flatbuffers::ET_UINT:
+ fbb_.AddElement<uint32_t>(field_offset, int_value, 0);
+ return true;
+ case flatbuffers::ET_LONG:
+ fbb_.AddElement<int64_t>(field_offset, int_value, 0);
+ return true;
+ case flatbuffers::ET_ULONG:
+ fbb_.AddElement<uint64_t>(field_offset, int_value, 0);
+ return true;
+ case flatbuffers::ET_FLOAT:
+ fbb_.AddElement<float>(field_offset, int_value, 0);
+ return true;
+ case flatbuffers::ET_DOUBLE:
+ fbb_.AddElement<double>(field_offset, int_value, 0);
+ return true;
+ case flatbuffers::ET_STRING:
+ case flatbuffers::ET_UTYPE:
+ case flatbuffers::ET_SEQUENCE:
+ printf("Mismatched type for field '%s'. Got: integer, expected %s\n",
+ stack_.back().field_name.c_str(),
+ ElementaryTypeName(elementary_type));
+ return false;
+ };
+ return false;
+}
+
+bool JsonParser::AddSingleElement(int field_index, double double_value) {
+ flatbuffers::voffset_t field_offset = flatbuffers::FieldIndexToOffset(
+ static_cast<flatbuffers::voffset_t>(field_index));
+
+ flatbuffers::TypeCode type_code =
+ stack_.back().typetable->type_codes[field_index];
+
+ const flatbuffers::ElementaryType elementary_type =
+ static_cast<flatbuffers::ElementaryType>(type_code.base_type);
+ switch (elementary_type) {
+ case flatbuffers::ET_UTYPE:
+ case flatbuffers::ET_BOOL:
+ case flatbuffers::ET_CHAR:
+ case flatbuffers::ET_UCHAR:
+ case flatbuffers::ET_SHORT:
+ case flatbuffers::ET_USHORT:
+ case flatbuffers::ET_INT:
+ case flatbuffers::ET_UINT:
+ case flatbuffers::ET_LONG:
+ case flatbuffers::ET_ULONG:
+ case flatbuffers::ET_STRING:
+ case flatbuffers::ET_SEQUENCE:
+ printf("Mismatched type for field '%s'. Got: double, expected %s\n",
+ stack_.back().field_name.c_str(),
+ ElementaryTypeName(elementary_type));
+ return false;
+ case flatbuffers::ET_FLOAT:
+ fbb_.AddElement<float>(field_offset, double_value, 0);
+ return true;
+ case flatbuffers::ET_DOUBLE:
+ fbb_.AddElement<double>(field_offset, double_value, 0);
+ return true;
+ }
+ return false;
+}
+bool JsonParser::AddSingleElement(
+ int field_index, flatbuffers::Offset<flatbuffers::String> offset_element) {
+ flatbuffers::TypeCode type_code =
+ stack_.back().typetable->type_codes[field_index];
+
+ flatbuffers::voffset_t field_offset = flatbuffers::FieldIndexToOffset(
+ static_cast<flatbuffers::voffset_t>(field_index));
+
+ // Vectors will always be Offset<>'s.
+ if (type_code.is_vector) {
+ fbb_.AddOffset(field_offset, offset_element);
+ return true;
+ }
+
+ const flatbuffers::ElementaryType elementary_type =
+ static_cast<flatbuffers::ElementaryType>(type_code.base_type);
+ switch (elementary_type) {
+ case flatbuffers::ET_UTYPE:
+ case flatbuffers::ET_BOOL:
+ case flatbuffers::ET_CHAR:
+ case flatbuffers::ET_UCHAR:
+ case flatbuffers::ET_SHORT:
+ case flatbuffers::ET_USHORT:
+ case flatbuffers::ET_INT:
+ case flatbuffers::ET_UINT:
+ case flatbuffers::ET_LONG:
+ case flatbuffers::ET_ULONG:
+ case flatbuffers::ET_FLOAT:
+ case flatbuffers::ET_DOUBLE:
+ printf("Mismatched type for field '%s'. Got: string, expected %s\n",
+ stack_.back().field_name.c_str(),
+ ElementaryTypeName(elementary_type));
+ return false;
+ case flatbuffers::ET_SEQUENCE:
+ case flatbuffers::ET_STRING:
+ fbb_.AddOffset(field_offset, offset_element);
+ return true;
+ }
+ return false;
+}
+
+bool JsonParser::FinishVector(int field_index) {
+ flatbuffers::TypeCode type_code =
+ stack_.back().typetable->type_codes[field_index];
+
+ const flatbuffers::ElementaryType elementary_type =
+ static_cast<flatbuffers::ElementaryType>(type_code.base_type);
+
+ // Vectors have a start (unfortunately which needs to know the size)
+ fbb_.StartVector(
+ vector_elements_.size(),
+ flatbuffers::InlineSize(elementary_type, stack_.back().typetable));
+
+ // Then the data (in reverse order for some reason...)
+ for (size_t i = vector_elements_.size(); i > 0;) {
+ const Element &element = vector_elements_[--i];
+ switch (element.type) {
+ case Element::ElementType::INT:
+ if (!PushElement(elementary_type, element.int_element)) return false;
+ break;
+ case Element::ElementType::DOUBLE:
+ if (!PushElement(elementary_type, element.double_element)) return false;
+ break;
+ case Element::ElementType::OFFSET:
+ if (!PushElement(elementary_type, element.offset_element)) return false;
+ break;
+ }
+ }
+
+ // Then an End which is placed into the buffer the same as any other offset.
+ stack_.back().elements.emplace_back(
+ field_index, flatbuffers::Offset<flatbuffers::String>(
+ fbb_.EndVector(vector_elements_.size())));
+ return true;
+}
+
+bool JsonParser::PushElement(flatbuffers::ElementaryType elementary_type,
+ int64_t int_value) {
+ switch (elementary_type) {
+ case flatbuffers::ET_BOOL:
+ fbb_.PushElement<bool>(int_value);
+ return true;
+ case flatbuffers::ET_CHAR:
+ fbb_.PushElement<int8_t>(int_value);
+ return true;
+ case flatbuffers::ET_UCHAR:
+ fbb_.PushElement<uint8_t>(int_value);
+ return true;
+ case flatbuffers::ET_SHORT:
+ fbb_.PushElement<int16_t>(int_value);
+ return true;
+ case flatbuffers::ET_USHORT:
+ fbb_.PushElement<uint16_t>(int_value);
+ return true;
+ case flatbuffers::ET_INT:
+ fbb_.PushElement<int32_t>(int_value);
+ return true;
+ case flatbuffers::ET_UINT:
+ fbb_.PushElement<uint32_t>(int_value);
+ return true;
+ case flatbuffers::ET_LONG:
+ fbb_.PushElement<int64_t>(int_value);
+ return true;
+ case flatbuffers::ET_ULONG:
+ fbb_.PushElement<uint64_t>(int_value);
+ return true;
+ case flatbuffers::ET_FLOAT:
+ fbb_.PushElement<float>(int_value);
+ return true;
+ case flatbuffers::ET_DOUBLE:
+ fbb_.PushElement<double>(int_value);
+ return true;
+ case flatbuffers::ET_STRING:
+ case flatbuffers::ET_UTYPE:
+ case flatbuffers::ET_SEQUENCE:
+ printf("Mismatched type for field '%s'. Got: integer, expected %s\n",
+ stack_.back().field_name.c_str(),
+ ElementaryTypeName(elementary_type));
+ return false;
+ };
+ return false;
+}
+
+bool JsonParser::PushElement(flatbuffers::ElementaryType elementary_type,
+ double double_value) {
+ switch (elementary_type) {
+ case flatbuffers::ET_UTYPE:
+ case flatbuffers::ET_BOOL:
+ case flatbuffers::ET_CHAR:
+ case flatbuffers::ET_UCHAR:
+ case flatbuffers::ET_SHORT:
+ case flatbuffers::ET_USHORT:
+ case flatbuffers::ET_INT:
+ case flatbuffers::ET_UINT:
+ case flatbuffers::ET_LONG:
+ case flatbuffers::ET_ULONG:
+ case flatbuffers::ET_STRING:
+ case flatbuffers::ET_SEQUENCE:
+ printf("Mismatched type for field '%s'. Got: double, expected %s\n",
+ stack_.back().field_name.c_str(),
+ ElementaryTypeName(elementary_type));
+ return false;
+ case flatbuffers::ET_FLOAT:
+ fbb_.PushElement<float>(double_value);
+ return true;
+ case flatbuffers::ET_DOUBLE:
+ fbb_.PushElement<double>(double_value);
+ return true;
+ }
+ return false;
+}
+
+bool JsonParser::PushElement(
+ flatbuffers::ElementaryType elementary_type,
+ flatbuffers::Offset<flatbuffers::String> offset_value) {
+ switch (elementary_type) {
+ case flatbuffers::ET_UTYPE:
+ case flatbuffers::ET_BOOL:
+ case flatbuffers::ET_CHAR:
+ case flatbuffers::ET_UCHAR:
+ case flatbuffers::ET_SHORT:
+ case flatbuffers::ET_USHORT:
+ case flatbuffers::ET_INT:
+ case flatbuffers::ET_UINT:
+ case flatbuffers::ET_LONG:
+ case flatbuffers::ET_ULONG:
+ case flatbuffers::ET_FLOAT:
+ case flatbuffers::ET_DOUBLE:
+ printf("Mismatched type for field '%s'. Got: sequence, expected %s\n",
+ stack_.back().field_name.c_str(),
+ ElementaryTypeName(elementary_type));
+ return false;
+ case flatbuffers::ET_STRING:
+ case flatbuffers::ET_SEQUENCE:
+ fbb_.PushElement(offset_value);
+ return true;
+ }
+ return false;
+}
+
+} // namespace
+
+::std::vector<uint8_t> JsonToFlatbuffer(
+ const char *data, const flatbuffers::TypeTable *typetable) {
+ JsonParser p;
+ return p.Parse(data, typetable);
+}
+
+::std::string FlatbufferToJson(const uint8_t *buffer,
+ const ::flatbuffers::TypeTable *typetable,
+ bool multi_line) {
+ ::flatbuffers::ToStringVisitor tostring_visitor(
+ multi_line ? "\n" : " ", true, multi_line ? " " : "", multi_line);
+ IterateFlatBuffer(buffer, typetable, &tostring_visitor);
+ return tostring_visitor.s;
+}
+
+void Tokenizer::ConsumeWhitespace() {
+ while (true) {
+ if (*data_ == '\0') {
+ return;
+ }
+ // Skip any whitespace.
+ if (*data_ == ' ' || *data_ == '\r' || *data_ == '\t') {
+ ++data_;
+ } else if (*data_ == '\n') {
+ ++data_;
+ ++linenumber_;
+ } else {
+ // There is no fail. Once we are out of whitespace (including 0 of it),
+ // declare success.
+ return;
+ }
+ }
+}
+
+bool Tokenizer::Consume(const char *token) {
+ const char *original = data_;
+ while (true) {
+ // Finishing the token is success.
+ if (*token == '\0') {
+ return true;
+ }
+
+ // But finishing the data first is failure.
+ if (*data_ == '\0') {
+ data_ = original;
+ return false;
+ }
+
+ // Missmatch is failure.
+ if (*token != *data_) {
+ data_ = original;
+ return false;
+ }
+
+ ++data_;
+ ++token;
+ }
+}
+
+bool Tokenizer::ConsumeString(::std::string *s) {
+ // Under no conditions is it acceptible to run out of data while parsing a
+ // string. Any '\0' checks should confirm that.
+ const char *original = data_;
+ if (*data_ == '\0') {
+ return false;
+ }
+
+ // Expect the leading "
+ if (*data_ != '"') {
+ return false;
+ }
+
+ ++data_;
+ const char *last_parsed_data = data_;
+ *s = ::std::string();
+
+ while (true) {
+ if (*data_ == '\0') {
+ data_ = original;
+ return false;
+ }
+
+ // If we get an end or an escape, do something special.
+ if (*data_ == '"' || *data_ == '\\') {
+ // Save what we found up until now, not including this character.
+ *s += ::std::string(last_parsed_data, data_);
+
+ // Update the pointer.
+ last_parsed_data = data_;
+
+ // " is the end, declare victory.
+ if (*data_ == '"') {
+ ++data_;
+ return true;
+ } else {
+ ++data_;
+ // Now consume valid escape characters and add their representation onto
+ // the output string.
+ if (*data_ == '\0') {
+ data_ = original;
+ return false;
+ } else if (*data_ == '"') {
+ *s += "\"";
+ } else if (*data_ == '\\') {
+ *s += "\\";
+ } else if (*data_ == '/') {
+ *s += "/";
+ } else if (*data_ == 'b') {
+ *s += "\b";
+ } else if (*data_ == 'f') {
+ *s += "\f";
+ } else if (*data_ == 'n') {
+ *s += "\n";
+ } else if (*data_ == 'r') {
+ *s += "\r";
+ } else if (*data_ == 't') {
+ *s += "\t";
+ } else if (*data_ == 'u') {
+ // TODO(austin): Unicode should be valid, but I really don't care to
+ // do this now...
+ fprintf(stderr, "Unexpected unicode on line %d\n", linenumber_);
+ data_ = original;
+ return false;
+ }
+ }
+ // And skip the escaped character.
+ last_parsed_data = data_ + 1;
+ }
+
+ ++data_;
+ }
+}
+
+bool Tokenizer::ConsumeNumber(::std::string *s) {
+ // Under no conditions is it acceptible to run out of data while parsing a
+ // number. Any '\0' checks should confirm that.
+ *s = ::std::string();
+ const char *original = data_;
+
+ // Consume the leading - unconditionally.
+ Consume("-");
+
+ // Then, we either get a 0, or we get a nonzero. Only nonzero can be followed
+ // by a second number.
+ if (!Consume("0")) {
+ if (*data_ == '\0') {
+ return false;
+ } else if (*data_ >= '1' && *data_ <= '9') {
+ // This wasn't a zero, but was a valid digit. Consume it.
+ ++data_;
+ } else {
+ return false;
+ }
+
+ // Now consume any number of any digits.
+ while (true) {
+ if (*data_ == '\0') {
+ data_ = original;
+ return false;
+ }
+ if (*data_ < '0' || *data_ > '9') {
+ break;
+ }
+ ++data_;
+ }
+ }
+
+ // We could now have a decimal.
+ if (*data_ == '.') {
+ ++data_;
+ while (true) {
+ if (*data_ == '\0') {
+ data_ = original;
+ return false;
+ }
+ // And any number of digits.
+ if (*data_ < '0' || *data_ > '9') {
+ break;
+ }
+ ++data_;
+ }
+ }
+
+ // And now an exponent.
+ if (*data_ == 'e' || *data_ == 'E') {
+ ++data_;
+ if (*data_ == '\0') {
+ data_ = original;
+ return false;
+ }
+
+ // Which could have a +-
+ if (*data_ == '+' || *data_ == '-') {
+ ++data_;
+ }
+ int count = 0;
+ while (true) {
+ if (*data_ == '\0') {
+ data_ = original;
+ return false;
+ }
+ // And digits.
+ if (*data_ < '0' || *data_ > '9') {
+ break;
+ }
+ ++data_;
+ ++count;
+ }
+ // But, it is an error to have an exponent and nothing following it.
+ if (count == 0) {
+ data_ = original;
+ return false;
+ }
+ }
+
+ *s = ::std::string(original, data_);
+ return true;
+}
+
+Tokenizer::TokenType Tokenizer::Next() {
+ switch (state_) {
+ case State::kExpectObjectStart:
+ // We should always start out with a {
+ if (!Consume("{")) return TokenType::kError;
+
+ // Document that we just started an object.
+ object_type_.push_back(ObjectType::kObject);
+
+ ConsumeWhitespace();
+
+ state_ = State::kExpectField;
+ return TokenType::kStartObject;
+
+ case State::kExpectField: {
+ // Fields are built up of strings, whitespace, and then a : (followed by
+ // whitespace...)
+ ::std::string s;
+ if (!ConsumeString(&s)) {
+ fprintf(stderr, "Error on line %d, expected string for field name.\n",
+ linenumber_);
+ return TokenType::kError;
+ }
+ field_name_ = ::std::move(s);
+
+ ConsumeWhitespace();
+
+ if (!Consume(":")) {
+ fprintf(stderr, "Error on line %d\n", linenumber_);
+ return TokenType::kError;
+ }
+
+ ConsumeWhitespace();
+
+ state_ = State::kExpectValue;
+
+ return TokenType::kField;
+ } break;
+ case State::kExpectValue: {
+ TokenType result = TokenType::kError;
+
+ ::std::string s;
+ if (Consume("{")) {
+ // Fields are in objects. Record and recurse.
+ object_type_.push_back(ObjectType::kObject);
+
+ ConsumeWhitespace();
+
+ state_ = State::kExpectField;
+ return TokenType::kStartObject;
+ } else if (Consume("[")) {
+ // Values are in arrays. Record and recurse.
+ object_type_.push_back(ObjectType::kArray);
+
+ ConsumeWhitespace();
+ state_ = State::kExpectValue;
+ return TokenType::kStartArray;
+ } else if (ConsumeString(&s)) {
+ // Parsed as a string, grab it.
+ field_value_ = ::std::move(s);
+ result = TokenType::kStringValue;
+ } else if (ConsumeNumber(&s)) {
+ // Parsed as a number, grab it.
+ field_value_ = ::std::move(s);
+ result = TokenType::kNumberValue;
+ } else if (Consume("true")) {
+ // Parsed as a true, grab it.
+ field_value_ = "true";
+ result = TokenType::kTrueValue;
+ } else if (Consume("false")) {
+ // Parsed as a false, grab it.
+ field_value_ = "false";
+ result = TokenType::kFalseValue;
+ } else {
+ // Couldn't parse, so we have a syntax error.
+ fprintf(stderr, "Error line %d, invalid field value.\n", linenumber_);
+ }
+
+ ConsumeWhitespace();
+
+ // After a field, we either have a , and another field (or value if we are
+ // in an array), or we should be closing out the object (or array).
+ if (Consume(",")) {
+ ConsumeWhitespace();
+ switch (object_type_.back()) {
+ case ObjectType::kObject:
+ state_ = State::kExpectField;
+ break;
+ case ObjectType::kArray:
+ state_ = State::kExpectValue;
+ break;
+ }
+ } else {
+ // Sanity check that the stack is deep enough.
+ if (object_type_.size() == 0) {
+ fprintf(stderr, "Error on line %d\n", linenumber_);
+ return TokenType::kError;
+ }
+
+ // And then require closing out the object.
+ switch (object_type_.back()) {
+ case ObjectType::kObject:
+ if (Consume("}")) {
+ ConsumeWhitespace();
+ state_ = State::kExpectObjectEnd;
+ } else {
+ return TokenType::kError;
+ }
+ break;
+ case ObjectType::kArray:
+ if (Consume("]")) {
+ ConsumeWhitespace();
+ state_ = State::kExpectArrayEnd;
+ } else {
+ return TokenType::kError;
+ }
+ break;
+ }
+ }
+ return result;
+ } break;
+
+ case State::kExpectArrayEnd:
+ case State::kExpectObjectEnd: {
+ const TokenType result = state_ == State::kExpectArrayEnd
+ ? TokenType::kEndArray
+ : TokenType::kEndObject;
+ // This is a transient state so we can send 2 tokens out in a row. We
+ // discover the object or array end at the end of reading the value.
+ object_type_.pop_back();
+ if (object_type_.size() == 0) {
+ // We unwound the outer object. We should send kEnd next.
+ state_ = State::kExpectEnd;
+ } else if (object_type_.back() == ObjectType::kObject) {
+ // If we are going into an object, it should either have another field
+ // or end.
+ if (Consume(",")) {
+ ConsumeWhitespace();
+ state_ = State::kExpectField;
+ } else if (Consume("}")) {
+ ConsumeWhitespace();
+ state_ = State::kExpectObjectEnd;
+ } else {
+ return TokenType::kError;
+ }
+ } else if (object_type_.back() == ObjectType::kArray) {
+ // If we are going into an array, it should either have another value
+ // or end.
+ if (Consume(",")) {
+ ConsumeWhitespace();
+ state_ = State::kExpectValue;
+ } else if (Consume("]")) {
+ ConsumeWhitespace();
+ state_ = State::kExpectArrayEnd;
+ } else {
+ return TokenType::kError;
+ }
+ }
+ // And then send out the correct token.
+ return result;
+ }
+ case State::kExpectEnd:
+ // If we are supposed to be done, confirm nothing is after the end.
+ if (AtEnd()) {
+ return TokenType::kEnd;
+ } else {
+ fprintf(stderr, "Data past end at line %d\n", linenumber_);
+ return TokenType::kError;
+ }
+ }
+ return TokenType::kError;
+}
+
+bool Tokenizer::FieldAsInt(long long *value) {
+ const char *pos = field_value().c_str();
+ errno = 0;
+ *value = strtoll(field_value().c_str(), const_cast<char **>(&pos), 10);
+ if (pos == field_value().c_str() || errno != 0) {
+ return false;
+ }
+ return true;
+}
+
+bool Tokenizer::FieldAsDouble(double *value) {
+ const char *pos = field_value().c_str();
+ errno = 0;
+ *value = strtod(field_value().c_str(), const_cast<char **>(&pos));
+
+ if (pos == field_value().c_str() || errno != 0) {
+ return false;
+ }
+ return true;
+}
+
+} // namespace aos
diff --git a/aos/json_to_flatbuffer.fbs b/aos/json_to_flatbuffer.fbs
new file mode 100644
index 0000000..9743345
--- /dev/null
+++ b/aos/json_to_flatbuffer.fbs
@@ -0,0 +1,73 @@
+namespace aos.testing;
+
+table Location {
+ name:string;
+ type:string;
+ frequency:int;
+ max_size:int;
+}
+
+table Map {
+ match:Location;
+ rename:Location;
+}
+
+table Application {
+ name:string;
+ maps:[Map];
+}
+
+table Configuration {
+ locations:[Location] (id: 0);
+ maps:[Map] (id: 1);
+ applications:[Application] (id: 2);
+ imports:[string] (id: 3);
+
+ // 8 bit: byte ubyte bool
+ // 16 bit: short ushort
+ // 32 bit: int uint float
+ // 64 bit: long ulong double
+
+ // Simple values.
+ foo_byte:byte (id: 4);
+ foo_ubyte:ubyte (id: 5);
+ foo_bool:bool (id: 6);
+
+ foo_short:short (id: 7);
+ foo_ushort:ushort (id: 8);
+
+ foo_int:int (id: 9);
+ foo_uint:uint (id: 10);
+
+ foo_long:long (id: 11);
+ foo_ulong:ulong (id: 12);
+
+ foo_float:float (id: 13);
+ foo_double:double (id: 14);
+
+ foo_string:string (id: 15);
+
+ // Test vectors now.
+ vector_foo_byte:[byte] (id: 16);
+ vector_foo_ubyte:[ubyte] (id: 17);
+ vector_foo_bool:[bool] (id: 18);
+
+ vector_foo_short:[short] (id: 19);
+ vector_foo_ushort:[ushort] (id: 20);
+
+ vector_foo_int:[int] (id: 21);
+ vector_foo_uint:[uint] (id: 22);
+
+ vector_foo_long:[long] (id: 23);
+ vector_foo_ulong:[ulong] (id: 24);
+
+ vector_foo_float:[float] (id: 25);
+ vector_foo_double:[double] (id: 26);
+
+ vector_foo_string:[string] (id: 27);
+
+ // And a simple nested application.
+ single_application:Application (id: 28);
+}
+
+root_type Configuration;
diff --git a/aos/json_to_flatbuffer.h b/aos/json_to_flatbuffer.h
new file mode 100644
index 0000000..6deb663
--- /dev/null
+++ b/aos/json_to_flatbuffer.h
@@ -0,0 +1,110 @@
+#ifndef AOS_JSON_TO_FLATBUFFER_H_
+#define AOS_JSON_TO_FLATBUFFER_H_
+
+#include <cstddef>
+#include <string>
+
+#include "flatbuffers/flatbuffers.h"
+
+namespace aos {
+
+// Parses the flatbuffer into the vector, or returns an empty vector.
+::std::vector<uint8_t> JsonToFlatbuffer(
+ const char *data, const flatbuffers::TypeTable *typetable);
+
+// Converts a flatbuffer into a Json string.
+//
+// multi_line controls if the Json is written out on multiple lines or one.
+::std::string FlatbufferToJson(const uint8_t *buffer,
+ const flatbuffers::TypeTable *typetable,
+ bool multi_line = false);
+
+// This class implements the state machine at json.org
+class Tokenizer {
+ public:
+ Tokenizer(const char *data) : data_(data) {}
+
+ enum class TokenType {
+ kEnd,
+ kError,
+ kStartObject,
+ kEndObject,
+ kStartArray,
+ kEndArray,
+ kField,
+ kNumberValue,
+ kStringValue,
+ kTrueValue,
+ kFalseValue,
+ };
+
+ // Returns the next token.
+ TokenType Next();
+
+ // Returns the last field_name and field_value. These are only valid when
+ // Next returns them.
+ const ::std::string &field_name() const { return field_name_; }
+ const ::std::string &field_value() const { return field_value_; }
+
+ // Parses the current field value as a long long. Returns false if it failed
+ // to parse.
+ bool FieldAsInt(long long *value);
+ // Parses the current field value as a double. Returns false if it failed
+ // to parse.
+ bool FieldAsDouble(double *value);
+
+ // Returns true if we are at the end of the input.
+ bool AtEnd() { return *data_ == '\0'; }
+
+ const char *data_left() const { return data_; }
+
+ private:
+ // Consumes a string out of data_. Populates s with the string. Returns true
+ // if a valid string was found, and false otherwise.
+ // data_ is updated only on success.
+ bool ConsumeString(::std::string *s);
+ // Consumes a number out of data_. Populates s with the string containing the
+ // number. Returns true if a valid number was found, and false otherwise.
+ // data_ is updated only on success.
+ bool ConsumeNumber(::std::string *s);
+ // Consumes a fixed token out of data_. Returns true if the string was found,
+ // and false otherwise.
+ // data_ is updated only on success.
+ bool Consume(const char* token);
+ // Consumes whitespace out of data_. Returns true if the string was found,
+ // and false otherwise.
+ // data_ is unconditionally updated.
+ void ConsumeWhitespace();
+
+ // State for the parsing state machine.
+ enum class State {
+ kExpectField,
+ kExpectObjectStart,
+ kExpectObjectEnd,
+ kExpectArrayEnd,
+ kExpectValue,
+ kExpectEnd,
+ };
+
+ State state_ = State::kExpectObjectStart;
+
+ // Data pointer.
+ const char *data_;
+ // Current line number used for printing debug.
+ int linenumber_ = 0;
+
+ // Stack used to track which object type we were in when we recursed.
+ enum class ObjectType {
+ kObject,
+ kArray,
+ };
+ ::std::vector<ObjectType> object_type_;
+
+ // Last field name.
+ ::std::string field_name_;
+ // Last field value.
+ ::std::string field_value_;
+};
+} // namespace aos
+
+#endif // AOS_JSON_TO_FLATBUFFER_H_
diff --git a/aos/json_to_flatbuffer_test.cc b/aos/json_to_flatbuffer_test.cc
new file mode 100644
index 0000000..37edbf3
--- /dev/null
+++ b/aos/json_to_flatbuffer_test.cc
@@ -0,0 +1,139 @@
+#include "aos/json_to_flatbuffer.h"
+
+#include "gtest/gtest.h"
+
+#include "aos/json_to_flatbuffer_generated.h"
+#include "flatbuffers/minireflect.h"
+
+namespace aos {
+namespace testing {
+
+class JsonToFlatbufferTest : public ::testing::Test {
+ public:
+ JsonToFlatbufferTest() {}
+
+ bool JsonAndBack(const ::std::string str) { return JsonAndBack(str, str); }
+
+ bool JsonAndBack(const ::std::string in, const ::std::string out) {
+ printf("Testing: %s\n", in.c_str());
+ const ::std::vector<uint8_t> fb =
+ JsonToFlatbuffer(in.data(), ConfigurationTypeTable());
+
+ if (fb.size() == 0) {
+ return false;
+ }
+
+ const ::std::string back =
+ FlatbufferToJson(fb.data(), ConfigurationTypeTable());
+
+ printf("Back to string: %s\n", back.c_str());
+
+ return back == out;
+ }
+};
+
+// Tests that the various escapes work as expected.
+TEST_F(JsonToFlatbufferTest, ValidEscapes) {
+ EXPECT_TRUE(
+ JsonAndBack("{ \"foo_string\": \"a\\\"b\\/c\\bd\\fc\\nd\\re\\tf\" }",
+ "{ \"foo_string\": \"a\\\"b/c\\bd\\fc\\nd\\re\\tf\" }"));
+}
+
+// Test the easy ones. Test every type, single, no nesting.
+TEST_F(JsonToFlatbufferTest, Basic) {
+ EXPECT_TRUE(JsonAndBack("{ \"foo_bool\": true }"));
+
+ EXPECT_TRUE(JsonAndBack("{ \"foo_byte\": 5 }"));
+ EXPECT_TRUE(JsonAndBack("{ \"foo_ubyte\": 5 }"));
+
+ EXPECT_TRUE(JsonAndBack("{ \"foo_short\": 5 }"));
+ EXPECT_TRUE(JsonAndBack("{ \"foo_ushort\": 5 }"));
+
+ EXPECT_TRUE(JsonAndBack("{ \"foo_int\": 5 }"));
+ EXPECT_TRUE(JsonAndBack("{ \"foo_uint\": 5 }"));
+
+ EXPECT_TRUE(JsonAndBack("{ \"foo_long\": 5 }"));
+ EXPECT_TRUE(JsonAndBack("{ \"foo_ulong\": 5 }"));
+
+ EXPECT_TRUE(JsonAndBack("{ \"foo_float\": 5.0 }"));
+ EXPECT_TRUE(JsonAndBack("{ \"foo_double\": 5.0 }"));
+
+ EXPECT_TRUE(JsonAndBack("{ \"foo_string\": \"baz\" }"));
+}
+
+// Test what happens if you pass a field name that we don't know.
+TEST_F(JsonToFlatbufferTest, InvalidFieldName) {
+ EXPECT_FALSE(JsonAndBack("{ \"foo\": 5 }"));
+}
+
+// Test that adding a duplicate field results in an error.
+TEST_F(JsonToFlatbufferTest, DuplicateField) {
+ EXPECT_FALSE(
+ JsonAndBack("{ \"foo_int\": 5, \"foo_int\": 7 }", "{ \"foo_int\": 7 }"));
+}
+
+// Test that various syntax errors are caught correctly
+TEST_F(JsonToFlatbufferTest, InvalidSyntax) {
+ EXPECT_FALSE(JsonAndBack("{ \"foo_int\": 5"));
+ EXPECT_FALSE(JsonAndBack("{ \"foo_int\": 5 "));
+ EXPECT_FALSE(JsonAndBack("{ \"foo_string\": \""));
+ EXPECT_FALSE(JsonAndBack("{ \"foo_int\": 5 } }"));
+
+ EXPECT_FALSE(JsonAndBack("{ foo_int: 5 }"));
+
+ EXPECT_FALSE(JsonAndBack("{ \"foo_int\": 5, }", "{ \"foo_int\": 5 }"));
+
+ EXPECT_FALSE(JsonAndBack(
+ "{ \"applications\":\n[\n{\n\"name\": \"woot\"\n},\n{\n\"name\": "
+ "\"wow\"\n} ,\n]\n}"));
+
+ EXPECT_FALSE(
+ JsonAndBack("{ \"applications\": [ { \"name\": \"woot\" }, { \"name\": "
+ "\"wow\" } ] , }"));
+
+ EXPECT_FALSE(
+ JsonAndBack("{ \"vector_foo_string\": [ \"bar\", \"baz\" ] , }"));
+
+ EXPECT_FALSE(
+ JsonAndBack("{ \"single_application\": { \"name\": \"woot\" } , }"));
+}
+
+// Test arrays of simple types.
+TEST_F(JsonToFlatbufferTest, Array) {
+ EXPECT_TRUE(JsonAndBack("{ \"vector_foo_byte\": [ 9, 7, 1 ] }"));
+ EXPECT_TRUE(JsonAndBack("{ \"vector_foo_ubyte\": [ 9, 7, 1 ] }"));
+
+ EXPECT_TRUE(JsonAndBack("{ \"vector_foo_short\": [ 9, 7, 1 ] }"));
+ EXPECT_TRUE(JsonAndBack("{ \"vector_foo_ushort\": [ 9, 7, 1 ] }"));
+
+ EXPECT_TRUE(JsonAndBack("{ \"vector_foo_int\": [ 9, 7, 1 ] }"));
+ EXPECT_TRUE(JsonAndBack("{ \"vector_foo_uint\": [ 9, 7, 1 ] }"));
+
+ EXPECT_TRUE(JsonAndBack("{ \"vector_foo_long\": [ 9, 7, 1 ] }"));
+ EXPECT_TRUE(JsonAndBack("{ \"vector_foo_ulong\": [ 9, 7, 1 ] }"));
+
+ EXPECT_TRUE(JsonAndBack("{ \"vector_foo_float\": [ 9.0, 7.0, 1.0 ] }"));
+ EXPECT_TRUE(JsonAndBack("{ \"vector_foo_double\": [ 9.0, 7.0, 1.0 ] }"));
+
+ EXPECT_TRUE(JsonAndBack("{ \"vector_foo_float\": [ 9, 7, 1 ] }",
+ "{ \"vector_foo_float\": [ 9.0, 7.0, 1.0 ] }"));
+ EXPECT_TRUE(JsonAndBack("{ \"vector_foo_double\": [ 9, 7, 1 ] }",
+ "{ \"vector_foo_double\": [ 9.0, 7.0, 1.0 ] }"));
+
+ EXPECT_TRUE(JsonAndBack("{ \"vector_foo_string\": [ \"bar\", \"baz\" ] }"));
+}
+
+// Test nested messages, and arrays of nested messages.
+TEST_F(JsonToFlatbufferTest, NestedStruct) {
+ EXPECT_TRUE(
+ JsonAndBack("{ \"single_application\": { \"name\": \"woot\" } }"));
+
+ EXPECT_TRUE(
+ JsonAndBack("{ \"applications\": [ { \"name\": \"woot\" }, { \"name\": "
+ "\"wow\" } ] }"));
+}
+
+// TODO(austin): Missmatched values.
+
+} // namespace testing
+} // namespace aos
diff --git a/third_party/flatbuffers/BUILD b/third_party/flatbuffers/BUILD
index 520f98a..17f5b8c 100644
--- a/third_party/flatbuffers/BUILD
+++ b/third_party/flatbuffers/BUILD
@@ -27,6 +27,7 @@
"src/util.cpp",
],
hdrs = [":public_headers"],
+ copts = ["-Wno-cast-align"],
includes = ["include/"],
linkstatic = 1,
)
@@ -86,11 +87,11 @@
"src/idl_gen_cpp.cpp",
"src/idl_gen_dart.cpp",
"src/idl_gen_general.cpp",
- "src/idl_gen_kotlin.cpp",
"src/idl_gen_go.cpp",
"src/idl_gen_grpc.cpp",
"src/idl_gen_js_ts.cpp",
"src/idl_gen_json_schema.cpp",
+ "src/idl_gen_kotlin.cpp",
"src/idl_gen_lobster.cpp",
"src/idl_gen_lua.cpp",
"src/idl_gen_php.cpp",
@@ -137,8 +138,8 @@
"src/util.cpp",
"tests/namespace_test/namespace_test1_generated.h",
"tests/namespace_test/namespace_test2_generated.h",
- "tests/native_type_test_impl.h",
"tests/native_type_test_impl.cpp",
+ "tests/native_type_test_impl.h",
"tests/test.cpp",
"tests/test_assert.cpp",
"tests/test_assert.h",
@@ -152,12 +153,18 @@
"-DBAZEL_TEST_DATA_PATH",
],
data = [
+ ":tests/arrays_test.bfbs",
+ ":tests/arrays_test.fbs",
+ ":tests/arrays_test.golden",
":tests/include_test/include_test1.fbs",
":tests/include_test/sub/include_test2.fbs",
+ ":tests/monster_extra.fbs",
":tests/monster_test.bfbs",
":tests/monster_test.fbs",
+ ":tests/monsterdata_extra.json",
":tests/monsterdata_test.golden",
":tests/monsterdata_test.json",
+ ":tests/native_type_test.fbs",
":tests/prototest/imported.proto",
":tests/prototest/test.golden",
":tests/prototest/test.proto",
@@ -165,21 +172,15 @@
":tests/unicode_test.json",
":tests/union_vector/union_vector.fbs",
":tests/union_vector/union_vector.json",
- ":tests/monster_extra.fbs",
- ":tests/monsterdata_extra.json",
- ":tests/arrays_test.bfbs",
- ":tests/arrays_test.fbs",
- ":tests/arrays_test.golden",
- ":tests/native_type_test.fbs",
],
includes = [
"include/",
"tests/",
],
deps = [
+ ":arrays_test_cc_fbs",
":monster_extra_cc_fbs",
":monster_test_cc_fbs",
- ":arrays_test_cc_fbs",
":native_type_test_cc_fbs",
],
)
@@ -211,7 +212,8 @@
"--gen-mutable",
"--reflect-names",
"--cpp-ptr-type flatbuffers::unique_ptr",
- "--scoped-enums" ],
+ "--scoped-enums",
+ ],
)
flatbuffer_cc_library(
@@ -220,6 +222,6 @@
flatc_args = [
"--gen-object-api",
"--gen-mutable",
- "--cpp-ptr-type flatbuffers::unique_ptr" ],
+ "--cpp-ptr-type flatbuffers::unique_ptr",
+ ],
)
-
diff --git a/third_party/flatbuffers/include/flatbuffers/minireflect.h b/third_party/flatbuffers/include/flatbuffers/minireflect.h
index 9d648ec..e7b9024 100644
--- a/third_party/flatbuffers/include/flatbuffers/minireflect.h
+++ b/third_party/flatbuffers/include/flatbuffers/minireflect.h
@@ -396,7 +396,7 @@
const TypeTable *type_table,
bool multi_line = false,
bool vector_delimited = true) {
- ToStringVisitor tostring_visitor(multi_line ? "\n" : " ", false, "",
+ ToStringVisitor tostring_visitor(multi_line ? "\n" : " ", true, "",
vector_delimited);
IterateFlatBuffer(buffer, type_table, &tostring_visitor);
return tostring_visitor.s;
diff --git a/third_party/jsont/BUILD b/third_party/jsont/BUILD
new file mode 100644
index 0000000..dc1d03d
--- /dev/null
+++ b/third_party/jsont/BUILD
@@ -0,0 +1,28 @@
+licenses(["notice"])
+
+cc_library(
+ name = "jsont",
+ srcs = [
+ "jsont.c",
+ ],
+ hdrs = [
+ "jsont.h",
+ ],
+ includes = ["."],
+ visibility = ["//visibility:public"],
+)
+
+cc_test(
+ name = "jsont_test",
+ srcs = ["test/test_tokenizer.c"],
+ copts = [
+ "-Wno-unused-parameter",
+ "-Wno-unused-variable",
+ ] + select({
+ "@//tools:cpu_roborio": [
+ "-Wno-unused-but-set-variable",
+ ],
+ "//conditions:default": [],
+ }),
+ deps = [":jsont"],
+)
diff --git a/third_party/jsont/jsont.c b/third_party/jsont/jsont.c
index 5863c7a..b318494 100644
--- a/third_party/jsont/jsont.c
+++ b/third_party/jsont/jsont.c
@@ -74,7 +74,7 @@
for (size_t i = 0; i != len; ++i) {
uint8_t b = bytes[i];
int digit = (b > '0'-1 && b < 'f'+1) ? kHexValueTable[b-'0'] : -1;
- if (b == -1 || // bad digit
+ if (b == 0xff || // bad digit
(value > cutoff) || // overflow
((value == cutoff) && (digit > cutoff_digit)) ) {
return ULONG_MAX;
@@ -172,9 +172,10 @@
(memcmp((const void*)ctx->value_buf.data,
(const void*)bytes, length) == 0);
} else {
- return (ctx->input_buf_value_end - ctx->input_buf_value_start == length) &&
- (memcmp((const void*)ctx->input_buf_value_start,
- (const void*)bytes, length) == 0);
+ return (ctx->input_buf_value_end - ctx->input_buf_value_start ==
+ (ssize_t)length) &&
+ (memcmp((const void *)ctx->input_buf_value_start,
+ (const void *)bytes, length) == 0);
}
}