Add a flatbuffer merge function

The second value wins.  Vectors get appended.  Nested objects get both
merged.

Change-Id: I1cb3f65b57d7cf219fca3c6d8dbb8418f8a0d697
diff --git a/aos/BUILD b/aos/BUILD
index 95418ea..46aa1b9 100644
--- a/aos/BUILD
+++ b/aos/BUILD
@@ -458,3 +458,27 @@
         "//aos/testing:googletest",
     ],
 )
+
+cc_library(
+    name = "flatbuffer_merge",
+    srcs = ["flatbuffer_merge.cc"],
+    hdrs = ["flatbuffer_merge.h"],
+    copts = ["-Wno-cast-align"],
+    deps = [
+        ":flatbuffer_utils",
+        "@com_github_google_flatbuffers//:flatbuffers",
+    ],
+)
+
+cc_test(
+    name = "flatbuffer_merge_test",
+    srcs = [
+        "flatbuffer_merge_test.cc",
+    ],
+    deps = [
+        ":flatbuffer_merge",
+        ":json_to_flatbuffer",
+        ":json_to_flatbuffer_flatbuffer",
+        "//aos/testing:googletest",
+    ],
+)
diff --git a/aos/flatbuffer_merge.cc b/aos/flatbuffer_merge.cc
new file mode 100644
index 0000000..3f72bb9
--- /dev/null
+++ b/aos/flatbuffer_merge.cc
@@ -0,0 +1,535 @@
+#include "aos/flatbuffer_merge.h"
+
+#include <stdio.h>
+
+#include "aos/flatbuffer_utils.h"
+#include "flatbuffers/flatbuffers.h"
+#include "flatbuffers/minireflect.h"
+
+namespace aos {
+
+namespace {
+
+// Simple structure to hold both field_offsets and elements.
+struct OffsetAndFieldOffset {
+  OffsetAndFieldOffset(flatbuffers::voffset_t new_field_offset,
+                       flatbuffers::Offset<flatbuffers::String> new_element)
+      : field_offset(new_field_offset), element(new_element) {}
+
+  flatbuffers::voffset_t field_offset;
+  flatbuffers::Offset<flatbuffers::String> element;
+};
+
+// Merges 2 flat buffers with the provided type table into the builder.  Returns
+// the offset to the flatbuffers.
+// One or both of t1 and t2 must be non-null.  If one is null, this method
+// coppies instead of merging.
+flatbuffers::uoffset_t MergeFlatBuffers(const flatbuffers::TypeTable *typetable,
+                                        const flatbuffers::Table *t1,
+                                        const flatbuffers::Table *t2,
+                                        flatbuffers::FlatBufferBuilder *fbb);
+
+// Merges a single element to a builder for the provided field.
+// One or both of t1 and t2 must be non-null.  If one is null, this method
+// copies instead of merging.
+template <typename T>
+void MergeElement(flatbuffers::voffset_t field_offset,
+                  const flatbuffers::Table *t1, const flatbuffers::Table *t2,
+                  flatbuffers::FlatBufferBuilder *fbb) {
+  const uint8_t *val1 =
+      t1 != nullptr ? t1->GetAddressOf(field_offset) : nullptr;
+  const uint8_t *val2 =
+      t2 != nullptr ? t2->GetAddressOf(field_offset) : nullptr;
+  const bool t1_has = val1 != nullptr;
+  const bool t2_has = val2 != nullptr;
+
+  if (t2_has) {
+    fbb->AddElement<T>(field_offset, flatbuffers::ReadScalar<T>(val2), 0);
+  } else if (t1_has) {
+    fbb->AddElement<T>(field_offset, flatbuffers::ReadScalar<T>(val1), 0);
+  }
+}
+
+// Merges a single string to a builder for the provided field.
+// One or both of t1 and t2 must be non-null.  If one is null, this method
+// copies instead of merging.
+void MergeString(flatbuffers::voffset_t field_offset,
+                 const flatbuffers::Table *t1, const flatbuffers::Table *t2,
+                 flatbuffers::FlatBufferBuilder *fbb,
+                 ::std::vector<OffsetAndFieldOffset> *elements) {
+  const uint8_t *val1 =
+      t1 != nullptr ? t1->GetAddressOf(field_offset) : nullptr;
+  const uint8_t *val2 =
+      t2 != nullptr ? t2->GetAddressOf(field_offset) : nullptr;
+  const bool t1_has = val1 != nullptr;
+  const bool t2_has = val2 != nullptr;
+
+  if (t2_has) {
+    val2 += flatbuffers::ReadScalar<flatbuffers::uoffset_t>(val2);
+    const flatbuffers::String *string2 =
+        reinterpret_cast<const flatbuffers::String *>(val2);
+    elements->emplace_back(field_offset,
+                           fbb->CreateString(string2->data(), string2->size()));
+  } else if (t1_has) {
+    val1 += flatbuffers::ReadScalar<flatbuffers::uoffset_t>(val1);
+    const flatbuffers::String *string1 =
+        reinterpret_cast<const flatbuffers::String *>(val1);
+    elements->emplace_back(field_offset,
+                           fbb->CreateString(string1->data(), string1->size()));
+  }
+}
+
+// Merges an object to a builder for the provided field.
+// One or both of t1 and t2 must be non-null.  If one is null, this method
+// copies instead of merging.
+void MergeTables(flatbuffers::voffset_t field_offset,
+                 const flatbuffers::Table *t1, const flatbuffers::Table *t2,
+                 const flatbuffers::TypeTable *sub_typetable,
+                 flatbuffers::FlatBufferBuilder *fbb,
+                 ::std::vector<OffsetAndFieldOffset> *elements) {
+  const uint8_t *val1 =
+      t1 != nullptr ? t1->GetAddressOf(field_offset) : nullptr;
+  const uint8_t *val2 =
+      t2 != nullptr ? t2->GetAddressOf(field_offset) : nullptr;
+  const bool t1_has = val1 != nullptr;
+  const bool t2_has = val2 != nullptr;
+  if (t1_has || t2_has) {
+    if (val1 != nullptr) {
+      val1 += flatbuffers::ReadScalar<flatbuffers::uoffset_t>(val1);
+    }
+    if (val2 != nullptr) {
+      val2 += flatbuffers::ReadScalar<flatbuffers::uoffset_t>(val2);
+    }
+
+    const flatbuffers::Table *sub_t1 =
+        reinterpret_cast<const flatbuffers::Table *>(val1);
+    const flatbuffers::Table *sub_t2 =
+        reinterpret_cast<const flatbuffers::Table *>(val2);
+
+    elements->emplace_back(field_offset,
+                          MergeFlatBuffers(sub_typetable, sub_t1, sub_t2, fbb));
+  }
+}
+
+// Adds a vector of strings to the elements vector so it can be added later.
+// One or both of t1 and t2 must be non-null.  If one is null, this method
+// copies instead of merging.
+void AddVectorOfStrings(flatbuffers::ElementaryType elementary_type,
+                        flatbuffers::voffset_t field_offset,
+                        const flatbuffers::Table *t1,
+                        const flatbuffers::Table *t2,
+                        flatbuffers::FlatBufferBuilder *fbb,
+                        ::std::vector<OffsetAndFieldOffset> *elements) {
+  const uint8_t *val1 =
+      t1 != nullptr ? t1->GetAddressOf(field_offset) : nullptr;
+  const uint8_t *val2 =
+      t2 != nullptr ? t2->GetAddressOf(field_offset) : nullptr;
+  const bool t1_has = val1 != nullptr;
+  const bool t2_has = val2 != nullptr;
+
+  // Compute end size of the vector.
+  size_t size = 0;
+  if (t1_has) {
+    val1 += flatbuffers::ReadScalar<flatbuffers::uoffset_t>(val1);
+    auto vec1 = reinterpret_cast<
+        const flatbuffers::Vector<flatbuffers::Offset<flatbuffers::String>> *>(
+        val1);
+    size += vec1->size();
+  }
+  if (t2_has) {
+    val2 += flatbuffers::ReadScalar<flatbuffers::uoffset_t>(val2);
+    auto vec2 = reinterpret_cast<
+        const flatbuffers::Vector<flatbuffers::Offset<flatbuffers::String>> *>(
+        val2);
+    size += vec2->size();
+  }
+
+  // Only add the vector if there is something to add.
+  if (t1_has || t2_has) {
+    const size_t inline_size =
+        flatbuffers::InlineSize(elementary_type, nullptr);
+
+    ::std::vector<flatbuffers::Offset<flatbuffers::String>> string_elements;
+
+    // Pack the contents in in reverse order.
+    if (t2_has) {
+      auto vec2 = reinterpret_cast<const flatbuffers::Vector<
+          flatbuffers::Offset<flatbuffers::String>> *>(val2);
+      for (auto i = vec2->rbegin(); i != vec2->rend(); ++i) {
+        const flatbuffers::String *s = *i;
+        string_elements.emplace_back(fbb->CreateString(s->data(), s->size()));
+      }
+    }
+    if (t1_has) {
+      auto vec1 = reinterpret_cast<const flatbuffers::Vector<
+          flatbuffers::Offset<flatbuffers::String>> *>(val1);
+      for (auto i = vec1->rbegin(); i != vec1->rend(); ++i) {
+        const flatbuffers::String *s = *i;
+        string_elements.emplace_back(fbb->CreateString(s->data(), s->size()));
+      }
+    }
+
+    // Start the vector.
+    fbb->StartVector(size, inline_size);
+
+    for (const flatbuffers::Offset<flatbuffers::String> &element :
+         string_elements) {
+      fbb->PushElement(element);
+    }
+
+    // And then finish the vector and put it in the list of offsets to add to
+    // the message when it finishes.
+    elements->emplace_back(
+        field_offset,
+        flatbuffers::Offset<flatbuffers::String>(fbb->EndVector(size)));
+  }
+}
+
+// Adds a vector of values to the elements vector so it can be added later.
+// One or both of t1 and t2 must be non-null.  If one is null, this method
+// copies instead of merging.
+template <typename T>
+void AddVector(flatbuffers::ElementaryType elementary_type,
+               flatbuffers::voffset_t field_offset,
+               const flatbuffers::Table *t1, const flatbuffers::Table *t2,
+               flatbuffers::FlatBufferBuilder *fbb,
+               ::std::vector<OffsetAndFieldOffset> *elements) {
+  const uint8_t *val1 =
+      t1 != nullptr ? t1->GetAddressOf(field_offset) : nullptr;
+  const uint8_t *val2 =
+      t2 != nullptr ? t2->GetAddressOf(field_offset) : nullptr;
+  const bool t1_has = val1 != nullptr;
+  const bool t2_has = val2 != nullptr;
+
+  // Compute end size of the vector.
+  size_t size = 0;
+  if (t1_has) {
+    val1 += flatbuffers::ReadScalar<flatbuffers::uoffset_t>(val1);
+    auto vec1 = reinterpret_cast<const flatbuffers::Vector<T> *>(val1);
+    size += vec1->size();
+  }
+  if (t2_has) {
+    val2 += flatbuffers::ReadScalar<flatbuffers::uoffset_t>(val2);
+    auto vec2 = reinterpret_cast<const flatbuffers::Vector<T> *>(val2);
+    size += vec2->size();
+  }
+
+  // Only add the vector if there is something to add.
+  if (t1_has || t2_has) {
+    const size_t inline_size =
+        flatbuffers::InlineSize(elementary_type, nullptr);
+
+    // Start the vector.
+    fbb->StartVector(size, inline_size);
+
+    // Pack the contents in in reverse order.
+    if (t2_has) {
+      auto vec2 = reinterpret_cast<const flatbuffers::Vector<T> *>(val2);
+      // Iterate backwards.
+      for (auto i = vec2->rbegin(); i != vec2->rend(); ++i) {
+        fbb->PushElement<T>(*i);
+      }
+    }
+    if (t1_has) {
+      auto vec1 = reinterpret_cast<const flatbuffers::Vector<T> *>(val1);
+      // Iterate backwards.
+      for (auto i = vec1->rbegin(); i != vec1->rend(); ++i) {
+        fbb->PushElement<T>(*i);
+      }
+    }
+    // And then finish the vector and put it in the list of offsets to add to
+    // the message when it finishes.
+    elements->emplace_back(
+        field_offset,
+        flatbuffers::Offset<flatbuffers::String>(fbb->EndVector(size)));
+  }
+}
+
+void AddVectorOfObjects(flatbuffers::FlatBufferBuilder *fbb,
+                        ::std::vector<OffsetAndFieldOffset> *elements,
+                        flatbuffers::ElementaryType elementary_type,
+                        const flatbuffers::TypeTable *sub_typetable,
+                        flatbuffers::voffset_t field_offset,
+                        const flatbuffers::Table *t1,
+                        const flatbuffers::Table *t2) {
+  const uint8_t *val1 =
+      t1 != nullptr ? t1->GetAddressOf(field_offset) : nullptr;
+  const uint8_t *val2 =
+      t2 != nullptr ? t2->GetAddressOf(field_offset) : nullptr;
+  const bool t1_has = val1 != nullptr;
+  const bool t2_has = val2 != nullptr;
+
+  // Compute end size of the vector.
+  size_t size = 0;
+  if (t1_has) {
+    val1 += flatbuffers::ReadScalar<flatbuffers::uoffset_t>(val1);
+    auto vec1 = reinterpret_cast<
+        const flatbuffers::Vector<flatbuffers::Offset<flatbuffers::Table>> *>(
+        val1);
+    size += vec1->size();
+  }
+  if (t2_has) {
+    val2 += flatbuffers::ReadScalar<flatbuffers::uoffset_t>(val2);
+    auto vec2 = reinterpret_cast<
+        const flatbuffers::Vector<flatbuffers::Offset<flatbuffers::Table>> *>(
+        val2);
+    size += vec2->size();
+  }
+
+  // Only add the vector if there is something to add.
+  if (t1_has || t2_has) {
+    const size_t inline_size =
+        flatbuffers::InlineSize(elementary_type, sub_typetable);
+
+    ::std::vector<flatbuffers::Offset<flatbuffers::Table>> object_elements;
+
+    // Pack the contents in in reverse order.
+    if (t2_has) {
+      auto vec2 = reinterpret_cast<
+          const flatbuffers::Vector<flatbuffers::Offset<flatbuffers::Table>> *>(
+          val2);
+      for (auto i = vec2->rbegin(); i != vec2->rend(); ++i) {
+        const flatbuffers::Table *t = *i;
+
+        flatbuffers::uoffset_t end =
+            MergeFlatBuffers(sub_typetable, t, nullptr, fbb);
+
+        object_elements.emplace_back(
+            flatbuffers::Offset<flatbuffers::Table>(end));
+      }
+    }
+    if (t1_has) {
+      auto vec1 = reinterpret_cast<
+          const flatbuffers::Vector<flatbuffers::Offset<flatbuffers::Table>> *>(
+          val1);
+      for (auto i = vec1->rbegin(); i != vec1->rend(); ++i) {
+        const flatbuffers::Table *t = *i;
+
+        flatbuffers::uoffset_t end =
+            MergeFlatBuffers(sub_typetable, t, nullptr, fbb);
+
+        object_elements.emplace_back(
+            flatbuffers::Offset<flatbuffers::Table>(end));
+      }
+    }
+
+    // Start the vector.
+    fbb->StartVector(size, inline_size);
+
+    for (const flatbuffers::Offset<flatbuffers::Table> &element :
+         object_elements) {
+      fbb->PushElement(element);
+    }
+
+    // And then finish the vector and put it in the list of offsets to add to
+    // the message when it finishes.
+    elements->emplace_back(
+        field_offset,
+        flatbuffers::Offset<flatbuffers::String>(fbb->EndVector(size)));
+  }
+}
+
+flatbuffers::uoffset_t MergeFlatBuffers(const flatbuffers::TypeTable *typetable,
+                                        const flatbuffers::Table *t1,
+                                        const flatbuffers::Table *t2,
+                                        flatbuffers::FlatBufferBuilder *fbb) {
+  ::std::vector<OffsetAndFieldOffset> elements;
+
+  // We need to do this in 2 passes
+  // The first pass builds up all the sub-objects which are encoded in the
+  // message as offsets.
+  // The second pass builds up the actual table by adding all the values to the
+  // messages, and encoding the offsets in the table.
+  for (size_t field_index = 0; field_index < typetable->num_elems;
+       ++field_index) {
+    const flatbuffers::TypeCode type_code = typetable->type_codes[field_index];
+    const flatbuffers::ElementaryType elementary_type =
+        static_cast<flatbuffers::ElementaryType>(type_code.base_type);
+
+    const flatbuffers::voffset_t field_offset = flatbuffers::FieldIndexToOffset(
+        static_cast<flatbuffers::voffset_t>(field_index));
+
+    switch (elementary_type) {
+      case flatbuffers::ElementaryType::ET_UTYPE:
+        if (!type_code.is_vector) continue;
+        printf("ET_UTYPE, %s\n", typetable->names[field_index]);
+        break;
+      case flatbuffers::ElementaryType::ET_BOOL:
+        if (!type_code.is_vector) continue;
+        AddVector<uint8_t>(elementary_type, field_offset, t1, t2, fbb,
+                           &elements);
+        break;
+      case flatbuffers::ElementaryType::ET_CHAR:
+        if (!type_code.is_vector) continue;
+        AddVector<int8_t>(elementary_type, field_offset, t1, t2, fbb,
+                          &elements);
+        break;
+      case flatbuffers::ElementaryType::ET_UCHAR:
+        if (!type_code.is_vector) continue;
+        AddVector<uint8_t>(elementary_type, field_offset, t1, t2, fbb,
+                           &elements);
+        break;
+      case flatbuffers::ElementaryType::ET_SHORT:
+        if (!type_code.is_vector) continue;
+        AddVector<int16_t>(elementary_type, field_offset, t1, t2, fbb,
+                           &elements);
+        break;
+      case flatbuffers::ElementaryType::ET_USHORT:
+        if (!type_code.is_vector) continue;
+        AddVector<uint16_t>(elementary_type, field_offset, t1, t2, fbb,
+                            &elements);
+        break;
+      case flatbuffers::ElementaryType::ET_INT:
+        if (!type_code.is_vector) continue;
+        AddVector<int32_t>(elementary_type, field_offset, t1, t2, fbb,
+                           &elements);
+        break;
+      case flatbuffers::ElementaryType::ET_UINT:
+        if (!type_code.is_vector) continue;
+        AddVector<uint32_t>(elementary_type, field_offset, t1, t2, fbb,
+                            &elements);
+        break;
+      case flatbuffers::ElementaryType::ET_LONG:
+        if (!type_code.is_vector) continue;
+        AddVector<int64_t>(elementary_type, field_offset, t1, t2, fbb,
+                           &elements);
+        break;
+      case flatbuffers::ElementaryType::ET_ULONG:
+        if (!type_code.is_vector) continue;
+        AddVector<uint64_t>(elementary_type, field_offset, t1, t2, fbb,
+                            &elements);
+        break;
+      case flatbuffers::ElementaryType::ET_FLOAT:
+        if (!type_code.is_vector) continue;
+        AddVector<float>(elementary_type, field_offset, t1, t2, fbb, &elements);
+        break;
+      case flatbuffers::ElementaryType::ET_DOUBLE:
+        if (!type_code.is_vector) continue;
+        AddVector<double>(elementary_type, field_offset, t1, t2, fbb,
+                          &elements);
+        break;
+      case flatbuffers::ElementaryType::ET_STRING:
+        if (!type_code.is_vector) {
+          MergeString(field_offset, t1, t2, fbb, &elements);
+        } else {
+          AddVectorOfStrings(elementary_type, field_offset, t1, t2, fbb,
+                             &elements);
+        }
+        break;
+      case flatbuffers::ElementaryType::ET_SEQUENCE: {
+        const flatbuffers::TypeTable *sub_typetable =
+            typetable->type_refs[type_code.sequence_ref]();
+        if (!type_code.is_vector) {
+          MergeTables(field_offset, t1, t2, sub_typetable, fbb, &elements);
+        } else {
+          const flatbuffers::TypeTable *sub_typetable =
+              typetable->type_refs[type_code.sequence_ref]();
+
+          AddVectorOfObjects(fbb, &elements, elementary_type, sub_typetable,
+                             field_offset, t1, t2);
+        }
+      } break;
+    }
+  }
+
+  const flatbuffers::uoffset_t start = fbb->StartTable();
+
+  // We want to do this the same way as the json library.  Rip through the
+  // fields and generate a list of things to add.  Then add them.
+  // Also need recursion for subtypes.
+  for (size_t field_index = 0; field_index < typetable->num_elems;
+       ++field_index) {
+    const flatbuffers::TypeCode type_code = typetable->type_codes[field_index];
+    if (type_code.is_vector) {
+      continue;
+    }
+    const flatbuffers::ElementaryType elementary_type =
+        static_cast<flatbuffers::ElementaryType>(type_code.base_type);
+
+    const flatbuffers::voffset_t field_offset = flatbuffers::FieldIndexToOffset(
+        static_cast<flatbuffers::voffset_t>(field_index));
+
+    switch (elementary_type) {
+      case flatbuffers::ElementaryType::ET_UTYPE:
+        // TODO(austin): Need to see one and try it.
+        printf("ET_UTYPE, %s\n", typetable->names[field_index]);
+        break;
+      case flatbuffers::ElementaryType::ET_BOOL: {
+        MergeElement<uint8_t>(field_offset, t1, t2, fbb);
+      } break;
+      case flatbuffers::ElementaryType::ET_CHAR:
+        MergeElement<int8_t>(field_offset, t1, t2, fbb);
+        break;
+      case flatbuffers::ElementaryType::ET_UCHAR:
+        MergeElement<uint8_t>(field_offset, t1, t2, fbb);
+        break;
+      case flatbuffers::ElementaryType::ET_SHORT:
+        MergeElement<int16_t>(field_offset, t1, t2, fbb);
+        break;
+      case flatbuffers::ElementaryType::ET_USHORT:
+        MergeElement<uint16_t>(field_offset, t1, t2, fbb);
+        break;
+      case flatbuffers::ElementaryType::ET_INT:
+        MergeElement<int32_t>(field_offset, t1, t2, fbb);
+        break;
+      case flatbuffers::ElementaryType::ET_UINT:
+        MergeElement<uint32_t>(field_offset, t1, t2, fbb);
+        break;
+      case flatbuffers::ElementaryType::ET_LONG:
+        MergeElement<int64_t>(field_offset, t1, t2, fbb);
+        break;
+      case flatbuffers::ElementaryType::ET_ULONG:
+        MergeElement<uint64_t>(field_offset, t1, t2, fbb);
+        break;
+      case flatbuffers::ElementaryType::ET_FLOAT:
+        MergeElement<float>(field_offset, t1, t2, fbb);
+        break;
+      case flatbuffers::ElementaryType::ET_DOUBLE:
+        MergeElement<double>(field_offset, t1, t2, fbb);
+        break;
+      case flatbuffers::ElementaryType::ET_STRING:
+      case flatbuffers::ElementaryType::ET_SEQUENCE:
+        // Already handled above since this is an uoffset.
+        break;
+    }
+  }
+
+  // And there is no need to check for duplicates since we are creating this
+  // list very carefully from the type table.
+  for (const OffsetAndFieldOffset &element : elements) {
+    fbb->AddOffset(element.field_offset, element.element);
+  }
+
+  return fbb->EndTable(start);
+}
+
+}  // namespace
+
+::std::vector<uint8_t> MergeFlatBuffers(const flatbuffers::TypeTable *typetable,
+                                        const uint8_t *data1,
+                                        const uint8_t *data2) {
+  // Build up a builder.
+  flatbuffers::FlatBufferBuilder fbb;
+  fbb.ForceDefaults(1);
+
+  // Grab the 2 tables.
+  const flatbuffers::Table *t1 =
+      data1 != nullptr ? flatbuffers::GetRoot<flatbuffers::Table>(data1)
+                       : nullptr;
+  const flatbuffers::Table *t2 =
+      data2 != nullptr ? flatbuffers::GetRoot<flatbuffers::Table>(data2)
+                       : nullptr;
+
+  // And then do the actual build.  This doesn't contain finish so we can nest
+  // them nicely.
+  flatbuffers::uoffset_t end = MergeFlatBuffers(typetable, t1, t2, &fbb);
+
+  // Finish up the buffer and return it.
+  fbb.Finish(flatbuffers::Offset<flatbuffers::Table>(end));
+
+  const uint8_t *buf = fbb.GetBufferPointer();
+  const int size = fbb.GetSize();
+  return ::std::vector<uint8_t>(buf, buf + size);
+}
+
+}  // namespace aos
diff --git a/aos/flatbuffer_merge.h b/aos/flatbuffer_merge.h
new file mode 100644
index 0000000..c63b143
--- /dev/null
+++ b/aos/flatbuffer_merge.h
@@ -0,0 +1,23 @@
+#ifndef AOS_FLATBUFFER_MERGE_H_
+#define AOS_FLATBUFFER_MERGE_H_
+
+#include <cstddef>
+#include <string>
+
+#include "flatbuffers/flatbuffers.h"
+
+namespace aos {
+
+::std::vector<uint8_t> MergeFlatBuffers(const flatbuffers::TypeTable *typetable,
+                                        const uint8_t *data1,
+                                        const uint8_t *data2);
+
+template <class T>
+::std::vector<uint8_t> MergeFlatBuffers(const uint8_t *data1,
+                                        const uint8_t *data2) {
+  return MergeFlatBuffers(T::MiniReflectTypeTable(), data1, data2);
+}
+
+}  // namespace aos
+
+#endif  // AOS_FLATBUFFER_MERGE_H_
diff --git a/aos/flatbuffer_merge_test.cc b/aos/flatbuffer_merge_test.cc
new file mode 100644
index 0000000..f102db9
--- /dev/null
+++ b/aos/flatbuffer_merge_test.cc
@@ -0,0 +1,310 @@
+#include "aos/flatbuffer_merge.h"
+
+#include "gtest/gtest.h"
+
+#include "aos/json_to_flatbuffer.h"
+#include "aos/json_to_flatbuffer_generated.h"
+#include "flatbuffers/minireflect.h"
+
+namespace aos {
+namespace testing {
+
+class FlatbufferMerge : public ::testing::Test {
+ public:
+  FlatbufferMerge() {}
+
+  void JsonMerge(const ::std::string in1, const ::std::string in2,
+                 const ::std::string out) {
+    printf("Merging: %s\n", in1.c_str());
+    printf("Merging: %s\n", in2.c_str());
+    const ::std::vector<uint8_t> fb1 = JsonToFlatbuffer(
+        static_cast<const char *>(in1.c_str()), ConfigurationTypeTable());
+
+    const ::std::string in1_nested = "{ \"nested_config\": " + in1 + " }";
+    const ::std::vector<uint8_t> fb1_nested = JsonToFlatbuffer(
+        static_cast<const char *>(in1_nested.c_str()),
+        ConfigurationTypeTable());
+
+    const ::std::vector<uint8_t> fb2 = JsonToFlatbuffer(
+        static_cast<const char *>(in2.c_str()), ConfigurationTypeTable());
+
+    const ::std::string in2_nested = "{ \"nested_config\": " + in2 + " }";
+    const ::std::vector<uint8_t> fb2_nested =
+        JsonToFlatbuffer(static_cast<const char *>(in2_nested.c_str()),
+                         ConfigurationTypeTable());
+
+    const ::std::string out_nested = "{ \"nested_config\": " + out + " }";
+
+    const ::std::vector<uint8_t> empty =
+        JsonToFlatbuffer("{ }", ConfigurationTypeTable());
+
+    ASSERT_NE(fb1.size(), 0u);
+    ASSERT_NE(fb2.size(), 0u);
+    ASSERT_NE(fb1_nested.size(), 0u);
+    ASSERT_NE(fb2_nested.size(), 0u);
+
+    // We now want to run 7 tests.
+    //  in1 merged "" -> in1.
+    //  in2 merged "" -> in2.
+    //  "" merged in1 -> in1.
+    //  "" merged in2 -> in2.
+    //  nullptr merged in1 -> in1.
+    //  in1 merged nullptr -> in1.
+    //  in1 merged in2 -> out.
+
+    {
+      // in1 merged with "" => in1.
+      ::std::vector<uint8_t> fb_merged =
+          MergeFlatBuffers<Configuration>(fb1.data(), empty.data());
+      ASSERT_NE(fb_merged.size(), 0u);
+
+      EXPECT_EQ(in1,
+                FlatbufferToJson(fb_merged.data(), ConfigurationTypeTable()));
+    }
+
+    {
+      // in2 merged with "" => in2.
+      ::std::vector<uint8_t> fb_merged =
+          MergeFlatBuffers<Configuration>(fb2.data(), empty.data());
+      ASSERT_NE(fb_merged.size(), 0u);
+
+      EXPECT_EQ(in2,
+                FlatbufferToJson(fb_merged.data(), ConfigurationTypeTable()));
+    }
+
+    {
+      // "" merged with in1 => in1.
+      ::std::vector<uint8_t> fb_merged =
+          MergeFlatBuffers<Configuration>(empty.data(), fb1.data());
+      ASSERT_NE(fb_merged.size(), 0u);
+
+      EXPECT_EQ(in1,
+                FlatbufferToJson(fb_merged.data(), ConfigurationTypeTable()));
+    }
+
+    {
+      // "" merged with in2 => in2.
+      ::std::vector<uint8_t> fb_merged =
+          MergeFlatBuffers<Configuration>(empty.data(), fb2.data());
+      ASSERT_NE(fb_merged.size(), 0u);
+
+      EXPECT_EQ(in2,
+                FlatbufferToJson(fb_merged.data(), ConfigurationTypeTable()));
+    }
+
+    {
+      // nullptr merged with in1 => in1.
+      ::std::vector<uint8_t> fb_merged =
+          MergeFlatBuffers<Configuration>(nullptr, fb1.data());
+      ASSERT_NE(fb_merged.size(), 0u);
+
+      EXPECT_EQ(in1, FlatbufferToJson(fb_merged.data(), ConfigurationTypeTable()));
+    }
+
+    {
+      // in1 merged with nullptr => in1.
+      ::std::vector<uint8_t> fb_merged =
+          MergeFlatBuffers<Configuration>(fb1.data(), nullptr);
+      ASSERT_NE(fb_merged.size(), 0u);
+
+      EXPECT_EQ(in1, FlatbufferToJson(fb_merged.data(), ConfigurationTypeTable()));
+    }
+
+    {
+      // in1 merged with in2 => out.
+      ::std::vector<uint8_t> fb_merged =
+          MergeFlatBuffers<Configuration>(fb1.data(), fb2.data());
+      ASSERT_NE(fb_merged.size(), 0u);
+
+      const ::std::string merged_output =
+          FlatbufferToJson(fb_merged.data(), ConfigurationTypeTable());
+      EXPECT_EQ(out, merged_output);
+
+      printf("Merged to: %s\n", merged_output.c_str());
+    }
+
+    // Now, to make things extra exciting, nest a config inside a config.  And
+    // run all the tests.  This will exercise some fun nested merges and copies.
+
+    {
+      // in1_nested merged with "" => in1.
+      ::std::vector<uint8_t> fb_merged =
+          MergeFlatBuffers<Configuration>(fb1_nested.data(), empty.data());
+      ASSERT_NE(fb_merged.size(), 0u);
+
+      EXPECT_EQ(in1_nested,
+                FlatbufferToJson(fb_merged.data(), ConfigurationTypeTable()));
+    }
+
+    {
+      // in2_nested merged with "" => in2_nested.
+      ::std::vector<uint8_t> fb_merged =
+          MergeFlatBuffers<Configuration>(fb2_nested.data(), empty.data());
+      ASSERT_NE(fb_merged.size(), 0u);
+
+      EXPECT_EQ(in2_nested,
+                FlatbufferToJson(fb_merged.data(), ConfigurationTypeTable()));
+    }
+
+    {
+      // "" merged with in1_nested => in1_nested.
+      ::std::vector<uint8_t> fb_merged =
+          MergeFlatBuffers<Configuration>(empty.data(), fb1_nested.data());
+      ASSERT_NE(fb_merged.size(), 0u);
+
+      EXPECT_EQ(in1_nested,
+                FlatbufferToJson(fb_merged.data(), ConfigurationTypeTable()));
+    }
+
+    {
+      // "" merged with in2_nested => in2_nested.
+      ::std::vector<uint8_t> fb_merged =
+          MergeFlatBuffers<Configuration>(empty.data(), fb2_nested.data());
+      ASSERT_NE(fb_merged.size(), 0u);
+
+      EXPECT_EQ(in2_nested,
+                FlatbufferToJson(fb_merged.data(), ConfigurationTypeTable()));
+    }
+
+    {
+      // nullptr merged with in1_nested => in1_nested.
+      ::std::vector<uint8_t> fb_merged =
+          MergeFlatBuffers<Configuration>(nullptr, fb1_nested.data());
+      ASSERT_NE(fb_merged.size(), 0u);
+
+      EXPECT_EQ(in1_nested,
+                FlatbufferToJson(fb_merged.data(), ConfigurationTypeTable()));
+    }
+
+    {
+      // nullptr merged with in1_nested => in1_nested.
+      ::std::vector<uint8_t> fb_merged =
+          MergeFlatBuffers<Configuration>(fb1_nested.data(), nullptr);
+      ASSERT_NE(fb_merged.size(), 0u);
+
+      EXPECT_EQ(in1_nested,
+                FlatbufferToJson(fb_merged.data(), ConfigurationTypeTable()));
+    }
+
+    {
+      // in1_nested merged with in2_nested => out_nested.
+      ::std::vector<uint8_t> fb_merged =
+          MergeFlatBuffers<Configuration>(fb1_nested.data(), fb2_nested.data());
+      ASSERT_NE(fb_merged.size(), 0u);
+
+      EXPECT_EQ(out_nested,
+                FlatbufferToJson(fb_merged.data(), ConfigurationTypeTable()));
+    }
+  }
+};
+
+// Test the easy ones.  Test every type, single, no nesting.
+TEST_F(FlatbufferMerge, Basic) {
+  JsonMerge("{ \"foo_bool\": true }", "{ \"foo_bool\": false }",
+            "{ \"foo_bool\": false }");
+
+  JsonMerge("{ \"foo_bool\": false }", "{ \"foo_bool\": true }",
+            "{ \"foo_bool\": true }");
+
+  JsonMerge("{ \"foo_byte\": 5 }", "{ \"foo_byte\": -7 }",
+            "{ \"foo_byte\": -7 }");
+
+  JsonMerge("{ \"foo_ubyte\": 5 }", "{ \"foo_ubyte\": 7 }",
+            "{ \"foo_ubyte\": 7 }");
+
+  JsonMerge("{ \"foo_short\": 5 }", "{ \"foo_short\": 7 }",
+            "{ \"foo_short\": 7 }");
+
+  JsonMerge("{ \"foo_int\": 5 }", "{ \"foo_int\": 7 }", "{ \"foo_int\": 7 }");
+
+  JsonMerge("{ \"foo_uint\": 5 }", "{ \"foo_uint\": 7 }",
+            "{ \"foo_uint\": 7 }");
+
+  JsonMerge("{ \"foo_long\": 5 }", "{ \"foo_long\": 7 }",
+            "{ \"foo_long\": 7 }");
+
+  JsonMerge("{ \"foo_ulong\": 5 }", "{ \"foo_ulong\": 7 }",
+            "{ \"foo_ulong\": 7 }");
+
+  JsonMerge("{ \"foo_float\": 5.0 }", "{ \"foo_float\": 7.1 }",
+            "{ \"foo_float\": 7.1 }");
+
+  JsonMerge("{ \"foo_double\": 5.0 }", "{ \"foo_double\": 7.1 }",
+            "{ \"foo_double\": 7.1 }");
+}
+
+// Test arrays of simple types.
+TEST_F(FlatbufferMerge, Array) {
+  JsonMerge("{ \"foo_string\": \"baz\" }", "{ \"foo_string\": \"bar\" }",
+            "{ \"foo_string\": \"bar\" }");
+
+  JsonMerge("{ \"vector_foo_bool\": [ true, true, false ] }",
+            "{ \"vector_foo_bool\": [ false, true, true, false ] }",
+            "{ \"vector_foo_bool\": [ true, true, false, false, "
+            "true, true, false ] }");
+
+  JsonMerge("{ \"vector_foo_byte\": [ 9, 7, 1 ] }",
+            "{ \"vector_foo_byte\": [ -3, 1, 3, 2 ] }",
+            "{ \"vector_foo_byte\": [ 9, 7, 1, -3, 1, 3, 2 ] }");
+
+  JsonMerge("{ \"vector_foo_ubyte\": [ 9, 7, 1 ] }",
+            "{ \"vector_foo_ubyte\": [ 3, 1, 3, 2 ] }",
+            "{ \"vector_foo_ubyte\": [ 9, 7, 1, 3, 1, 3, 2 ] }");
+
+  JsonMerge("{ \"vector_foo_short\": [ 9, 7, 1 ] }",
+            "{ \"vector_foo_short\": [ -3, 1, 3, 2 ] }",
+            "{ \"vector_foo_short\": [ 9, 7, 1, -3, 1, 3, 2 ] }");
+
+  JsonMerge("{ \"vector_foo_ushort\": [ 9, 7, 1 ] }",
+            "{ \"vector_foo_ushort\": [ 3, 1, 3, 2 ] }",
+            "{ \"vector_foo_ushort\": [ 9, 7, 1, 3, 1, 3, 2 ] }");
+
+  JsonMerge("{ \"vector_foo_int\": [ 9, 7, 1 ] }",
+            "{ \"vector_foo_int\": [ -3, 1, 3, 2 ] }",
+            "{ \"vector_foo_int\": [ 9, 7, 1, -3, 1, 3, 2 ] }");
+
+  JsonMerge("{ \"vector_foo_uint\": [ 9, 7, 1 ] }",
+            "{ \"vector_foo_uint\": [ 3, 1, 3, 2 ] }",
+            "{ \"vector_foo_uint\": [ 9, 7, 1, 3, 1, 3, 2 ] }");
+
+  JsonMerge("{ \"vector_foo_long\": [ 9, 7, 1 ] }",
+            "{ \"vector_foo_long\": [ -3, 1, 3, 2 ] }",
+            "{ \"vector_foo_long\": [ 9, 7, 1, -3, 1, 3, 2 ] }");
+
+  JsonMerge("{ \"vector_foo_ulong\": [ 9, 7, 1 ] }",
+            "{ \"vector_foo_ulong\": [ 3, 1, 3, 2 ] }",
+            "{ \"vector_foo_ulong\": [ 9, 7, 1, 3, 1, 3, 2 ] }");
+
+  JsonMerge("{ \"vector_foo_float\": [ 9.0, 7.0, 1.0 ] }",
+            "{ \"vector_foo_float\": [ -3.0, 1.3, 3.0, 2.0 ] }",
+            "{ \"vector_foo_float\": [ 9.0, 7.0, 1.0, -3.0, 1.3, 3.0, 2.0 ] }");
+
+  JsonMerge("{ \"vector_foo_string\": [ \"9\", \"7\", \"1 \" ] }",
+            "{ \"vector_foo_string\": [ \"31\", \"32\" ] }",
+            "{ \"vector_foo_string\": [ \"9\", \"7\", \"1 \", \"31\", \"32\" ] }");
+}
+
+// Test nested messages, and arrays of nested messages.
+TEST_F(FlatbufferMerge, NestedStruct) {
+  JsonMerge(
+      "{ \"single_application\": { \"name\": \"woot\" } }",
+      "{ \"single_application\": { \"name\": \"wow\", \"priority\": 7 } }",
+      "{ \"single_application\": { \"name\": \"wow\", \"priority\": 7 } }");
+
+  JsonMerge(
+      "{ \"single_application\": { \"name\": \"wow\", \"priority\": 7 } }",
+      "{ \"single_application\": { \"name\": \"woot\" } }",
+      "{ \"single_application\": { \"name\": \"woot\", \"priority\": 7 } }");
+
+  JsonMerge("{ \"apps\": [ { \"name\": \"woot\" }, { \"name\": \"wow\" } ] }",
+            "{ \"apps\": [ { \"name\": \"woo2\" }, { \"name\": \"wo3\" } ] }",
+            "{ \"apps\": [ { \"name\": \"woot\" }, { \"name\": \"wow\" }, { "
+            "\"name\": \"woo2\" }, { \"name\": \"wo3\" } ] }");
+}
+
+// TODO(austin): enums
+// TODO(austin): unions
+// TODO(austin): struct
+
+}  // namespace testing
+}  // namespace aos
diff --git a/aos/json_to_flatbuffer.fbs b/aos/json_to_flatbuffer.fbs
index 9743345..2e90a53 100644
--- a/aos/json_to_flatbuffer.fbs
+++ b/aos/json_to_flatbuffer.fbs
@@ -14,13 +14,14 @@
 
 table Application {
   name:string;
+  priority:int;
   maps:[Map];
 }
 
 table Configuration {
   locations:[Location] (id: 0);
   maps:[Map] (id: 1);
-  applications:[Application] (id: 2);
+  apps:[Application] (id: 2);
   imports:[string] (id: 3);
 
   // 8 bit: byte ubyte bool
@@ -68,6 +69,11 @@
 
   // And a simple nested application.
   single_application:Application (id: 28);
+
+  // TODO(austin): enum
+
+  // And nest this object to get some crazy coverage.
+  nested_config:Configuration (id: 29);
 }
 
 root_type Configuration;
diff --git a/aos/json_to_flatbuffer_test.cc b/aos/json_to_flatbuffer_test.cc
index 4b0629e..82bbd52 100644
--- a/aos/json_to_flatbuffer_test.cc
+++ b/aos/json_to_flatbuffer_test.cc
@@ -67,6 +67,7 @@
   EXPECT_TRUE(JsonAndBack("{ \"foo_double\": 5.1 }"));
 }
 
+
 // Test what happens if you pass a field name that we don't know.
 TEST_F(JsonToFlatbufferTest, InvalidFieldName) {
   EXPECT_FALSE(JsonAndBack("{ \"foo\": 5 }"));
@@ -89,13 +90,12 @@
 
   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\" } ] , }"));
+      JsonAndBack("{ \"apps\":\n[\n{\n\"name\": \"woot\"\n},\n{\n\"name\": "
+                  "\"wow\"\n} ,\n]\n}"));
+
+  EXPECT_FALSE(JsonAndBack(
+      "{ \"apps\": [ { \"name\": \"woot\" }, { \"name\": \"wow\" } ] , }"));
 
   EXPECT_FALSE(
       JsonAndBack("{ \"vector_foo_string\": [ \"bar\", \"baz\" ] , }"));
@@ -134,17 +134,21 @@
   EXPECT_TRUE(
       JsonAndBack("{ \"single_application\": { \"name\": \"woot\" } }"));
 
-  EXPECT_TRUE(
-      JsonAndBack("{ \"applications\": [ { \"name\": \"woot\" }, { \"name\": "
-                  "\"wow\" } ] }"));
+  EXPECT_TRUE(JsonAndBack(
+      "{ \"apps\": [ { \"name\": \"woot\" }, { \"name\": \"wow\" } ] }"));
 }
 
 // Test that we can parse an empty message.
 TEST_F(JsonToFlatbufferTest, EmptyMessage) {
+  // Empty message works.
   EXPECT_TRUE(JsonAndBack("{  }"));
 }
 
+
 // TODO(austin): Missmatched values.
+// TODO(austin): enums
+//
+// TODO(austin): unions?
 
 }  // namespace testing
 }  // namespace aos