Add struct support to the flatbuffers domain

PiperOrigin-RevId: 743170054
diff --git a/domain_tests/BUILD b/domain_tests/BUILD
index 0dfd652..e2b7215 100644
--- a/domain_tests/BUILD
+++ b/domain_tests/BUILD
@@ -46,11 +46,13 @@
     deps = [
         ":domain_testing",
         "@abseil-cpp//absl/container:flat_hash_map",
+        "@abseil-cpp//absl/log:check",
         "@abseil-cpp//absl/random",
         "@abseil-cpp//absl/status",
         "@com_google_fuzztest//fuzztest:domain",
         "@com_google_fuzztest//fuzztest:flatbuffers",
         "@com_google_fuzztest//fuzztest/internal:meta",
+        "@com_google_fuzztest//fuzztest/internal:serialization",
         "@com_google_fuzztest//fuzztest/internal:test_flatbuffers_cc_fbs",
         "@flatbuffers//:runtime_cc",
         "@googletest//:gtest_main",
diff --git a/domain_tests/arbitrary_domains_flatbuffers_test.cc b/domain_tests/arbitrary_domains_flatbuffers_test.cc
index d96c24d..59bb0c4 100644
--- a/domain_tests/arbitrary_domains_flatbuffers_test.cc
+++ b/domain_tests/arbitrary_domains_flatbuffers_test.cc
@@ -19,11 +19,14 @@
 #include <cstring>
 #include <optional>
 #include <string>
+#include <type_traits>
+#include <utility>
 #include <vector>
 
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
 #include "absl/container/flat_hash_map.h"
+#include "absl/log/check.h"
 #include "absl/random/random.h"
 #include "absl/status/status.h"
 #include "flatbuffers/base.h"
@@ -32,23 +35,74 @@
 #include "flatbuffers/reflection_generated.h"
 #include "flatbuffers/string.h"
 #include "flatbuffers/vector.h"
+#include "flatbuffers/verifier.h"
 #include "./fuzztest/domain.h"
 #include "./domain_tests/domain_testing.h"
 #include "./fuzztest/flatbuffers.h"
 #include "./fuzztest/internal/meta.h"
+#include "./fuzztest/internal/serialization.h"
 #include "./fuzztest/internal/test_flatbuffers_64bits_generated.h"
 #include "./fuzztest/internal/test_flatbuffers_generated.h"
 
 namespace fuzztest {
+namespace internal {
+template <typename Sink>
+void AbslStringify(Sink& sink, const Union& e) {
+  absl::Format(&sink, "%s", EnumNameUnion(e));
+}
+template <typename Sink>
+void AbslStringify(Sink& sink, const ByteEnum& e) {
+  absl::Format(&sink, "%s", EnumNameByteEnum(e));
+}
+template <typename Sink>
+void AbslStringify(Sink& sink, const ShortEnum& e) {
+  absl::Format(&sink, "%s", EnumNameShortEnum(e));
+}
+template <typename Sink>
+void AbslStringify(Sink& sink, const IntEnum& e) {
+  absl::Format(&sink, "%s", EnumNameIntEnum(e));
+}
+template <typename Sink>
+void AbslStringify(Sink& sink, const LongEnum& e) {
+  absl::Format(&sink, "%s", EnumNameLongEnum(e));
+}
+template <typename Sink>
+void AbslStringify(Sink& sink, const UByteEnum& e) {
+  absl::Format(&sink, "%s", EnumNameUByteEnum(e));
+}
+template <typename Sink>
+void AbslStringify(Sink& sink, const UShortEnum& e) {
+  absl::Format(&sink, "%s", EnumNameUShortEnum(e));
+}
+template <typename Sink>
+void AbslStringify(Sink& sink, const UIntEnum& e) {
+  absl::Format(&sink, "%s", EnumNameUIntEnum(e));
+}
+template <typename Sink>
+void AbslStringify(Sink& sink, const ULongEnum& e) {
+  absl::Format(&sink, "%s", EnumNameULongEnum(e));
+}
+}  // namespace internal
 namespace {
 
+using ::fuzztest::internal::BoolStruct;
 using ::fuzztest::internal::BoolTable;
+using ::fuzztest::internal::ByteEnum;
+using ::fuzztest::internal::DefaultStruct;
 using ::fuzztest::internal::DefaultTable;
 using ::fuzztest::internal::DefaultTable64;
+using ::fuzztest::internal::IntEnum;
+using ::fuzztest::internal::LongEnum;
 using ::fuzztest::internal::OptionalTable;
 using ::fuzztest::internal::RecursiveTable;
 using ::fuzztest::internal::RequiredTable;
-using ::fuzztest::internal::UnsupportedTypesTable;
+using ::fuzztest::internal::ShortEnum;
+using ::fuzztest::internal::StringTable;
+using ::fuzztest::internal::UByteEnum;
+using ::fuzztest::internal::UIntEnum;
+using ::fuzztest::internal::ULongEnum;
+using ::fuzztest::internal::UnionTable;
+using ::fuzztest::internal::UShortEnum;
 using ::testing::_;
 using ::testing::AllOf;
 using ::testing::Each;
@@ -73,6 +127,40 @@
 }
 
 template <>
+inline bool Eq<BoolStruct>(const BoolStruct& lhs, const BoolStruct& rhs) {
+  return Eq(lhs.b(), rhs.b());
+}
+
+template <>
+inline bool Eq<DefaultStruct>(const DefaultStruct& lhs,
+                              const DefaultStruct& rhs) {
+  const bool eq_b = lhs.b() == rhs.b();
+  const bool eq_i8 = lhs.i8() == rhs.i8();
+  const bool eq_i16 = lhs.i16() == rhs.i16();
+  const bool eq_i32 = lhs.i32() == rhs.i32();
+  const bool eq_i64 = lhs.i64() == rhs.i64();
+  const bool eq_u8 = lhs.u8() == rhs.u8();
+  const bool eq_u16 = lhs.u16() == rhs.u16();
+  const bool eq_u32 = lhs.u32() == rhs.u32();
+  const bool eq_u64 = lhs.u64() == rhs.u64();
+  const bool eq_f = lhs.f() == rhs.f();
+  const bool eq_d = lhs.d() == rhs.d();
+  const bool eq_ei8 = lhs.ei8() == rhs.ei8();
+  const bool eq_ei16 = lhs.ei16() == rhs.ei16();
+  const bool eq_ei32 = lhs.ei32() == rhs.ei32();
+  const bool eq_ei64 = lhs.ei64() == rhs.ei64();
+  const bool eq_eu8 = lhs.eu8() == rhs.eu8();
+  const bool eq_eu16 = lhs.eu16() == rhs.eu16();
+  const bool eq_eu32 = lhs.eu32() == rhs.eu32();
+  const bool eq_eu64 = lhs.eu64() == rhs.eu64();
+  const bool eq_s = Eq(lhs.s(), rhs.s());
+
+  return eq_b && eq_i8 && eq_i16 && eq_i32 && eq_i64 && eq_u8 && eq_u16 &&
+         eq_u32 && eq_u64 && eq_f && eq_d && eq_ei8 && eq_ei16 && eq_ei32 &&
+         eq_ei64 && eq_eu8 && eq_eu16 && eq_eu32 && eq_eu64 && eq_s;
+}
+
+template <>
 inline bool Eq<flatbuffers::String>(const flatbuffers::String& lhs,
                                     const flatbuffers::String& rhs) {
   if (lhs.size() != rhs.size()) return false;
@@ -84,6 +172,78 @@
   return lhs.b() == rhs.b();
 }
 
+template <typename T>
+inline bool Eq(const flatbuffers::Vector<T>& lhs,
+               const flatbuffers::Vector<T>& rhs) {
+  if (lhs.size() != rhs.size()) return false;
+  for (int i = 0; i < lhs.size(); ++i) {
+    if (!Eq(lhs.Get(i), rhs.Get(i))) return false;
+  }
+  return true;
+}
+
+template <>
+inline bool Eq<StringTable>(const StringTable& lhs, const StringTable& rhs) {
+  return Eq(lhs.str(), rhs.str());
+}
+
+template <>
+inline bool Eq<std::pair<uint8_t, const void*>>(
+    const std::pair<uint8_t, const void*>& lhs,
+    const std::pair<uint8_t, const void*>& rhs) {
+  if (lhs.first == internal::Union_NONE && rhs.first == internal::Union_NONE) {
+    return true;
+  }
+  if (lhs.first != rhs.first) return false;
+
+  switch (lhs.first) {
+    case internal::Union_BoolTable:
+      return Eq(static_cast<const BoolTable*>(lhs.second),
+                static_cast<const BoolTable*>(rhs.second));
+    case internal::Union_StringTable:
+      return Eq(static_cast<const StringTable*>(lhs.second),
+                static_cast<const StringTable*>(rhs.second));
+    case internal::Union_BoolStruct:
+      return Eq(static_cast<const BoolStruct*>(rhs.second),
+                static_cast<const BoolStruct*>(lhs.second));
+    default:
+      CHECK(false) << "Unsupported union type";
+  }
+}
+
+template <typename T>
+inline bool Eq(const flatbuffers::Vector<T>* lhs,
+               const flatbuffers::Vector<T>* rhs) {
+  if (lhs == nullptr && rhs == nullptr) return true;
+  if (lhs == nullptr || rhs == nullptr) return false;
+  return Eq(*lhs, *rhs);
+}
+
+template <>
+inline bool
+Eq<std::pair<const flatbuffers::Vector<uint8_t>*,
+             const flatbuffers::Vector<::flatbuffers::Offset<void>>*>>(
+    const std::pair<const flatbuffers::Vector<uint8_t>*,
+                    const flatbuffers::Vector<::flatbuffers::Offset<void>>*>&
+        lhs,
+    const std::pair<const flatbuffers::Vector<uint8_t>*,
+                    const flatbuffers::Vector<::flatbuffers::Offset<void>>*>&
+        rhs) {
+  if (!Eq(lhs.first, rhs.first)) return false;
+  if (lhs.second == nullptr && rhs.second == nullptr) return true;
+  if (lhs.second == nullptr || rhs.second == nullptr) return false;
+  if (lhs.first->size() != lhs.second->size()) return false;
+  if (lhs.second->size() != rhs.second->size()) return false;
+
+  for (int i = 0; i < lhs.second->size(); ++i) {
+    if (!Eq(std::pair(lhs.first->Get(i), lhs.second->Get(i)),
+            std::pair(rhs.first->Get(i), rhs.second->Get(i)))) {
+      return false;
+    }
+  }
+  return true;
+}
+
 template <>
 inline bool Eq<DefaultTable>(const DefaultTable& lhs, const DefaultTable& rhs) {
   const bool eq_b = lhs.b() == rhs.b();
@@ -107,14 +267,111 @@
   const bool eq_eu32 = lhs.eu32() == rhs.eu32();
   const bool eq_eu64 = lhs.eu64() == rhs.eu64();
   const bool eq_t = Eq(lhs.t(), rhs.t());
+  const bool eq_u = Eq(std::pair(static_cast<uint8_t>(lhs.u_type()), lhs.u()),
+                       std::pair(static_cast<uint8_t>(rhs.u_type()), rhs.u()));
+  const bool eq_s = Eq(lhs.s(), rhs.s());
+  const bool eq_v_b = Eq(lhs.v_b(), rhs.v_b());
+  const bool eq_v_i8 = Eq(lhs.v_i8(), rhs.v_i8());
+  const bool eq_v_i16 = Eq(lhs.v_i16(), rhs.v_i16());
+  const bool eq_v_i32 = Eq(lhs.v_i32(), rhs.v_i32());
+  const bool eq_v_i64 = Eq(lhs.v_i64(), rhs.v_i64());
+  const bool eq_v_u8 = Eq(lhs.v_u8(), rhs.v_u8());
+  const bool eq_v_u16 = Eq(lhs.v_u16(), rhs.v_u16());
+  const bool eq_v_u32 = Eq(lhs.v_u32(), rhs.v_u32());
+  const bool eq_v_u64 = Eq(lhs.v_u64(), rhs.v_u64());
+  const bool eq_v_f = Eq(lhs.v_f(), rhs.v_f());
+  const bool eq_v_d = Eq(lhs.v_d(), rhs.v_d());
+  const bool eq_v_str = Eq(lhs.v_str(), rhs.v_str());
+  const bool eq_v_ei8 = Eq(lhs.v_ei8(), rhs.v_ei8());
+  const bool eq_v_ei16 = Eq(lhs.v_ei16(), rhs.v_ei16());
+  const bool eq_v_ei32 = Eq(lhs.v_ei32(), rhs.v_ei32());
+  const bool eq_v_ei64 = Eq(lhs.v_ei64(), rhs.v_ei64());
+  const bool eq_v_eu8 = Eq(lhs.v_eu8(), rhs.v_eu8());
+  const bool eq_v_eu16 = Eq(lhs.v_eu16(), rhs.v_eu16());
+  const bool eq_v_eu32 = Eq(lhs.v_eu32(), rhs.v_eu32());
+  const bool eq_v_eu64 = Eq(lhs.v_eu64(), rhs.v_eu64());
+  const bool eq_v_t = Eq(lhs.v_t(), rhs.v_t());
+  const bool eq_v_u_type = Eq(lhs.v_u_type(), rhs.v_u_type());
+  const bool eq_v_u = Eq(std::make_pair(lhs.v_u_type(), lhs.v_u()),
+                         std::make_pair(rhs.v_u_type(), rhs.v_u()));
+  const bool eq_v_s = Eq(lhs.v_s(), rhs.v_s());
   return eq_b && eq_i8 && eq_i16 && eq_i32 && eq_i64 && eq_u8 && eq_u16 &&
          eq_u32 && eq_u64 && eq_f && eq_d && eq_str && eq_ei8 && eq_ei16 &&
-         eq_ei32 && eq_ei64 && eq_eu8 && eq_eu16 && eq_eu32 && eq_eu64 && eq_t;
+         eq_ei32 && eq_ei64 && eq_eu8 && eq_eu16 && eq_eu32 && eq_eu64 &&
+         eq_t && eq_u && eq_s && eq_v_b && eq_v_i8 && eq_v_i16 && eq_v_i32 &&
+         eq_v_i64 && eq_v_u8 && eq_v_u16 && eq_v_u32 && eq_v_u64 && eq_v_f &&
+         eq_v_d && eq_v_str && eq_v_ei8 && eq_v_ei16 && eq_v_ei32 &&
+         eq_v_ei64 && eq_v_eu8 && eq_v_eu16 && eq_v_eu32 && eq_v_eu64 &&
+         eq_v_t && eq_v_u_type && eq_v_u && eq_v_s;
 }
 
 const internal::DefaultTable* CreateDefaultTable(
     flatbuffers::FlatBufferBuilder& fbb) {
   auto bool_table_offset = internal::CreateBoolTable(fbb, true);
+  auto string_table_offset =
+      internal::CreateStringTableDirect(fbb, "foo bar baz");
+  DefaultStruct s{
+      true,                        // b
+      1,                           // i8
+      2,                           // i16
+      3,                           // i32
+      4,                           // i64
+      5,                           // u8
+      6,                           // u16
+      7,                           // u32
+      8,                           // u64
+      9,                           // f
+      10.0,                        // d
+      internal::ByteEnum_First,    // ei8
+      internal::ShortEnum_First,   // ei16
+      internal::IntEnum_First,     // ei32
+      internal::LongEnum_First,    // ei64
+      internal::UByteEnum_First,   // eu8
+      internal::UShortEnum_First,  // eu16
+      internal::UIntEnum_First,    // eu32
+      internal::ULongEnum_First,   // eu64
+      BoolStruct{true}             // s
+  };
+  std::vector<uint8_t> v_b{true, false};
+  std::vector<int8_t> v_i8{1, 2, 3};
+  std::vector<int16_t> v_i16{1, 2, 3};
+  std::vector<int32_t> v_i32{1, 2, 3};
+  std::vector<int64_t> v_i64{1, 2, 3};
+  std::vector<uint8_t> v_u8{1, 2, 3};
+  std::vector<uint16_t> v_u16{1, 2, 3};
+  std::vector<uint32_t> v_u32{1, 2, 3};
+  std::vector<uint64_t> v_u64{1, 2, 3};
+  std::vector<float> v_f{1, 2, 3};
+  std::vector<double> v_d{1, 2, 3};
+  std::vector<flatbuffers::Offset<flatbuffers::String>> v_str{
+      fbb.CreateString("foo"), fbb.CreateString("bar"),
+      fbb.CreateString("baz")};
+  std::vector<std::underlying_type_t<ByteEnum>> v_ei8{
+      internal::ByteEnum_First, internal::ByteEnum_Second};
+  std::vector<std::underlying_type_t<ShortEnum>> v_ei16{
+      internal::ShortEnum_First, internal::ShortEnum_Second};
+  std::vector<std::underlying_type_t<IntEnum>> v_ei32{internal::IntEnum_First,
+                                                      internal::IntEnum_Second};
+  std::vector<std::underlying_type_t<LongEnum>> v_ei64{
+      internal::LongEnum_First, internal::LongEnum_Second};
+  std::vector<std::underlying_type_t<UByteEnum>> v_eu8{
+      internal::UByteEnum_First, internal::UByteEnum_Second};
+  std::vector<std::underlying_type_t<UShortEnum>> v_eu16{
+      internal::UShortEnum_First, internal::UShortEnum_Second};
+  std::vector<std::underlying_type_t<UIntEnum>> v_eu32{
+      internal::UIntEnum_First, internal::UIntEnum_Second};
+  std::vector<std::underlying_type_t<ULongEnum>> v_eu64{
+      internal::ULongEnum_First, internal::ULongEnum_Second};
+  std::vector<flatbuffers::Offset<BoolTable>> v_t{bool_table_offset};
+  std::vector<std::underlying_type_t<internal::Union>> v_u_type{
+      internal::Union_BoolTable,
+      internal::Union_StringTable,
+  };
+  std::vector<flatbuffers::Offset<>> v_u{
+      bool_table_offset.Union(),
+      string_table_offset.Union(),
+  };
+  std::vector<DefaultStruct> v_s{s};
   auto table_offset =
       internal::CreateDefaultTableDirect(fbb,
                                          /*b=*/true,
@@ -137,7 +394,34 @@
                                          /*eu16=*/internal::UShortEnum_Second,
                                          /*eu32=*/internal::UIntEnum_Second,
                                          /*eu64=*/internal::ULongEnum_Second,
-                                         /*t=*/bool_table_offset);
+                                         /*t=*/bool_table_offset,
+                                         /*u_type=*/internal::Union_BoolTable,
+                                         /*u=*/bool_table_offset.Union(),
+                                         /*s=*/&s,
+                                         /*v_b=*/&v_b,
+                                         /*v_i8=*/&v_i8,
+                                         /*v_i16=*/&v_i16,
+                                         /*v_i32=*/&v_i32,
+                                         /*v_i64=*/&v_i64,
+                                         /*v_u8=*/&v_u8,
+                                         /*v_u16=*/&v_u16,
+                                         /*v_u32=*/&v_u32,
+                                         /*v_u64=*/&v_u64,
+                                         /*v_f=*/&v_f,
+                                         /*v_d=*/&v_d,
+                                         /*v_str=*/&v_str,
+                                         /*v_ei8=*/&v_ei8,
+                                         /*v_ei16=*/&v_ei16,
+                                         /*v_ei32=*/&v_ei32,
+                                         /*v_ei64=*/&v_ei64,
+                                         /*v_eu8=*/&v_eu8,
+                                         /*v_eu16=*/&v_eu16,
+                                         /*v_eu32=*/&v_eu32,
+                                         /*v_eu64=*/&v_eu64,
+                                         /*v_t=*/&v_t,
+                                         /*v_u_type=*/&v_u_type,
+                                         /*v_u=*/&v_u,
+                                         /*v_s=*/&v_s);
   fbb.Finish(table_offset);
   return flatbuffers::GetRoot<DefaultTable>(fbb.GetBufferPointer());
 }
@@ -263,6 +547,7 @@
   EXPECT_EQ(new_table->u64(), 8);
   EXPECT_EQ(new_table->f(), 9.0);
   EXPECT_EQ(new_table->d(), 10.0);
+  ASSERT_THAT(new_table->str(), NotNull());
   EXPECT_EQ(new_table->str()->str(), "foo bar baz");
   EXPECT_EQ(new_table->ei8(), internal::ByteEnum_Second);
   EXPECT_EQ(new_table->ei16(), internal::ShortEnum_Second);
@@ -274,6 +559,159 @@
   EXPECT_EQ(new_table->eu64(), internal::ULongEnum_Second);
   ASSERT_THAT(new_table->t(), NotNull());
   EXPECT_EQ(new_table->t()->b(), true);
+  EXPECT_EQ(new_table->u_type(), internal::Union_BoolTable);
+  ASSERT_THAT(new_table->u(), NotNull());
+  EXPECT_EQ(new_table->u_as_BoolTable()->b(), true);
+  ASSERT_THAT(new_table->s(), NotNull());
+  EXPECT_EQ(new_table->s()->b(), true);
+  EXPECT_EQ(new_table->s()->i8(), 1);
+  EXPECT_EQ(new_table->s()->i16(), 2);
+  EXPECT_EQ(new_table->s()->i32(), 3);
+  EXPECT_EQ(new_table->s()->i64(), 4);
+  EXPECT_EQ(new_table->s()->u8(), 5);
+  EXPECT_EQ(new_table->s()->u16(), 6);
+  EXPECT_EQ(new_table->s()->u32(), 7);
+  EXPECT_EQ(new_table->s()->u64(), 8);
+  EXPECT_EQ(new_table->s()->f(), 9.0);
+  EXPECT_EQ(new_table->s()->d(), 10.0);
+  EXPECT_EQ(new_table->s()->ei8(), internal::ByteEnum_First);
+  EXPECT_EQ(new_table->s()->ei16(), internal::ShortEnum_First);
+  EXPECT_EQ(new_table->s()->ei32(), internal::IntEnum_First);
+  EXPECT_EQ(new_table->s()->ei64(), internal::LongEnum_First);
+  EXPECT_EQ(new_table->s()->eu8(), internal::UByteEnum_First);
+  EXPECT_EQ(new_table->s()->eu16(), internal::UShortEnum_First);
+  EXPECT_EQ(new_table->s()->eu32(), internal::UIntEnum_First);
+  EXPECT_EQ(new_table->s()->eu64(), internal::ULongEnum_First);
+  EXPECT_EQ(new_table->s()->s().b(), true);
+  ASSERT_THAT(new_table->v_b(), NotNull());
+  EXPECT_EQ(new_table->v_b()->size(), 2);
+  EXPECT_EQ(new_table->v_b()->Get(0), true);
+  EXPECT_EQ(new_table->v_b()->Get(1), false);
+  ASSERT_THAT(new_table->v_i8(), NotNull());
+  EXPECT_EQ(new_table->v_i8()->size(), 3);
+  EXPECT_EQ(new_table->v_i8()->Get(0), 1);
+  EXPECT_EQ(new_table->v_i8()->Get(1), 2);
+  EXPECT_EQ(new_table->v_i8()->Get(2), 3);
+  ASSERT_THAT(new_table->v_i16(), NotNull());
+  EXPECT_EQ(new_table->v_i16()->size(), 3);
+  EXPECT_EQ(new_table->v_i16()->Get(0), 1);
+  EXPECT_EQ(new_table->v_i16()->Get(1), 2);
+  EXPECT_EQ(new_table->v_i16()->Get(2), 3);
+  ASSERT_THAT(new_table->v_i32(), NotNull());
+  EXPECT_EQ(new_table->v_i32()->size(), 3);
+  EXPECT_EQ(new_table->v_i32()->Get(0), 1);
+  EXPECT_EQ(new_table->v_i32()->Get(1), 2);
+  EXPECT_EQ(new_table->v_i32()->Get(2), 3);
+  ASSERT_THAT(new_table->v_i64(), NotNull());
+  EXPECT_EQ(new_table->v_i64()->size(), 3);
+  EXPECT_EQ(new_table->v_i64()->Get(0), 1);
+  EXPECT_EQ(new_table->v_i64()->Get(1), 2);
+  EXPECT_EQ(new_table->v_i64()->Get(2), 3);
+  ASSERT_THAT(new_table->v_u8(), NotNull());
+  EXPECT_EQ(new_table->v_u8()->size(), 3);
+  EXPECT_EQ(new_table->v_u8()->Get(0), 1);
+  EXPECT_EQ(new_table->v_u8()->Get(1), 2);
+  EXPECT_EQ(new_table->v_u8()->Get(2), 3);
+  ASSERT_THAT(new_table->v_u16(), NotNull());
+  EXPECT_EQ(new_table->v_u16()->size(), 3);
+  EXPECT_EQ(new_table->v_u16()->Get(0), 1);
+  EXPECT_EQ(new_table->v_u16()->Get(1), 2);
+  EXPECT_EQ(new_table->v_u16()->Get(2), 3);
+  ASSERT_THAT(new_table->v_u32(), NotNull());
+  EXPECT_EQ(new_table->v_u32()->size(), 3);
+  EXPECT_EQ(new_table->v_u32()->Get(0), 1);
+  EXPECT_EQ(new_table->v_u32()->Get(1), 2);
+  EXPECT_EQ(new_table->v_u32()->Get(2), 3);
+  ASSERT_THAT(new_table->v_u64(), NotNull());
+  EXPECT_EQ(new_table->v_u64()->size(), 3);
+  EXPECT_EQ(new_table->v_u64()->Get(0), 1);
+  EXPECT_EQ(new_table->v_u64()->Get(1), 2);
+  EXPECT_EQ(new_table->v_u64()->Get(2), 3);
+  ASSERT_THAT(new_table->v_f(), NotNull());
+  EXPECT_EQ(new_table->v_f()->size(), 3);
+  EXPECT_EQ(new_table->v_f()->Get(0), 1);
+  EXPECT_EQ(new_table->v_f()->Get(1), 2);
+  EXPECT_EQ(new_table->v_f()->Get(2), 3);
+  ASSERT_THAT(new_table->v_d(), NotNull());
+  EXPECT_EQ(new_table->v_d()->size(), 3);
+  EXPECT_EQ(new_table->v_d()->Get(0), 1);
+  EXPECT_EQ(new_table->v_d()->Get(1), 2);
+  EXPECT_EQ(new_table->v_d()->Get(2), 3);
+  EXPECT_EQ(new_table->v_str()->size(), 3);
+  EXPECT_EQ(new_table->v_str()->Get(0)->str(), "foo");
+  EXPECT_EQ(new_table->v_str()->Get(1)->str(), "bar");
+  EXPECT_EQ(new_table->v_str()->Get(2)->str(), "baz");
+  ASSERT_THAT(new_table->v_ei8(), NotNull());
+  EXPECT_EQ(new_table->v_ei8()->size(), 2);
+  EXPECT_EQ(new_table->v_ei8()->Get(0), internal::ByteEnum_First);
+  EXPECT_EQ(new_table->v_ei8()->Get(1), internal::ByteEnum_Second);
+  ASSERT_THAT(new_table->v_ei16(), NotNull());
+  EXPECT_EQ(new_table->v_ei16()->size(), 2);
+  EXPECT_EQ(new_table->v_ei16()->Get(0), internal::ShortEnum_First);
+  EXPECT_EQ(new_table->v_ei16()->Get(1), internal::ShortEnum_Second);
+  ASSERT_THAT(new_table->v_ei32(), NotNull());
+  EXPECT_EQ(new_table->v_ei32()->size(), 2);
+  EXPECT_EQ(new_table->v_ei32()->Get(0), internal::IntEnum_First);
+  EXPECT_EQ(new_table->v_ei32()->Get(1), internal::IntEnum_Second);
+  ASSERT_THAT(new_table->v_ei64(), NotNull());
+  EXPECT_EQ(new_table->v_ei64()->size(), 2);
+  EXPECT_EQ(new_table->v_ei64()->Get(0), internal::LongEnum_First);
+  EXPECT_EQ(new_table->v_ei64()->Get(1), internal::LongEnum_Second);
+  ASSERT_THAT(new_table->v_eu8(), NotNull());
+  EXPECT_EQ(new_table->v_eu8()->size(), 2);
+  EXPECT_EQ(new_table->v_eu8()->Get(0), internal::UByteEnum_First);
+  EXPECT_EQ(new_table->v_eu8()->Get(1), internal::UByteEnum_Second);
+  ASSERT_THAT(new_table->v_eu16(), NotNull());
+  EXPECT_EQ(new_table->v_eu16()->size(), 2);
+  EXPECT_EQ(new_table->v_eu16()->Get(0), internal::UShortEnum_First);
+  EXPECT_EQ(new_table->v_eu16()->Get(1), internal::UShortEnum_Second);
+  ASSERT_THAT(new_table->v_eu32(), NotNull());
+  EXPECT_EQ(new_table->v_eu32()->size(), 2);
+  EXPECT_EQ(new_table->v_eu32()->Get(0), internal::UIntEnum_First);
+  EXPECT_EQ(new_table->v_eu32()->Get(1), internal::UIntEnum_Second);
+  ASSERT_THAT(new_table->v_t(), NotNull());
+  EXPECT_EQ(new_table->v_t()->size(), 1);
+  ASSERT_THAT(new_table->v_t()->Get(0), NotNull());
+  EXPECT_EQ(new_table->v_t()->Get(0)->b(), true);
+  ASSERT_THAT(new_table->v_u_type(), NotNull());
+  ASSERT_EQ(new_table->v_u_type()->size(), 2);
+  EXPECT_EQ(new_table->v_u_type()->Get(0), internal::Union_BoolTable);
+  EXPECT_EQ(new_table->v_u_type()->Get(1), internal::Union_StringTable);
+  ASSERT_THAT(new_table->v_u(), NotNull());
+  EXPECT_EQ(new_table->v_u()->size(), 2);
+  auto v_u_0 =
+      static_cast<const internal::BoolTable*>(new_table->v_u()->Get(0));
+  ASSERT_THAT(v_u_0, NotNull());
+  EXPECT_EQ(v_u_0->b(), true);
+  auto v_u_1 =
+      static_cast<const internal::StringTable*>(new_table->v_u()->Get(1));
+  ASSERT_THAT(v_u_1, NotNull());
+  ASSERT_THAT(v_u_1->str(), NotNull());
+  EXPECT_EQ(v_u_1->str()->str(), "foo bar baz");
+  ASSERT_THAT(new_table->v_s(), NotNull());
+  ASSERT_EQ(new_table->v_s()->size(), 1);
+  auto v_s_0 = new_table->v_s()->Get(0);
+  ASSERT_THAT(v_s_0, NotNull());
+  EXPECT_EQ(v_s_0->b(), true);
+  EXPECT_EQ(v_s_0->i8(), 1);
+  EXPECT_EQ(v_s_0->i16(), 2);
+  EXPECT_EQ(v_s_0->i32(), 3);
+  EXPECT_EQ(v_s_0->i64(), 4);
+  EXPECT_EQ(v_s_0->u8(), 5);
+  EXPECT_EQ(v_s_0->u16(), 6);
+  EXPECT_EQ(v_s_0->u32(), 7);
+  EXPECT_EQ(v_s_0->u64(), 8);
+  EXPECT_EQ(v_s_0->f(), 9.0);
+  EXPECT_EQ(v_s_0->d(), 10.0);
+  EXPECT_EQ(v_s_0->ei8(), internal::ByteEnum_First);
+  EXPECT_EQ(v_s_0->ei16(), internal::ShortEnum_First);
+  EXPECT_EQ(v_s_0->ei32(), internal::IntEnum_First);
+  EXPECT_EQ(v_s_0->ei64(), internal::LongEnum_First);
+  EXPECT_EQ(v_s_0->eu8(), internal::UByteEnum_First);
+  EXPECT_EQ(v_s_0->eu16(), internal::UShortEnum_First);
+  EXPECT_EQ(v_s_0->eu32(), internal::UIntEnum_First);
+  EXPECT_EQ(v_s_0->eu64(), internal::ULongEnum_First);
+  EXPECT_EQ(v_s_0->s().b(), true);
 }
 
 TEST(FlatbuffersTableDomainImplTest, InitGeneratesSeeds) {
@@ -293,12 +731,22 @@
 
 TEST(FlatbuffersTableDomainImplTest, CanMutateAnyTableField) {
   absl::flat_hash_map<std::string, bool> mutated_fields{
-      {"b", false},   {"i8", false},   {"i16", false},  {"i32", false},
-      {"i64", false}, {"u8", false},   {"u16", false},  {"u32", false},
-      {"u64", false}, {"f", false},    {"d", false},    {"str", false},
-      {"ei8", false}, {"ei16", false}, {"ei32", false}, {"ei64", false},
-      {"eu8", false}, {"eu16", false}, {"eu32", false}, {"eu64", false},
-      {"t", false},
+      {"b", false},        {"i8", false},     {"i16", false},
+      {"i32", false},      {"i64", false},    {"u8", false},
+      {"u16", false},      {"u32", false},    {"u64", false},
+      {"f", false},        {"d", false},      {"str", false},
+      {"ei8", false},      {"ei16", false},   {"ei32", false},
+      {"ei64", false},     {"eu8", false},    {"eu16", false},
+      {"eu32", false},     {"eu64", false},   {"t", false},
+      {"u_type", false},   {"u", false},      {"s", false},
+      {"v_b", false},      {"v_i8", false},   {"v_i16", false},
+      {"v_i32", false},    {"v_i64", false},  {"v_u8", false},
+      {"v_u16", false},    {"v_u32", false},  {"v_u64", false},
+      {"v_f", false},      {"v_d", false},    {"v_str", false},
+      {"v_ei8", false},    {"v_ei16", false}, {"v_ei32", false},
+      {"v_ei64", false},   {"v_eu8", false},  {"v_eu16", false},
+      {"v_eu32", false},   {"v_eu64", false}, {"v_t", false},
+      {"v_u_type", false}, {"v_u", false},    {"v_s", false},
   };
 
   auto domain = Arbitrary<const DefaultTable*>();
@@ -334,6 +782,36 @@
     mutated_fields["eu32"] |= mut->eu32() != init->eu32();
     mutated_fields["eu64"] |= mut->eu64() != init->eu64();
     mutated_fields["t"] |= !Eq(mut->t(), init->t());
+    mutated_fields["u_type"] |= !Eq(mut->u_type(), init->u_type());
+    mutated_fields["u"] |=
+        !Eq(std::pair(static_cast<uint8_t>(mut->u_type()), mut->u()),
+            std::pair(static_cast<uint8_t>(init->u_type()), init->u()));
+    mutated_fields["s"] |= !Eq(mut->s(), init->s());
+    mutated_fields["v_b"] |= !Eq(mut->v_b(), init->v_b());
+    mutated_fields["v_i8"] |= !Eq(mut->v_i8(), init->v_i8());
+    mutated_fields["v_i16"] |= !Eq(mut->v_i16(), init->v_i16());
+    mutated_fields["v_i32"] |= !Eq(mut->v_i32(), init->v_i32());
+    mutated_fields["v_i64"] |= !Eq(mut->v_i64(), init->v_i64());
+    mutated_fields["v_u8"] |= !Eq(mut->v_u8(), init->v_u8());
+    mutated_fields["v_u16"] |= !Eq(mut->v_u16(), init->v_u16());
+    mutated_fields["v_u32"] |= !Eq(mut->v_u32(), init->v_u32());
+    mutated_fields["v_u64"] |= !Eq(mut->v_u64(), init->v_u64());
+    mutated_fields["v_f"] |= !Eq(mut->v_f(), init->v_f());
+    mutated_fields["v_d"] |= !Eq(mut->v_d(), init->v_d());
+    mutated_fields["v_str"] |= !Eq(mut->v_str(), init->v_str());
+    mutated_fields["v_ei8"] |= !Eq(mut->v_ei8(), init->v_ei8());
+    mutated_fields["v_ei16"] |= !Eq(mut->v_ei16(), init->v_ei16());
+    mutated_fields["v_ei32"] |= !Eq(mut->v_ei32(), init->v_ei32());
+    mutated_fields["v_ei64"] |= !Eq(mut->v_ei64(), init->v_ei64());
+    mutated_fields["v_eu8"] |= !Eq(mut->v_eu8(), init->v_eu8());
+    mutated_fields["v_eu16"] |= !Eq(mut->v_eu16(), init->v_eu16());
+    mutated_fields["v_eu32"] |= !Eq(mut->v_eu32(), init->v_eu32());
+    mutated_fields["v_eu64"] |= !Eq(mut->v_eu64(), init->v_eu64());
+    mutated_fields["v_t"] |= !Eq(mut->v_t(), init->v_t());
+    mutated_fields["v_u_type"] |= !Eq(mut->v_u_type(), init->v_u_type());
+    mutated_fields["v_u"] |= !Eq(std::make_pair(mut->v_u_type(), mut->v_u()),
+                                 std::make_pair(init->v_u_type(), init->v_u()));
+    mutated_fields["v_s"] |= !Eq(mut->v_s(), init->v_s());
 
     if (std::all_of(mutated_fields.begin(), mutated_fields.end(),
                     [](const auto& p) { return p.second; })) {
@@ -347,6 +825,32 @@
 TEST(FlatbuffersTableDomainImplTest, OptionalTableEventuallyBecomeEmpty) {
   flatbuffers::FlatBufferBuilder fbb;
   auto bool_table_offset = internal::CreateBoolTable(fbb, true);
+  DefaultStruct s;
+  std::vector<uint8_t> v_b{true, false};
+  std::vector<int8_t> v_i8{};
+  std::vector<int16_t> v_i16{};
+  std::vector<int32_t> v_i32{};
+  std::vector<int64_t> v_i64{};
+  std::vector<uint8_t> v_u8{};
+  std::vector<uint16_t> v_u16{};
+  std::vector<uint32_t> v_u32{};
+  std::vector<uint64_t> v_u64{};
+  std::vector<float> v_f{};
+  std::vector<double> v_d{};
+  std::vector<flatbuffers::Offset<flatbuffers::String>> v_str{
+      fbb.CreateString(""), fbb.CreateString(""), fbb.CreateString("")};
+  std::vector<std::underlying_type_t<ByteEnum>> v_ei8{};
+  std::vector<std::underlying_type_t<ShortEnum>> v_ei16{};
+  std::vector<std::underlying_type_t<IntEnum>> v_ei32{};
+  std::vector<std::underlying_type_t<LongEnum>> v_ei64{};
+  std::vector<std::underlying_type_t<UByteEnum>> v_eu8{};
+  std::vector<std::underlying_type_t<UShortEnum>> v_eu16{};
+  std::vector<std::underlying_type_t<UIntEnum>> v_eu32{};
+  std::vector<std::underlying_type_t<ULongEnum>> v_eu64{};
+  std::vector<flatbuffers::Offset<BoolTable>> v_t{};
+  std::vector<std::underlying_type_t<internal::Union>> v_u_type{};
+  std::vector<flatbuffers::Offset<>> v_u{};
+  std::vector<DefaultStruct> v_s{};
   auto table_offset =
       internal::CreateOptionalTableDirect(fbb,
                                           true,                         // b
@@ -369,7 +873,34 @@
                                           internal::UShortEnum_Second,  // eu16
                                           internal::UIntEnum_Second,    // eu32
                                           internal::ULongEnum_Second,   // eu64
-                                          bool_table_offset             // t
+                                          bool_table_offset,            // t
+                                          internal::Union_BoolTable,  // u_type
+                                          bool_table_offset.Union(),  // u
+                                          &s,                         // s
+                                          &v_b,                       // v_b
+                                          &v_i8,                      // v_i8
+                                          &v_i16,                     // v_i16
+                                          &v_i32,                     // v_i32
+                                          &v_i64,                     // v_i64
+                                          &v_u8,                      // v_u8
+                                          &v_u16,                     // v_u16
+                                          &v_u32,                     // v_u32
+                                          &v_u64,                     // v_u64
+                                          &v_f,                       // v_f
+                                          &v_d,                       // v_d
+                                          &v_str,                     // v_str
+                                          &v_ei8,                     // v_ei8
+                                          &v_ei16,                    // v_ei16
+                                          &v_ei32,                    // v_ei32
+                                          &v_ei64,                    // v_ei64
+                                          &v_eu8,                     // v_eu8
+                                          &v_eu16,                    // v_eu16
+                                          &v_eu32,                    // v_eu32
+                                          &v_eu64,                    // v_eu64
+                                          &v_t,                       // v_t
+                                          &v_u_type,  // v_u_type
+                                          &v_u,       // v_u
+                                          &v_s        // v_s
       );
   fbb.Finish(table_offset);
   auto table = flatbuffers::GetRoot<OptionalTable>(fbb.GetBufferPointer());
@@ -379,12 +910,22 @@
   absl::BitGen bitgen;
 
   absl::flat_hash_map<std::string, bool> null_fields{
-      {"b", false},   {"i8", false},   {"i16", false},  {"i32", false},
-      {"i64", false}, {"u8", false},   {"u16", false},  {"u32", false},
-      {"u64", false}, {"f", false},    {"d", false},    {"str", false},
-      {"ei8", false}, {"ei16", false}, {"ei32", false}, {"ei64", false},
-      {"eu8", false}, {"eu16", false}, {"eu32", false}, {"eu64", false},
-      {"t", false},
+      {"b", false},        {"i8", false},     {"i16", false},
+      {"i32", false},      {"i64", false},    {"u8", false},
+      {"u16", false},      {"u32", false},    {"u64", false},
+      {"f", false},        {"d", false},      {"str", false},
+      {"ei8", false},      {"ei16", false},   {"ei32", false},
+      {"ei64", false},     {"eu8", false},    {"eu16", false},
+      {"eu32", false},     {"eu64", false},   {"t", false},
+      {"u_type", false},   {"u", false},      {"s", false},
+      {"v_b", false},      {"v_i8", false},   {"v_i16", false},
+      {"v_i32", false},    {"v_i64", false},  {"v_u8", false},
+      {"v_u16", false},    {"v_u32", false},  {"v_u64", false},
+      {"v_f", false},      {"v_d", false},    {"v_str", false},
+      {"v_ei8", false},    {"v_ei16", false}, {"v_ei32", false},
+      {"v_ei64", false},   {"v_eu8", false},  {"v_eu16", false},
+      {"v_eu32", false},   {"v_eu64", false}, {"v_t", false},
+      {"v_u_type", false}, {"v_u", false},    {"v_s", false},
   };
 
   // Optional fields are mutated to null with probability 1/100.
@@ -415,6 +956,33 @@
     null_fields["eu32"] |= !v->eu32().has_value();
     null_fields["eu64"] |= !v->eu64().has_value();
     null_fields["t"] |= v->t() == nullptr;
+    null_fields["u_type"] |= v->u_type() == internal::Union_NONE;
+    null_fields["u"] |= v->u() == nullptr;
+    null_fields["s"] |= v->s() == nullptr;
+    null_fields["v_b"] |= v->v_b() == nullptr;
+    null_fields["v_i8"] |= v->v_i8() == nullptr;
+    null_fields["v_i16"] |= v->v_i16() == nullptr;
+    null_fields["v_i32"] |= v->v_i32() == nullptr;
+    null_fields["v_i64"] |= v->v_i64() == nullptr;
+    null_fields["v_u8"] |= v->v_u8() == nullptr;
+    null_fields["v_u16"] |= v->v_u16() == nullptr;
+    null_fields["v_u32"] |= v->v_u32() == nullptr;
+    null_fields["v_u64"] |= v->v_u64() == nullptr;
+    null_fields["v_f"] |= v->v_f() == nullptr;
+    null_fields["v_d"] |= v->v_d() == nullptr;
+    null_fields["v_str"] |= v->v_str() == nullptr;
+    null_fields["v_ei8"] |= v->v_ei8() == nullptr;
+    null_fields["v_ei16"] |= v->v_ei16() == nullptr;
+    null_fields["v_ei32"] |= v->v_ei32() == nullptr;
+    null_fields["v_ei64"] |= v->v_ei64() == nullptr;
+    null_fields["v_eu8"] |= v->v_eu8() == nullptr;
+    null_fields["v_eu16"] |= v->v_eu16() == nullptr;
+    null_fields["v_eu32"] |= v->v_eu32() == nullptr;
+    null_fields["v_eu64"] |= v->v_eu64() == nullptr;
+    null_fields["v_t"] |= v->v_t() == nullptr;
+    null_fields["v_u_type"] |= v->v_u_type() == nullptr;
+    null_fields["v_u"] |= v->v_u() == nullptr;
+    null_fields["v_s"] |= v->v_s() == nullptr;
 
     if (std::all_of(null_fields.begin(), null_fields.end(),
                     [](const auto& p) { return p.second; })) {
@@ -453,83 +1021,164 @@
   printer.PrintCorpusValue(*corpus, &out,
                            domain_implementor::PrintMode::kHumanReadable);
 
-  EXPECT_THAT(out, AllOf(HasSubstr("b: (true)"),               // b
-                         HasSubstr("i8: (1)"),                 // i8
-                         HasSubstr("i16: (2)"),                // i16
-                         HasSubstr("i32: (3)"),                // i32
-                         HasSubstr("i64: (4)"),                // i64
-                         HasSubstr("u8: (5)"),                 // u8
-                         HasSubstr("u16: (6)"),                // u16
-                         HasSubstr("u32: (7)"),                // u32
-                         HasSubstr("u64: (8)"),                // u64
-                         HasSubstr("f: (9.f)"),                // f
-                         HasSubstr("d: (10.)"),                // d
-                         HasSubstr("str: (\"foo bar baz\")"),  // str
-                         HasSubstr("ei8: (Second)"),           // ei8
-                         HasSubstr("ei16: (Second)"),          // ei16
-                         HasSubstr("ei32: (Second)"),          // ei32
-                         HasSubstr("ei64: (Second)"),          // ei64
-                         HasSubstr("eu8: (Second)"),           // eu8
-                         HasSubstr("eu16: (Second)"),          // eu16
-                         HasSubstr("eu32: (Second)"),          // eu32
-                         HasSubstr("eu64: (Second)"),          // eu64
-                         HasSubstr("t: ({b: (true)})")         // t
-                         ));
+  EXPECT_THAT(
+      out,
+      AllOf(HasSubstr("b: (true)"),                      // b
+            HasSubstr("i8: (1)"),                        // i8
+            HasSubstr("i16: (2)"),                       // i16
+            HasSubstr("i32: (3)"),                       // i32
+            HasSubstr("i64: (4)"),                       // i64
+            HasSubstr("u8: (5)"),                        // u8
+            HasSubstr("u16: (6)"),                       // u16
+            HasSubstr("u32: (7)"),                       // u32
+            HasSubstr("u64: (8)"),                       // u64
+            HasSubstr("f: (9.f)"),                       // f
+            HasSubstr("d: (10.)"),                       // d
+            HasSubstr("str: (\"foo bar baz\")"),         // str
+            HasSubstr("ei8: (Second)"),                  // ei8
+            HasSubstr("ei16: (Second)"),                 // ei16
+            HasSubstr("ei32: (Second)"),                 // ei32
+            HasSubstr("ei64: (Second)"),                 // ei64
+            HasSubstr("eu8: (Second)"),                  // eu8
+            HasSubstr("eu16: (Second)"),                 // eu16
+            HasSubstr("eu32: (Second)"),                 // eu32
+            HasSubstr("eu64: (Second)"),                 // eu64
+            HasSubstr("t: ({b: (true)})"),               // t
+            HasSubstr("u: (<BoolTable>({b: (true)}))"),  // u
+            HasSubstr(
+                "s: ({b: true, i8: 1, i16: 2, i32: 3, i64: 4, u8: 5, u16: 6, "
+                "u32: 7, u64: 8, f: 9.f, d: 10., ei8: First, ei16: First, "
+                "ei32: First, ei64: First, eu8: First, eu16: First, eu32: "
+                "First, eu64: First, s: {b: true}})"),          // s
+            HasSubstr("v_b: ({true, false})"),                  // v_b
+            HasSubstr("v_i8: ({1, 2, 3})"),                     // v_i8
+            HasSubstr("v_i16: ({1, 2, 3})"),                    // v_i16
+            HasSubstr("v_i32: ({1, 2, 3})"),                    // v_i32
+            HasSubstr("v_i64: ({1, 2, 3})"),                    // v_i64
+            HasSubstr("v_u8: ({1, 2, 3})"),                     // v_u8
+            HasSubstr("v_u16: ({1, 2, 3})"),                    // v_u16
+            HasSubstr("v_u32: ({1, 2, 3})"),                    // v_u32
+            HasSubstr("v_u64: ({1, 2, 3})"),                    // v_u64
+            HasSubstr("v_f: ({1.f, 2.f, 3.f})"),                // v_f
+            HasSubstr("v_d: ({1., 2., 3.})"),                   // v_d
+            HasSubstr("v_str: ({\"foo\", \"bar\", \"baz\"})"),  // v_str
+            HasSubstr("v_ei8: ({First, Second})"),              // v_ei8
+            HasSubstr("v_ei16: ({First, Second})"),             // v_ei16
+            HasSubstr("v_ei32: ({First, Second})"),             // v_ei32
+            HasSubstr("v_ei64: ({First, Second})"),             // v_ei64
+            HasSubstr("v_eu8: ({First, Second})"),              // v_eu8
+            HasSubstr("v_eu16: ({First, Second})"),             // v_eu16
+            HasSubstr("v_eu32: ({First, Second})"),             // v_eu32
+            HasSubstr("v_eu64: ({First, Second})"),             // v_eu64
+            HasSubstr("v_t: ({{b: (true)}})"),                  // v_t
+            HasSubstr("v_u: ({<BoolTable>({b: (true)}), "
+                      "<StringTable>({str: (\"foo bar baz\")})})"),  // v_u
+            HasSubstr(
+                "v_s: ({{b: true, i8: 1, i16: 2, i32: 3, i64: 4, u8: 5, u16: "
+                "6, u32: 7, u64: 8, f: 9.f, d: 10., ei8: First, ei16: First, "
+                "ei32: First, ei64: First, eu8: First, eu16: First, eu32: "
+                "First, eu64: First, s: {b: true}}})")  // v_s
+            ));
 }
 
-TEST(FlatbuffersTableDomainImplTest, UnsupportedTypesRemainNull) {
-  absl::flat_hash_map<std::string, bool> null_fields{
-      {"u", true},      {"s", true},      {"v_b", true},   {"v_i8", true},
-      {"v_i16", true},  {"v_i32", true},  {"v_i64", true}, {"v_u8", true},
-      {"v_u16", true},  {"v_u32", true},  {"v_u64", true}, {"v_f", true},
-      {"v_d", true},    {"v_str", true},  {"v_ei8", true}, {"v_ei16", true},
-      {"v_ei32", true}, {"v_ei64", true}, {"v_eu8", true}, {"v_eu16", true},
-      {"v_eu32", true}, {"v_eu64", true}, {"v_t", true},   {"v_u", true},
-      {"v_s", true}};
+TEST(FlatbuffersTableDomainImplTest, MutateSelectedField) {
+  flatbuffers::FlatBufferBuilder fbb;
+  auto table = CreateDefaultTable(fbb);
+  auto domain = Arbitrary<const DefaultTable*>();
+  absl::BitGen prng;
 
-  auto domain = Arbitrary<const UnsupportedTypesTable*>();
-
-  absl::BitGen bitgen;
-  for (size_t i = 0;
-       i < IterationsToHitAll(null_fields.size(), 1.0 / null_fields.size());
-       ++i) {
-    Value val(domain, bitgen);
-    val.Mutate(domain, bitgen, {}, false);
-    const auto& mut = val.user_value;
-
-    null_fields["u"] &= mut->u() == nullptr;
-    null_fields["s"] &= mut->s() == nullptr;
-    null_fields["v_b"] &= mut->v_b() == nullptr;
-    null_fields["v_i8"] &= mut->v_i8() == nullptr;
-    null_fields["v_i16"] &= mut->v_i16() == nullptr;
-    null_fields["v_i32"] &= mut->v_i32() == nullptr;
-    null_fields["v_i64"] &= mut->v_i64() == nullptr;
-    null_fields["v_u8"] &= mut->v_u8() == nullptr;
-    null_fields["v_u16"] &= mut->v_u16() == nullptr;
-    null_fields["v_u32"] &= mut->v_u32() == nullptr;
-    null_fields["v_u64"] &= mut->v_u64() == nullptr;
-    null_fields["v_f"] &= mut->v_f() == nullptr;
-    null_fields["v_d"] &= mut->v_d() == nullptr;
-    null_fields["v_str"] &= mut->v_str() == nullptr;
-    null_fields["v_ei8"] &= mut->v_ei8() == nullptr;
-    null_fields["v_ei16"] &= mut->v_ei16() == nullptr;
-    null_fields["v_ei32"] &= mut->v_ei32() == nullptr;
-    null_fields["v_ei64"] &= mut->v_ei64() == nullptr;
-    null_fields["v_eu8"] &= mut->v_eu8() == nullptr;
-    null_fields["v_eu16"] &= mut->v_eu16() == nullptr;
-    null_fields["v_eu32"] &= mut->v_eu32() == nullptr;
-    null_fields["v_eu64"] &= mut->v_eu64() == nullptr;
-    null_fields["v_t"] &= mut->v_t() == nullptr;
-    null_fields["v_u"] &= mut->v_u() == nullptr;
-    null_fields["v_s"] &= mut->v_s() == nullptr;
-
-    if (std::any_of(null_fields.begin(), null_fields.end(),
-                    [](const auto& p) { return !p.second; })) {
-      break;
+  {
+    // Mutate scalar field (b).
+    auto corpus = domain.FromValue(table);
+    domain.MutateSelectedField(*corpus, prng, {}, false, 1);
+    EXPECT_NE(domain.GetValue(*corpus)->b(), table->b());
+  }
+  {
+    // Mutate nested table field (t.b).
+    auto corpus = domain.FromValue(table);
+    domain.MutateSelectedField(*corpus, prng, {}, false, 22);
+    EXPECT_NE(domain.GetValue(*corpus)->t()->b(), table->t()->b());
+  }
+  {
+    // Mutate union type field (u_type).
+    auto corpus = domain.FromValue(table);
+    domain.MutateSelectedField(*corpus, prng, {}, false, 24);
+    EXPECT_NE(domain.GetValue(*corpus)->u_type(), table->u_type());
+  }
+  {
+    // Mutate union field (u.*) content.
+    auto corpus = domain.FromValue(table);
+    domain.MutateSelectedField(*corpus, prng, {}, false, 25);
+    switch (domain.GetValue(*corpus)->u_type()) {
+      case internal::Union_BoolTable:
+        EXPECT_NE(domain.GetValue(*corpus)->u_as_BoolTable()->b(),
+                  table->u_as_BoolTable()->b());
+        break;
+      case internal::Union_StringTable:
+        EXPECT_NE(
+            domain.GetValue(*corpus)->u_as_StringTable()->str()->string_view(),
+            table->u_as_StringTable()->str()->string_view());
+        break;
+      default:
+        FAIL() << "Unexpected union type: "
+               << domain.GetValue(*corpus)->u_type();
     }
   }
-
-  EXPECT_THAT(null_fields, Each(Pair(_, true)));
+  {
+    // Mutate vector of tables field (v_t).
+    auto corpus = domain.FromValue(table);
+    domain.MutateSelectedField(*corpus, prng, {}, false, 68);
+    auto user_value = domain.GetValue(*corpus);
+    EXPECT_FALSE(Eq(user_value->v_t(), table->v_t()));
+  }
+  {
+    // Mutate vector of tables field (v_t[0].b).
+    auto corpus = domain.FromValue(table);
+    domain.MutateSelectedField(*corpus, prng, {}, false, 69);
+    EXPECT_NE(domain.GetValue(*corpus)->v_t()->Get(0)->b(),
+              table->v_t()->Get(0)->b());
+  }
+  {
+    // Mutate vector of unions field type (v_u_type[0]).
+    auto corpus = domain.FromValue(table);
+    domain.MutateSelectedField(*corpus, prng, {}, false, 71);
+    EXPECT_NE(domain.GetValue(*corpus)->v_u_type()->Get(0),
+              table->v_u_type()->Get(0));
+  }
+  {
+    // Mutate vector of unions field (v_u[0].*).
+    auto corpus = domain.FromValue(table);
+    domain.MutateSelectedField(*corpus, prng, {}, false, 72);
+    EXPECT_EQ(domain.GetValue(*corpus)->v_u_type()->size(),
+              table->v_u_type()->size());
+    EXPECT_EQ(domain.GetValue(*corpus)->v_u_type()->Get(0),
+              table->v_u_type()->Get(0));
+    if (!domain.GetValue(*corpus)->v_u_type()->empty()) {
+      switch (domain.GetValue(*corpus)->v_u_type()->Get(0)) {
+        case internal::Union_BoolTable:
+          EXPECT_NE(
+              static_cast<const internal::BoolTable*>(
+                  domain.GetValue(*corpus)->v_u()->Get(0))
+                  ->b(),
+              static_cast<const internal::BoolTable*>(table->v_u()->Get(0))
+                  ->b());
+          break;
+        case internal::Union_StringTable:
+          EXPECT_NE(
+              static_cast<const internal::StringTable*>(
+                  domain.GetValue(*corpus)->v_u()->Get(0))
+                  ->str()
+                  ->string_view(),
+              static_cast<const internal::StringTable*>(table->v_u()->Get(0))
+                  ->str()
+                  ->string_view());
+          break;
+        default:
+          FAIL() << "Unexpected union type: "
+                 << domain.GetValue(*corpus)->v_u_type()->Get(0);
+      }
+    }
+  }
 }
 
 TEST(FlatbuffersTableDomainImplTest, MutateAlwaysChangesValues) {
@@ -540,23 +1189,28 @@
       schema->objects()->LookupByKey(DefaultTable::GetFullyQualifiedName());
 
   absl::BitGen bitgen;
-  size_t iterations = IterationsToHitAll(object->fields()->size(),
-                                         1.0 / object->fields()->size());
+  const uint32_t field_count = object->fields()->size();
+  int iterations = IterationsToHitAll(field_count, 1.0 / field_count);
   typename decltype(domain)::corpus_type corpus = domain.Init(bitgen);
-  for (size_t i = 0; i < iterations; ++i) {
+  for (int i = 0; i < iterations; ++i) {
     auto mutated_corpus = corpus;
     domain.Mutate(mutated_corpus, bitgen, {}, false);
-    EXPECT_FALSE(Eq(domain.GetValue(mutated_corpus), domain.GetValue(corpus)));
+    if (Eq(domain.GetValue(mutated_corpus), domain.GetValue(corpus))) {
+      auto printer = domain.GetPrinter();
+      std::string corpus_str;
+      printer.PrintCorpusValue(corpus, &corpus_str,
+                               domain_implementor::PrintMode::kHumanReadable);
+      std::string mutated_corpus_str;
+      printer.PrintCorpusValue(mutated_corpus, &mutated_corpus_str,
+                               domain_implementor::PrintMode::kHumanReadable);
+      FAIL() << "Mutated corpus is equal to the original corpus."
+             << "\nOriginal: " << corpus_str
+             << "\nMutated: " << mutated_corpus_str;
+    }
     corpus = mutated_corpus;
   }
 }
 
-TEST(FlatbuffersTableDomainImplTest, UnsupportedFieldsCountIsZero) {
-  auto domain = Arbitrary<const UnsupportedTypesTable*>();
-  auto corpus = domain.Init(absl::BitGen());
-  EXPECT_EQ(domain.CountNumberOfFields(corpus), 0);
-}
-
 TEST(FlatbuffersTableDomainImplTest, CountNumberOfFieldsWithNull) {
   flatbuffers::FlatBufferBuilder fbb;
   auto table_offset = internal::CreateOptionalTableDirect(fbb);
@@ -566,7 +1220,72 @@
   auto domain = Arbitrary<const OptionalTable*>();
   auto corpus = domain.FromValue(table);
   ASSERT_TRUE(corpus.has_value());
-  EXPECT_EQ(domain.CountNumberOfFields(corpus.value()), 21);
+  EXPECT_EQ(domain.CountNumberOfFields(corpus.value()), 46);
+}
+
+TEST(FlatbuffersUnionDomainImpl, ParseCorpusRejectsInvalidValues) {
+  auto domain = Arbitrary<const UnionTable*>();
+  {
+    flatbuffers::FlatBufferBuilder fbb;
+    internal::CreateUnionTable(fbb, internal::Union_BoolTable, 0);
+    fbb.Finish(internal::CreateUnionTable(fbb, internal::Union_BoolTable, 0));
+    auto table = flatbuffers::GetRoot<UnionTable>(fbb.GetBufferPointer());
+    flatbuffers::Verifier verifier(fbb.GetBufferPointer(), fbb.GetSize());
+    ASSERT_TRUE(verifier.VerifyBuffer<UnionTable>());
+
+    auto corpus = domain.FromValue(table);
+    ASSERT_TRUE(corpus.has_value());
+    EXPECT_FALSE(domain.ValidateCorpusValue(corpus.value()).ok());
+  }
+  {
+    internal::IRObject ir_object;
+    auto& subs = ir_object.MutableSubs();
+    subs.reserve(2);
+
+    auto& u_obj = subs.emplace_back();
+    auto& u_subs = u_obj.MutableSubs();
+    u_subs.reserve(2);
+    u_subs.emplace_back(1);                     // id
+    auto& u_opt_value = u_subs.emplace_back();  // value
+    auto& u_opt_value_subs = u_opt_value.MutableSubs();
+    u_opt_value_subs.reserve(2);
+    u_opt_value_subs.emplace_back(1);  // has value
+    auto& u_inner_value = u_opt_value_subs.emplace_back();
+
+    u_inner_value.MutableSubs().reserve(2);
+    u_inner_value.MutableSubs().emplace_back(-1);  // type (invalid)
+    u_inner_value.MutableSubs().emplace_back();    // value
+
+    auto corpus = domain.ParseCorpus(ir_object);
+    ASSERT_FALSE(corpus.has_value());
+  }
+  {
+    internal::IRObject ir_object;
+    auto& subs = ir_object.MutableSubs();
+    subs.reserve(2);
+
+    auto& u_obj = subs.emplace_back();
+    auto& u_subs = u_obj.MutableSubs();
+    u_subs.reserve(2);
+    u_subs.emplace_back(1);                     // id
+    auto& u_opt_value = u_subs.emplace_back();  // value
+    auto& u_opt_value_subs = u_opt_value.MutableSubs();
+    u_opt_value_subs.reserve(2);
+    u_opt_value_subs.emplace_back(1);  // has value
+    auto& u_inner_value = u_opt_value_subs.emplace_back();
+
+    u_inner_value.MutableSubs().reserve(2);
+    u_inner_value.MutableSubs().emplace_back(
+        internal::Union_BoolTable);                                 // type
+    auto& bool_table = u_inner_value.MutableSubs().emplace_back();  // value
+    auto& bool_table_subs = bool_table.MutableSubs();
+    bool_table_subs.reserve(2);
+    bool_table_subs.emplace_back(200);  // id (invalid)
+    u_subs.emplace_back();              // value
+
+    auto corpus = domain.ParseCorpus(ir_object);
+    ASSERT_FALSE(corpus.has_value());
+  }
 }
 
 TEST(FlatbuffersTableDomainImplTest, RecursiveTable) {
@@ -597,7 +1316,10 @@
 TEST(FlatbuffersTableDomainImplTest, DefaultTable64ValueRoundTrip) {
   flatbuffers::FlatBufferBuilder64 fbb;
   auto str_offset = fbb.CreateString<flatbuffers::Offset64>("foo bar baz");
-  auto table_offset = internal::CreateDefaultTable64(fbb, str_offset);
+  std::vector<uint8_t> v_u8 = {1, 2, 3};
+  auto v_u8_offset = fbb.CreateVector64(v_u8);
+  auto table_offset =
+      internal::CreateDefaultTable64(fbb, str_offset, v_u8_offset);
   fbb.Finish(table_offset);
   auto table = flatbuffers::GetRoot<DefaultTable64>(fbb.GetBufferPointer());
 
@@ -616,6 +1338,11 @@
   ASSERT_THAT(new_table, NotNull());
   ASSERT_THAT(new_table->str(), NotNull());
   EXPECT_EQ(new_table->str()->str(), "foo bar baz");
+  ASSERT_THAT(new_table->v_u8(), NotNull());
+  ASSERT_EQ(new_table->v_u8()->size(), 3);
+  EXPECT_EQ(new_table->v_u8()->Get(0), 1);
+  EXPECT_EQ(new_table->v_u8()->Get(1), 2);
+  EXPECT_EQ(new_table->v_u8()->Get(2), 3);
 }
 
 }  // namespace
diff --git a/fuzztest/internal/domains/BUILD b/fuzztest/internal/domains/BUILD
index 545fef3..30790fc 100644
--- a/fuzztest/internal/domains/BUILD
+++ b/fuzztest/internal/domains/BUILD
@@ -187,9 +187,9 @@
     hdrs = ["flatbuffers_domain_impl.h"],
     deps = [
         ":core_domains_impl",
-        "@abseil-cpp//absl/algorithm:container",
         "@abseil-cpp//absl/base:core_headers",
         "@abseil-cpp//absl/base:nullability",
+        "@abseil-cpp//absl/container:btree",
         "@abseil-cpp//absl/container:flat_hash_map",
         "@abseil-cpp//absl/container:flat_hash_set",
         "@abseil-cpp//absl/random:bit_gen_ref",
@@ -204,6 +204,7 @@
         "@com_google_fuzztest//fuzztest/internal:meta",
         "@com_google_fuzztest//fuzztest/internal:serialization",
         "@com_google_fuzztest//fuzztest/internal:status",
+        "@com_google_fuzztest//fuzztest/internal:type_support",
         "@flatbuffers//:runtime_cc",
     ],
 )
diff --git a/fuzztest/internal/domains/CMakeLists.txt b/fuzztest/internal/domains/CMakeLists.txt
index e9702ff..d8c3de2 100644
--- a/fuzztest/internal/domains/CMakeLists.txt
+++ b/fuzztest/internal/domains/CMakeLists.txt
@@ -173,6 +173,7 @@
     fuzztest::core_domains_impl
     absl::core_headers
     absl::no_destructor
+    absl::btree
     absl::flat_hash_map
     absl::flat_hash_set
     absl::random_random
diff --git a/fuzztest/internal/domains/flatbuffers_domain_impl.cc b/fuzztest/internal/domains/flatbuffers_domain_impl.cc
index 9031344..866c4bd 100644
--- a/fuzztest/internal/domains/flatbuffers_domain_impl.cc
+++ b/fuzztest/internal/domains/flatbuffers_domain_impl.cc
@@ -11,170 +11,470 @@
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
+
 #include "./fuzztest/internal/domains/flatbuffers_domain_impl.h"
 
 #include <cstdint>
 #include <optional>
 #include <utility>
+#include <vector>
 
 #include "absl/base/nullability.h"
 #include "absl/container/flat_hash_map.h"
 #include "absl/random/bit_gen_ref.h"
 #include "absl/random/distributions.h"
 #include "absl/status/status.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/str_format.h"
 #include "absl/synchronization/mutex.h"
 #include "flatbuffers/base.h"
 #include "flatbuffers/flatbuffer_builder.h"
-#include "flatbuffers/reflection.h"
 #include "flatbuffers/reflection_generated.h"
+#include "flatbuffers/struct.h"
+#include "flatbuffers/table.h"
+#include "./common/logging.h"
 #include "./fuzztest/domain_core.h"
 #include "./fuzztest/internal/any.h"
 #include "./fuzztest/internal/domains/domain_base.h"
-#include "./fuzztest/internal/domains/domain_type_erasure.h"
+#include "./fuzztest/internal/meta.h"
 #include "./fuzztest/internal/serialization.h"
 
 namespace fuzztest::internal {
 
-FlatbuffersTableUntypedDomainImpl::FlatbuffersTableUntypedDomainImpl(
-    const reflection::Schema* absl_nonnull schema,
-    const reflection::Object* absl_nonnull table_object)
-    : schema_(schema), table_object_(table_object) {}
+// Gets a domain for a specific struct type.
+template <>
+auto FlatbuffersUnionDomainImpl::GetDefaultDomainForType<FlatbuffersStructTag>(
+    const reflection::EnumVal& enum_value) const {
+  const reflection::Object* object =
+      schema_->objects()->Get(enum_value.union_type()->index());
+  return Domain<const flatbuffers::Struct*>(
+      FlatbuffersStructUntypedDomainImpl{schema_, object});
+}
 
-FlatbuffersTableUntypedDomainImpl::FlatbuffersTableUntypedDomainImpl(
-    const FlatbuffersTableUntypedDomainImpl& other)
-    : DomainBase(other),
-      schema_(other.schema_),
-      table_object_(other.table_object_) {
+FlatbuffersUnionDomainImpl::FlatbuffersUnionDomainImpl(
+    const reflection::Schema* schema, const reflection::Enum* union_def)
+    : schema_(schema), union_def_(union_def), type_domain_(union_def) {
+  type_domain_.WithExcludedValues({0 /* NONE */});
+}
+
+FlatbuffersUnionDomainImpl::FlatbuffersUnionDomainImpl(
+    const FlatbuffersUnionDomainImpl& other)
+    : schema_(other.schema_),
+      union_def_(other.union_def_),
+      type_domain_(other.type_domain_) {
+  absl::MutexLock l(mutex_);
   absl::MutexLock l_other(other.mutex_);
-  absl::MutexLock l_this(mutex_);
   domains_ = other.domains_;
 }
 
-FlatbuffersTableUntypedDomainImpl& FlatbuffersTableUntypedDomainImpl::operator=(
-    const FlatbuffersTableUntypedDomainImpl& other) {
-  DomainBase::operator=(other);
-  schema_ = other.schema_;
-  table_object_ = other.table_object_;
+FlatbuffersUnionDomainImpl::FlatbuffersUnionDomainImpl(
+    FlatbuffersUnionDomainImpl&& other)
+    : schema_(other.schema_),
+      union_def_(other.union_def_),
+      type_domain_(std::move(other.type_domain_)) {
+  absl::MutexLock l(mutex_);
   absl::MutexLock l_other(other.mutex_);
-  absl::MutexLock l_this(mutex_);
-  domains_ = other.domains_;
-  return *this;
-}
-
-FlatbuffersTableUntypedDomainImpl::FlatbuffersTableUntypedDomainImpl(
-    FlatbuffersTableUntypedDomainImpl&& other)
-    : schema_(other.schema_), table_object_(other.table_object_) {
-  absl::MutexLock l_other(other.mutex_);
-  absl::MutexLock l_this(mutex_);
   domains_ = std::move(other.domains_);
-  DomainBase::operator=(std::move(other));
 }
 
-FlatbuffersTableUntypedDomainImpl& FlatbuffersTableUntypedDomainImpl::operator=(
-    FlatbuffersTableUntypedDomainImpl&& other) {
-  schema_ = other.schema_;
-  table_object_ = other.table_object_;
-  absl::MutexLock l_other(other.mutex_);
-  absl::MutexLock l_this(mutex_);
-  domains_ = std::move(other.domains_);
-  DomainBase::operator=(std::move(other));
-  return *this;
+// Get a domain for a specific table type.
+template <>
+auto FlatbuffersUnionDomainImpl::GetDefaultDomainForType<FlatbuffersTableTag>(
+    const reflection::EnumVal& enum_value) const {
+  const reflection::Object* object =
+      schema_->objects()->Get(enum_value.union_type()->index());
+  return Domain<const flatbuffers::Table*>(
+      FlatbuffersTableUntypedDomainImpl{schema_, object});
 }
 
-FlatbuffersTableUntypedDomainImpl::corpus_type
-FlatbuffersTableUntypedDomainImpl::Init(absl::BitGenRef prng) {
+FlatbuffersUnionDomainImpl::corpus_type FlatbuffersUnionDomainImpl::Init(
+    absl::BitGenRef prng) {
   if (auto seed = this->MaybeGetRandomSeed(prng)) {
     return *seed;
   }
+
+  // Unions are encoded as the combination of two fields: an enum representing
+  // the union choice and the offset to the actual element.
+  //
+  // The following code follows that logic.
   corpus_type val;
-  for (const auto* field : *table_object_->fields()) {
-    VisitFlatbufferField(schema_, field, InitializeVisitor{*this, prng, val});
+
+  val.type = type_domain_.Init(prng);
+  auto type_value = type_domain_.GetValue(val.type);
+
+  auto type_enumval = union_def_->values()->LookupByKey(type_value);
+  if (type_enumval == nullptr) {
+    return val;
+  }
+
+  const reflection::Object* object =
+      schema_->objects()->Get(type_enumval->union_type()->index());
+  if (object->is_struct()) {
+    auto inner_val =
+        GetCachedDomain<FlatbuffersStructTag>(*type_enumval).Init(prng);
+    val.value = std::move(inner_val);
+  } else {
+    auto inner_val =
+        GetCachedDomain<FlatbuffersTableTag>(*type_enumval).Init(prng);
+    val.value = std::move(inner_val);
   }
   return val;
 }
 
 // Mutates the corpus value.
-void FlatbuffersTableUntypedDomainImpl::Mutate(
-    corpus_type& val, absl::BitGenRef prng,
+void FlatbuffersUnionDomainImpl::Mutate(
+    corpus_type& corpus_value, absl::BitGenRef prng,
     const domain_implementor::MutationMetadata& metadata, bool only_shrink) {
-  uint64_t field_count = 0;
-  for (const auto* field : *table_object_->fields()) {
-    VisitFlatbufferField(schema_, field,
-                         CountNumberOfMutableFieldsVisitor{*this, field_count,
-                                                           val, only_shrink});
-  }
-  if (field_count == 0) return;
-  auto selected_field_index = absl::Uniform(prng, 1ul, field_count + 1);
+  auto type_value = type_domain_.GetValue(corpus_value.type);
 
-  MutateSelectedField(val, prng, metadata, only_shrink, selected_field_index);
+  // Mutate the type with probability 1%.
+  if (absl::Bernoulli(prng, 0.01)) {
+    // Mutate the type.
+    type_domain_.Mutate(corpus_value.type, prng, metadata, only_shrink);
+    type_value = type_domain_.GetValue(corpus_value.type);
+
+    // If the union is set after type mutation, init the value corpus value.
+    auto type_enumval = union_def_->values()->LookupByKey(type_value);
+    if (type_enumval == nullptr) return;
+
+    const reflection::Object* object =
+        schema_->objects()->Get(type_enumval->union_type()->index());
+    if (object->is_struct()) {
+      corpus_value.value =
+          GetCachedDomain<FlatbuffersStructTag>(*type_enumval).Init(prng);
+    } else {
+      corpus_value.value =
+          GetCachedDomain<FlatbuffersTableTag>(*type_enumval).Init(prng);
+    }
+    return;
+  }
+
+  // Mutate the value if the union is set.
+  auto type_enumval = union_def_->values()->LookupByKey(type_value);
+  if (type_enumval == nullptr) return;
+
+  const reflection::Object* object =
+      schema_->objects()->Get(type_enumval->union_type()->index());
+  if (object->is_struct()) {
+    auto domain = GetCachedDomain<FlatbuffersStructTag>(*type_enumval);
+    domain.Mutate(corpus_value.value, prng, metadata, only_shrink);
+  } else {
+    GetCachedDomain<FlatbuffersTableTag>(*type_enumval)
+        .Mutate(corpus_value.value, prng, metadata, only_shrink);
+  }
 }
 
-uint64_t FlatbuffersTableUntypedDomainImpl::CountNumberOfFields(
-    corpus_type& val) {
+uint64_t FlatbuffersUnionDomainImpl::CountNumberOfFields(
+    corpus_type& corpus_value) {
   uint64_t field_count = 0;
-  for (const auto* field : *table_object_->fields()) {
-    VisitFlatbufferField(
-        schema_, field,
-        CountNumberOfMutableFieldsVisitor{*this, field_count, val});
+
+  // If the union has only one type (besides NONE), the type is not counted
+  // as mutable field.
+  if (union_def_->values()->size() <= 2) {
+    return field_count;
+  }
+
+  // The first field is the union type.
+  ++field_count;
+
+  auto type_value = type_domain_.GetValue(corpus_value.type);
+  auto type_enumval = union_def_->values()->LookupByKey(type_value);
+  if (type_enumval == nullptr) {
+    return field_count;
+  }
+  const reflection::Object* object =
+      schema_->objects()->Get(type_enumval->union_type()->index());
+  if (object->is_struct()) {
+    auto domain = GetCachedDomain<FlatbuffersStructTag>(*type_enumval);
+    field_count += domain.CountNumberOfFields(corpus_value.value);
+  } else {
+    auto domain = GetCachedDomain<FlatbuffersTableTag>(*type_enumval);
+    field_count += domain.CountNumberOfFields(corpus_value.value);
   }
   return field_count;
 }
 
-uint64_t FlatbuffersTableUntypedDomainImpl::MutateSelectedField(
-    corpus_type& val, absl::BitGenRef prng,
+uint64_t FlatbuffersUnionDomainImpl::MutateSelectedField(
+    corpus_type& corpus_value, absl::BitGenRef prng,
     const domain_implementor::MutationMetadata& metadata, bool only_shrink,
     uint64_t selected_field_index) {
-  uint64_t field_counter = 0;
-  uint64_t fields_count = CountNumberOfFields(val);
-  if (fields_count < selected_field_index) {
-    return fields_count;
+  uint64_t field_count = 0;
+
+  // If the union has only one type (besides NONE), the type is not counted
+  // as mutable field.
+  if (union_def_->values()->size() <= 2) {
+    return field_count;
   }
 
-  for (const auto* field : *table_object_->fields()) {
-    if (!IsSupportedField(field)) {
-      if (only_shrink && !val.contains(field->id())) continue;
-    }
+  // The first field is the union type.
+  ++field_count;
+  if (selected_field_index == field_count) {
+    type_domain_.Mutate(corpus_value.type, prng, metadata, only_shrink);
+    auto type_value = type_domain_.GetValue(corpus_value.type);
+    auto type_enumval = union_def_->values()->LookupByKey(type_value);
+    if (type_enumval == nullptr) return selected_field_index;
 
-    ++field_counter;
-    if (field_counter == selected_field_index) {
-      VisitFlatbufferField(
-          schema_, field,
-          MutateVisitor{*this, prng, metadata, only_shrink, val});
-      return field_counter;
+    const reflection::Object* object =
+        schema_->objects()->Get(type_enumval->union_type()->index());
+    if (object->is_struct()) {
+      corpus_value.value =
+          GetCachedDomain<FlatbuffersStructTag>(*type_enumval).Init(prng);
+    } else {
+      corpus_value.value =
+          GetCachedDomain<FlatbuffersTableTag>(*type_enumval).Init(prng);
     }
-
-    if (field->type()->base_type() == reflection::BaseType::Obj) {
-      auto sub_object = schema_->objects()->Get(field->type()->index());
-      if (!sub_object->is_struct()) {
-        field_counter +=
-            GetCachedDomain<FlatbuffersTableTag>(field).MutateSelectedField(
-                val[field->id()], prng, metadata, only_shrink,
-                selected_field_index - field_counter);
-      }
-      // TODO: Add support for structs.
-    }
-
-    if (field_counter >= selected_field_index) {
-      return field_counter;
-    }
+    return field_count;
   }
-  return field_counter;
+
+  auto type_value = type_domain_.GetValue(corpus_value.type);
+
+  auto type_enumval = union_def_->values()->LookupByKey(type_value);
+  if (type_enumval == nullptr) {
+    return 0;
+  }
+  const reflection::Object* object =
+      schema_->objects()->Get(type_enumval->union_type()->index());
+  if (object->is_struct()) {
+    auto domain = GetCachedDomain<FlatbuffersStructTag>(*type_enumval);
+    field_count += domain.MutateSelectedField(
+        corpus_value.value, prng, metadata, only_shrink,
+        selected_field_index - field_count);
+  } else {
+    auto domain = GetCachedDomain<FlatbuffersTableTag>(*type_enumval);
+    field_count += domain.MutateSelectedField(
+        corpus_value.value, prng, metadata, only_shrink,
+        selected_field_index - field_count);
+  }
+  return field_count;
 }
 
-absl::Status FlatbuffersTableUntypedDomainImpl::ValidateCorpusValue(
+absl::Status FlatbuffersUnionDomainImpl::ValidateCorpusValue(
     const corpus_type& corpus_value) const {
-  for (const auto* field : *table_object_->fields()) {
-    absl::Status result;
-    GenericDomainCorpusType field_corpus;
-    if (auto it = corpus_value.find(field->id()); it != corpus_value.end()) {
-      field_corpus = it->second;
-    }
-    if (!field_corpus.has_value()) continue;
-    VisitFlatbufferField(schema_, field,
-                         ValidateVisitor{*this, field_corpus, result});
-    if (!result.ok()) return result;
+  // Unions are encoded as the combination of two fields: an enum representing
+  // the union choice and the offset to the actual element.
+  //
+  // Both type and value should be validated.
+  //
+  // Start with the type validation.
+  auto type_value = type_domain_.GetValue(corpus_value.type);
+
+  auto type_enumval = union_def_->values()->LookupByKey(type_value);
+  if (type_enumval == nullptr) {
+    return absl::InvalidArgumentError(
+        absl::StrCat("Invalid union type: ", type_value));
   }
-  return absl::OkStatus();
+
+  // Validate the value.
+  if (!corpus_value.value.has_value()) {
+    return absl::InvalidArgumentError("Union value is not set.");
+  }
+  const reflection::Object* object =
+      schema_->objects()->Get(type_enumval->union_type()->index());
+  if (object->is_struct()) {
+    auto domain = GetCachedDomain<FlatbuffersStructTag>(*type_enumval);
+    return domain.ValidateCorpusValue(corpus_value.value);
+  } else {
+    auto domain = GetCachedDomain<FlatbuffersTableTag>(*type_enumval);
+    return domain.ValidateCorpusValue(corpus_value.value);
+  }
+}
+
+// Converts the value to a corpus value.
+std::optional<FlatbuffersUnionDomainImpl::corpus_type>
+FlatbuffersUnionDomainImpl::FromValue(const value_type& value) const {
+  auto out = std::make_optional<corpus_type>();
+  auto type_corpus = type_domain_.FromValue(value.type);
+  if (type_corpus.has_value()) {
+    out->type = *type_corpus;
+  }
+  auto type_enumval = union_def_->values()->LookupByKey(value.type);
+  if (type_enumval == nullptr) {
+    return std::nullopt;
+  }
+  const reflection::Object* object =
+      schema_->objects()->Get(type_enumval->union_type()->index());
+  std::optional<CopyableAny> inner_corpus;
+  if (object->is_struct()) {
+    auto domain = GetCachedDomain<FlatbuffersStructTag>(*type_enumval);
+    inner_corpus =
+        domain.FromValue(static_cast<const flatbuffers::Struct*>(value.value));
+  } else {
+    auto domain = GetCachedDomain<FlatbuffersTableTag>(*type_enumval);
+    inner_corpus =
+        domain.FromValue(static_cast<const flatbuffers::Table*>(value.value));
+  }
+  if (inner_corpus.has_value()) {
+    out->value = std::move(inner_corpus.value());
+  }
+  return out;
+}
+
+// Converts the IRObject to a corpus value.
+std::optional<FlatbuffersUnionDomainImpl::corpus_type>
+FlatbuffersUnionDomainImpl::ParseCorpus(const IRObject& obj) const {
+  // Follows the structure created by `SerializeCorpus` to deserialize the
+  // IRObject.
+  corpus_type out;
+  auto subs = obj.Subs();
+  if (!subs) {
+    return std::nullopt;
+  }
+
+  // We expect 2 fields: the type and the value.
+  if (subs->size() != 2) {
+    return std::nullopt;
+  }
+
+  // Parse the type which is stored in the first field of the IRObject subs.
+  auto type_corpus = type_domain_.ParseCorpus((*subs)[0]);
+  if (!type_corpus.has_value()) {
+    return std::nullopt;
+  }
+  if (auto status = type_domain_.ValidateCorpusValue(*type_corpus);
+      !status.ok()) {
+    FUZZTEST_LOG(ERROR) << "Failed to validate type corpus: "
+                        << status.message();
+    return std::nullopt;
+  }
+  out.type = *type_corpus;
+  auto type_value = type_domain_.GetValue(out.type);
+  auto type_enumval = union_def_->values()->LookupByKey(type_value);
+  if (type_enumval == nullptr) {
+    return std::nullopt;
+  }
+
+  // Parse the value.
+  const reflection::Object* object =
+      schema_->objects()->Get(type_enumval->union_type()->index());
+  if (object == nullptr) {
+    return std::nullopt;
+  }
+  std::optional<CopyableAny> inner_corpus;
+  if (object->is_struct()) {
+    auto domain = GetCachedDomain<FlatbuffersStructTag>(*type_enumval);
+    // The value is stored in the second field of the IRObject subs.
+    inner_corpus = domain.ParseCorpus((*subs)[1]);
+  } else {
+    auto domain = GetCachedDomain<FlatbuffersTableTag>(*type_enumval);
+    // The value is stored in the second field of the IRObject subs.
+    inner_corpus = domain.ParseCorpus((*subs)[1]);
+  }
+
+  if (inner_corpus.has_value()) {
+    out.value = std::move(inner_corpus.value());
+  }
+  return out;
+}
+
+// Converts the corpus value to an IRObject.
+IRObject FlatbuffersUnionDomainImpl::SerializeCorpus(
+    const corpus_type& corpus_value) const {
+  IRObject out;
+  auto type_value = type_domain_.GetValue(corpus_value.type);
+
+  auto type_enumval = union_def_->values()->LookupByKey(type_value);
+  if (type_enumval == nullptr) {
+    return out;
+  }
+
+  auto& pair = out.MutableSubs();
+  // We have 2 fields: the type and the value.
+  pair.reserve(2);
+
+  // Serialize the type.
+  pair.push_back(type_domain_.SerializeCorpus(corpus_value.type));
+
+  // Serialize the value.
+  const reflection::Object* object =
+      schema_->objects()->Get(type_enumval->union_type()->index());
+  if (object->is_struct()) {
+    auto domain = GetCachedDomain<FlatbuffersStructTag>(*type_enumval);
+    pair.push_back(domain.SerializeCorpus(corpus_value.value));
+  } else {
+    auto domain = GetCachedDomain<FlatbuffersTableTag>(*type_enumval);
+    pair.push_back(domain.SerializeCorpus(corpus_value.value));
+  }
+  return out;
+}
+
+std::optional<flatbuffers::uoffset_t> FlatbuffersUnionDomainImpl::BuildValue(
+    const corpus_type& corpus_value,
+    flatbuffers::FlatBufferBuilder64& builder) const {
+  // Get the object type.
+  auto type_value = type_domain_.GetValue(corpus_value.type);
+  auto type_enumval = union_def_->values()->LookupByKey(type_value);
+  if (type_enumval == nullptr || !corpus_value.value.has_value()) {
+    return std::nullopt;
+  }
+  const reflection::Object* object =
+      schema_->objects()->Get(type_enumval->union_type()->index());
+  if (object == nullptr) {
+    return std::nullopt;
+  }
+  if (object->is_struct()) {
+    FlatbuffersStructUntypedDomainImpl domain{schema_, object};
+    return domain.BuildValue(
+        corpus_value.value
+            .GetAs<corpus_type_t<FlatbuffersStructUntypedDomainImpl>>(),
+        builder);
+  } else {
+    FlatbuffersTableUntypedDomainImpl domain{schema_, object};
+    return domain.BuildTable(
+        corpus_value.value
+            .GetAs<corpus_type_t<FlatbuffersTableUntypedDomainImpl>>(),
+        builder);
+  }
+}
+
+void FlatbuffersUnionDomainImpl::Printer::PrintCorpusValue(
+    const corpus_type& value, domain_implementor::RawSink out,
+    domain_implementor::PrintMode mode) const {
+  auto type_value = self.type_domain_.GetValue(value.type);
+  auto type_enumval = self.union_def_->values()->LookupByKey(type_value);
+  if (type_enumval == nullptr) {
+    return;
+  }
+  absl::Format(out, "<%s>(", type_enumval->name()->str());
+
+  const reflection::Object* object =
+      self.schema_->objects()->Get(type_enumval->union_type()->index());
+  if (object->is_struct()) {
+    auto domain = self.GetCachedDomain<FlatbuffersStructTag>(*type_enumval);
+    domain_implementor::PrintValue(domain, value.value, out, mode);
+  } else {
+    auto domain = self.GetCachedDomain<FlatbuffersTableTag>(*type_enumval);
+    domain_implementor::PrintValue(domain, value.value, out, mode);
+  }
+  absl::Format(out, ")");
+}
+
+std::optional<FlatbuffersStructUntypedDomainImpl::corpus_type>
+FlatbuffersStructUntypedDomainImpl::FromValue(const value_type& value) const {
+  if (value == nullptr) {
+    return std::nullopt;
+  }
+  corpus_type val;
+  for (const auto& [_, field] : fields_by_id_) {
+    VisitFlatbufferField(schema_, field, FromValueVisitor{*this, value, val});
+  }
+  return val;
+}
+
+std::optional<flatbuffers::uoffset_t>
+FlatbuffersStructUntypedDomainImpl::BuildValue(
+    const corpus_type& value, flatbuffers::FlatBufferBuilder64& builder) const {
+  std::vector<uint8_t> buf(object_->bytesize());
+  BuildValue(value, buf.data());
+  builder.StartStruct(object_->minalign());
+  builder.PushBytes(buf.data(), buf.size());
+  return builder.EndStruct();
+}
+
+void FlatbuffersStructUntypedDomainImpl::BuildValue(const corpus_type& value,
+                                                    uint8_t* buf) const {
+  for (const auto& [_, field] : fields_by_id_) {
+    VisitFlatbufferField(schema_, field, BuildValueVisitor{*this, value, buf});
+  }
 }
 
 std::optional<FlatbuffersTableUntypedDomainImpl::corpus_type>
@@ -183,98 +483,12 @@
     return std::nullopt;
   }
   corpus_type ret;
-  for (const auto* field : *table_object_->fields()) {
+  for (const auto& [_, field] : fields_by_id_) {
     VisitFlatbufferField(schema_, field, FromValueVisitor{*this, value, ret});
   }
   return ret;
 }
 
-std::optional<FlatbuffersTableUntypedDomainImpl::corpus_type>
-FlatbuffersTableUntypedDomainImpl::ParseCorpus(const IRObject& obj) const {
-  corpus_type out;
-  auto subs = obj.Subs();
-  if (!subs) {
-    return std::nullopt;
-  }
-  // Follows the structure created by `SerializeCorpus` to deserialize the
-  // IRObject.
-
-  // subs->size() represents the number of fields in the table.
-  out.reserve(subs->size());
-  for (const auto& sub : *subs) {
-    auto pair_subs = sub.Subs();
-    // Each field is represented by a pair of field id and the serialized
-    // corpus value.
-    if (!pair_subs.has_value() || pair_subs->size() != 2) {
-      return std::nullopt;
-    }
-
-    // Deserialize the field id.
-    auto id = (*pair_subs)[0].GetScalar<typename corpus_type::key_type>();
-    if (!id.has_value()) {
-      return std::nullopt;
-    }
-
-    // Get information about the field from reflection.
-    const reflection::Field* absl_nullable field = GetFieldById(*id);
-    if (field == nullptr) {
-      return std::nullopt;
-    }
-
-    // Deserialize the field corpus value.
-    std::optional<GenericDomainCorpusType> inner_parsed;
-    VisitFlatbufferField(schema_, field,
-                         ParseVisitor{*this, (*pair_subs)[1], inner_parsed});
-    if (!inner_parsed) {
-      return std::nullopt;
-    }
-    out[id.value()] = *std::move(inner_parsed);
-  }
-  return out;
-}
-
-IRObject FlatbuffersTableUntypedDomainImpl::SerializeCorpus(
-    const corpus_type& value) const {
-  IRObject out;
-  auto& subs = out.MutableSubs();
-  subs.reserve(value.size());
-
-  // Each field is represented by a pair of field id and the serialized
-  // corpus value.
-  for (const auto& [id, field_corpus] : value) {
-    // Get information about the field from reflection.
-    const reflection::Field* absl_nullable field = GetFieldById(id);
-    if (field == nullptr) {
-      continue;
-    }
-    IRObject& pair = subs.emplace_back();
-    auto& pair_subs = pair.MutableSubs();
-    pair_subs.reserve(2);
-
-    // Serialize the field id.
-    pair_subs.emplace_back(field->id());
-
-    // Serialize the field corpus value.
-    VisitFlatbufferField(
-        schema_, field,
-        SerializeVisitor{*this, field_corpus, pair_subs.emplace_back()});
-  }
-  return out;
-}
-
-bool FlatbuffersTableUntypedDomainImpl::IsSupportedField(
-    const reflection::Field* absl_nonnull field) const {
-  auto base_type = field->type()->base_type();
-  if (base_type == reflection::BaseType::UType) return false;
-  if (flatbuffers::IsScalar(base_type)) return true;
-  if (base_type == reflection::BaseType::String) return true;
-  if (base_type == reflection::BaseType::Obj) {
-    auto sub_object = schema_->objects()->Get(field->type()->index());
-    return !sub_object->is_struct();
-  };
-  return false;
-}
-
 uint32_t FlatbuffersTableUntypedDomainImpl::BuildTable(
     const corpus_type& value, flatbuffers::FlatBufferBuilder64& builder) const {
   // Add all the fields to the builder.
diff --git a/fuzztest/internal/domains/flatbuffers_domain_impl.h b/fuzztest/internal/domains/flatbuffers_domain_impl.h
index 328b2e7..0d5e4b0 100644
--- a/fuzztest/internal/domains/flatbuffers_domain_impl.h
+++ b/fuzztest/internal/domains/flatbuffers_domain_impl.h
@@ -16,9 +16,11 @@
 #define FUZZTEST_FUZZTEST_INTERNAL_DOMAINS_FLATBUFFERS_DOMAIN_IMPL_H_
 
 #include <algorithm>
+#include <cstddef>
 #include <cstdint>
 #include <initializer_list>
 #include <limits>
+#include <list>
 #include <optional>
 #include <string>
 #include <type_traits>
@@ -26,9 +28,9 @@
 #include <variant>
 #include <vector>
 
-#include "absl/algorithm/container.h"
 #include "absl/base/nullability.h"
 #include "absl/base/thread_annotations.h"
+#include "absl/container/btree_map.h"
 #include "absl/container/flat_hash_map.h"
 #include "absl/container/flat_hash_set.h"
 #include "absl/random/bit_gen_ref.h"
@@ -39,20 +41,25 @@
 #include "flatbuffers/base.h"
 #include "flatbuffers/buffer.h"
 #include "flatbuffers/flatbuffer_builder.h"
+#include "flatbuffers/reflection.h"
 #include "flatbuffers/reflection_generated.h"
 #include "flatbuffers/string.h"
+#include "flatbuffers/struct.h"
 #include "flatbuffers/table.h"
+#include "flatbuffers/vector.h"
 #include "flatbuffers/verifier.h"
 #include "./common/logging.h"
 #include "./fuzztest/domain_core.h"
 #include "./fuzztest/internal/any.h"
 #include "./fuzztest/internal/domains/arbitrary_impl.h"
+#include "./fuzztest/internal/domains/container_of_impl.h"
 #include "./fuzztest/internal/domains/domain_base.h"
 #include "./fuzztest/internal/domains/domain_type_erasure.h"
 #include "./fuzztest/internal/domains/element_of_impl.h"
 #include "./fuzztest/internal/meta.h"
 #include "./fuzztest/internal/serialization.h"
 #include "./fuzztest/internal/status.h"
+#include "./fuzztest/internal/type_support.h"
 
 namespace fuzztest::internal {
 
@@ -69,120 +76,221 @@
 template <typename T>
 struct is_flatbuffers_enum_tag : std::false_type {};
 
-template <typename Underlying>
-struct is_flatbuffers_enum_tag<FlatbuffersEnumTag<Underlying>>
+template <typename Underlying, typename Enable>
+struct is_flatbuffers_enum_tag<FlatbuffersEnumTag<Underlying, Enable>>
     : std::true_type {};
 
 template <typename T>
 inline constexpr bool is_flatbuffers_enum_tag_v =
     is_flatbuffers_enum_tag<T>::value;
 
+//
+// Flatbuffers vector detection.
+//
+template <typename T>
+struct FlatbuffersVectorTag {
+  using value_type = T;
+};
+
+template <typename T>
+struct is_flatbuffers_vector_tag : std::false_type {};
+
+template <typename T>
+struct is_flatbuffers_vector_tag<FlatbuffersVectorTag<T>> : std::true_type {};
+
+template <typename T>
+inline constexpr bool is_flatbuffers_vector_tag_v =
+    is_flatbuffers_vector_tag<T>::value;
+
+template <typename T>
+struct FlatbuffersVector64Tag {
+  using value_type = T;
+};
+
+template <typename T>
+struct is_flatbuffers_vector64_tag : std::false_type {};
+
+template <typename T>
+struct is_flatbuffers_vector64_tag<FlatbuffersVector64Tag<T>> : std::true_type {
+};
+
+template <typename T>
+inline constexpr bool is_flatbuffers_vector64_tag_v =
+    is_flatbuffers_vector64_tag<T>::value;
+
+template <typename T>
+inline constexpr bool is_any_flatbuffers_vector_tag_v =
+    is_flatbuffers_vector_tag_v<T> || is_flatbuffers_vector64_tag_v<T>;
+
+template <typename T>
+struct flatbuffers_vector_tag_offset;
+
+template <typename T>
+struct flatbuffers_vector_tag_offset<FlatbuffersVectorTag<T>> {
+  using type = flatbuffers::uoffset_t;
+};
+
+template <typename T>
+struct flatbuffers_vector_tag_offset<FlatbuffersVector64Tag<T>> {
+  using type = flatbuffers::uoffset64_t;
+};
+
+template <typename T>
+using flatbuffers_vector_tag_offset_t =
+    typename flatbuffers_vector_tag_offset<T>::type;
+
 struct FlatbuffersArrayTag;
+
+// Flatbuffers container element type detection.
+template <typename T, typename ValueType>
+inline constexpr bool is_flatbuffers_container_of_v = []() constexpr {
+  if constexpr (is_flatbuffers_vector_tag_v<T> ||
+                is_flatbuffers_vector64_tag_v<T>) {
+    return std::is_same_v<ValueType, typename T::value_type>;
+  } else {
+    return false;
+  }
+}();
+
 struct FlatbuffersTableTag;
 struct FlatbuffersStructTag;
 struct FlatbuffersUnionTag;
-struct FlatbuffersVectorTag;
+
+template <typename T>
+using IdentityWrapper = T;
 
 // Dynamic to static dispatch visitor pattern.
-template <typename Visitor>
-auto VisitFlatbufferField(const reflection::Schema* absl_nonnull schema,
+template <template <typename> typename Wrapper = IdentityWrapper,
+          bool in_container = false, typename Visitor>
+void VisitFlatbufferField(const reflection::Schema* absl_nonnull schema,
                           const reflection::Field* absl_nonnull field,
-                          Visitor visitor) {
-  auto field_index = field->type()->index();
-  switch (field->type()->base_type()) {
+                          Visitor&& visitor) {
+  const auto type =
+      in_container ? field->type()->element() : field->type()->base_type();
+  const auto field_index = field->type()->index();
+  const bool is_enum = flatbuffers::IsInteger(type) && field_index >= 0;
+  switch (type) {
     case reflection::BaseType::Bool:
-      visitor.template Visit<bool>(field);
+      std::forward<Visitor>(visitor).template Visit<Wrapper<bool>>(field);
       break;
     case reflection::BaseType::Byte:
-      if (field_index >= 0) {
-        visitor.template Visit<FlatbuffersEnumTag<int8_t>>(field);
+      if (is_enum) {
+        std::forward<Visitor>(visitor)
+            .template Visit<Wrapper<FlatbuffersEnumTag<int8_t>>>(field);
       } else {
-        visitor.template Visit<int8_t>(field);
+        std::forward<Visitor>(visitor).template Visit<Wrapper<int8_t>>(field);
       }
       break;
     case reflection::BaseType::Short:
-      if (field_index >= 0) {
-        visitor.template Visit<FlatbuffersEnumTag<int16_t>>(field);
+      if (is_enum) {
+        std::forward<Visitor>(visitor)
+            .template Visit<Wrapper<FlatbuffersEnumTag<int16_t>>>(field);
       } else {
-        visitor.template Visit<int16_t>(field);
+        std::forward<Visitor>(visitor).template Visit<Wrapper<int16_t>>(field);
       }
       break;
     case reflection::BaseType::Int:
-      if (field_index >= 0) {
-        visitor.template Visit<FlatbuffersEnumTag<int32_t>>(field);
+      if (is_enum) {
+        std::forward<Visitor>(visitor)
+            .template Visit<Wrapper<FlatbuffersEnumTag<int32_t>>>(field);
       } else {
-        visitor.template Visit<int32_t>(field);
+        std::forward<Visitor>(visitor).template Visit<Wrapper<int32_t>>(field);
       }
       break;
     case reflection::BaseType::Long:
-      if (field_index >= 0) {
-        visitor.template Visit<FlatbuffersEnumTag<int64_t>>(field);
+      if (is_enum) {
+        std::forward<Visitor>(visitor)
+            .template Visit<Wrapper<FlatbuffersEnumTag<int64_t>>>(field);
       } else {
-        visitor.template Visit<int64_t>(field);
+        std::forward<Visitor>(visitor).template Visit<Wrapper<int64_t>>(field);
       }
       break;
     case reflection::BaseType::UByte:
-      if (field_index >= 0) {
-        visitor.template Visit<FlatbuffersEnumTag<uint8_t>>(field);
+      if (is_enum) {
+        std::forward<Visitor>(visitor)
+            .template Visit<Wrapper<FlatbuffersEnumTag<uint8_t>>>(field);
       } else {
-        visitor.template Visit<uint8_t>(field);
+        std::forward<Visitor>(visitor).template Visit<Wrapper<uint8_t>>(field);
       }
       break;
     case reflection::BaseType::UShort:
-      if (field_index >= 0) {
-        visitor.template Visit<FlatbuffersEnumTag<uint16_t>>(field);
+      if (is_enum) {
+        std::forward<Visitor>(visitor)
+            .template Visit<Wrapper<FlatbuffersEnumTag<uint16_t>>>(field);
       } else {
-        visitor.template Visit<uint16_t>(field);
+        std::forward<Visitor>(visitor).template Visit<Wrapper<uint16_t>>(field);
       }
       break;
     case reflection::BaseType::UInt:
-      if (field_index >= 0) {
-        visitor.template Visit<FlatbuffersEnumTag<uint32_t>>(field);
+      if (is_enum) {
+        std::forward<Visitor>(visitor)
+            .template Visit<Wrapper<FlatbuffersEnumTag<uint32_t>>>(field);
       } else {
-        visitor.template Visit<uint32_t>(field);
+        std::forward<Visitor>(visitor).template Visit<Wrapper<uint32_t>>(field);
       }
       break;
     case reflection::BaseType::ULong:
-      if (field_index >= 0) {
-        visitor.template Visit<FlatbuffersEnumTag<uint64_t>>(field);
+      if (is_enum) {
+        std::forward<Visitor>(visitor)
+            .template Visit<Wrapper<FlatbuffersEnumTag<uint64_t>>>(field);
       } else {
-        visitor.template Visit<uint64_t>(field);
+        std::forward<Visitor>(visitor).template Visit<Wrapper<uint64_t>>(field);
       }
       break;
     case reflection::BaseType::Float:
-      visitor.template Visit<float>(field);
+      std::forward<Visitor>(visitor).template Visit<Wrapper<float>>(field);
       break;
     case reflection::BaseType::Double:
-      visitor.template Visit<double>(field);
+      std::forward<Visitor>(visitor).template Visit<Wrapper<double>>(field);
       break;
     case reflection::BaseType::String:
-      visitor.template Visit<std::string>(field);
+      std::forward<Visitor>(visitor).template Visit<Wrapper<std::string>>(
+          field);
       break;
     case reflection::BaseType::Vector:
-    case reflection::BaseType::Vector64:
-      visitor.template Visit<FlatbuffersVectorTag>(field);
-      break;
-    case reflection::BaseType::Array:
-      visitor.template Visit<FlatbuffersArrayTag>(field);
-      break;
-    case reflection::BaseType::Obj: {
-      auto sub_object = schema->objects()->Get(field->type()->index());
-      if (sub_object->is_struct()) {
-        visitor.template Visit<FlatbuffersStructTag>(field);
+      if constexpr (in_container) {
+        FUZZTEST_LOG(FATAL) << "Nested containers are not supported.";
       } else {
-        visitor.template Visit<FlatbuffersTableTag>(field);
+        VisitFlatbufferField<FlatbuffersVectorTag, /*in_container=*/true>(
+            schema, field, std::forward<Visitor>(visitor));
       }
       break;
-    }
+    case reflection::BaseType::Vector64:
+      if constexpr (in_container) {
+        FUZZTEST_LOG(FATAL) << "Nested containers are not supported.";
+      } else {
+        VisitFlatbufferField<FlatbuffersVector64Tag, /*in_container=*/true>(
+            schema, field, std::forward<Visitor>(visitor));
+      }
+      break;
+    case reflection::BaseType::Array:
+      if constexpr (in_container) {
+        FUZZTEST_LOG(FATAL) << "Nested containers are not supported.";
+      } else {
+        std::forward<Visitor>(visitor)
+            .template Visit<Wrapper<FlatbuffersArrayTag>>(field);
+      }
+      break;
+    case reflection::BaseType::Obj:
+      if (schema->objects()->Get(field_index)->is_struct()) {
+        std::forward<Visitor>(visitor)
+            .template Visit<Wrapper<FlatbuffersStructTag>>(field);
+      } else {
+        std::forward<Visitor>(visitor)
+            .template Visit<Wrapper<FlatbuffersTableTag>>(field);
+      }
+      break;
     case reflection::BaseType::Union:
-      visitor.template Visit<FlatbuffersUnionTag>(field);
+      std::forward<Visitor>(visitor)
+          .template Visit<Wrapper<FlatbuffersUnionTag>>(field);
       break;
     case reflection::BaseType::UType:
-      // Noop
+      // Noop: Union type fields are handled when processing their
+      // corresponding union field
       break;
     default:
       FUZZTEST_LOG(FATAL) << "Unsupported base type: "
-                          << field->type()->base_type();
+                          << reflection::EnumNameBaseType(type);
   }
 }
 
@@ -285,98 +393,795 @@
 auto GetDefaultDomain(const reflection::Schema* absl_nonnull schema,
                       const reflection::Field* absl_nonnull field);
 
-// Domain implementation for flatbuffers untyped tables.
+// Base class for flatbuffers struct and table domain implementations.
 // The corpus type is a map of field ids to field values.
-class FlatbuffersTableUntypedDomainImpl
-    : public fuzztest::domain_implementor::DomainBase<
-          /*Derived=*/FlatbuffersTableUntypedDomainImpl,
-          /*ValueType=*/const flatbuffers::Table* absl_nonnull,
+template <typename Derived, typename ValueType>
+class FlatbuffersUntypedObjectDomainBase
+    : public domain_implementor::DomainBase<
+          /*Derived=*/Derived,
+          /*ValueType=*/ValueType,
           /*CorpusType=*/
           absl::flat_hash_map<
               decltype(static_cast<reflection::Field*>(nullptr)->id()),
               GenericDomainCorpusType>> {
  public:
-  template <typename T>
-  friend class FlatbuffersTableDomainImpl;
+  using DomainBase = domain_implementor::DomainBase<
+      Derived, ValueType,
+      absl::flat_hash_map<
+          decltype(static_cast<reflection::Field*>(nullptr)->id()),
+          GenericDomainCorpusType>>;
+  using typename FlatbuffersUntypedObjectDomainBase::DomainBase::corpus_type;
+  using typename FlatbuffersUntypedObjectDomainBase::DomainBase::value_type;
 
-  using typename FlatbuffersTableUntypedDomainImpl::DomainBase::corpus_type;
-  using typename FlatbuffersTableUntypedDomainImpl::DomainBase::value_type;
-
-  explicit FlatbuffersTableUntypedDomainImpl(
+  FlatbuffersUntypedObjectDomainBase(
       const reflection::Schema* absl_nonnull schema,
-      const reflection::Object* absl_nonnull table_object);
+      const reflection::Object* absl_nonnull object)
+      : schema_(schema), object_(object) {
+    for (const auto* field : *object_->fields()) {
+      fields_by_id_[field->id()] = field;
+    }
+  }
 
-  FlatbuffersTableUntypedDomainImpl(
-      const FlatbuffersTableUntypedDomainImpl& other);
+  virtual ~FlatbuffersUntypedObjectDomainBase() = default;
 
-  FlatbuffersTableUntypedDomainImpl& operator=(
-      const FlatbuffersTableUntypedDomainImpl& other);
+  FlatbuffersUntypedObjectDomainBase(
+      const FlatbuffersUntypedObjectDomainBase& other)
+      : DomainBase(other),
+        schema_(other.schema_),
+        object_(other.object_),
+        fields_by_id_(other.fields_by_id_) {
+    absl::MutexLock l(mutex_);
+    absl::MutexLock l_other(other.mutex_);
+    domains_ = other.domains_;
+  }
 
-  FlatbuffersTableUntypedDomainImpl(FlatbuffersTableUntypedDomainImpl&& other);
+  FlatbuffersUntypedObjectDomainBase& operator=(
+      const FlatbuffersUntypedObjectDomainBase& other) {
+    schema_ = other.schema_;
+    object_ = other.object_;
+    fields_by_id_ = other.fields_by_id_;
+    absl::MutexLock l(mutex_);
+    absl::MutexLock l_other(other.mutex_);
+    domains_ = other.domains_;
+    DomainBase::operator=(other);
+    return *this;
+  }
 
-  FlatbuffersTableUntypedDomainImpl& operator=(
-      FlatbuffersTableUntypedDomainImpl&& other);
+  FlatbuffersUntypedObjectDomainBase(FlatbuffersUntypedObjectDomainBase&& other)
+      : schema_(other.schema_),
+        object_(other.object_),
+        fields_by_id_(std::move(other.fields_by_id_)) {
+    absl::MutexLock l(mutex_);
+    absl::MutexLock l_other(other.mutex_);
+    domains_ = std::move(other.domains_);
+    DomainBase::operator=(other);
+  }
+
+  FlatbuffersUntypedObjectDomainBase& operator=(
+      FlatbuffersUntypedObjectDomainBase&& other) {
+    schema_ = other.schema_;
+    object_ = other.object_;
+    fields_by_id_ = std::move(other.fields_by_id_);
+    absl::MutexLock l(mutex_);
+    absl::MutexLock l_other(other.mutex_);
+    domains_ = std::move(other.domains_);
+    DomainBase::operator=(std::move(other));
+    return *this;
+  }
 
   // Initializes the corpus value.
-  corpus_type Init(absl::BitGenRef prng);
+  corpus_type Init(absl::BitGenRef prng) {
+    if (auto seed = this->MaybeGetRandomSeed(prng)) {
+      return *seed;
+    }
+    corpus_type val;
+    for (const auto& [_, field] : fields_by_id_) {
+      VisitFlatbufferField(schema_, field,
+                           InitializeVisitor{Self(), prng, val});
+    }
+    return val;
+  }
 
   // Mutates the corpus value.
   void Mutate(corpus_type& val, absl::BitGenRef prng,
               const domain_implementor::MutationMetadata& metadata,
-              bool only_shrink);
+              bool only_shrink) {
+    auto field_count = CountNumberOfFields(val);
+    if (field_count == 0) return;
+    auto selected_field_index = absl::Uniform(prng, 1ul, field_count + 1);
+
+    MutateSelectedField(val, prng, metadata, only_shrink, selected_field_index);
+  }
 
   // Counts the number of fields that can be mutated.
-  uint64_t CountNumberOfFields(corpus_type& val);
+  // Returns the number of fields in the flattened tree for supported field
+  // types.
+  uint64_t CountNumberOfFields(corpus_type& val) {
+    uint64_t field_count = 0;
+    for (const auto& [_, field] : fields_by_id_) {
+      VisitFlatbufferField(
+          schema_, field,
+          CountNumberOfMutableFieldsVisitor{Self(), field_count, val});
+    }
+    return field_count;
+  }
 
   // Mutates the selected field.
   // The selected field index is based on the flattened tree.
   uint64_t MutateSelectedField(
       corpus_type& val, absl::BitGenRef prng,
       const domain_implementor::MutationMetadata& metadata, bool only_shrink,
+      uint64_t selected_field_index) {
+    uint64_t field_counter = 0;
+    uint64_t fields_count = CountNumberOfFields(val);
+    if (fields_count < selected_field_index) {
+      return fields_count;
+    }
+    for (const auto& [_, field] : fields_by_id_) {
+      VisitFlatbufferField(schema_, field,
+                           MutateSelectedFieldVisitor{
+                               Self(), prng, metadata, selected_field_index,
+                               only_shrink, val, field_counter});
+      if (field_counter >= selected_field_index) {
+        return field_counter;
+      }
+    }
+    return field_counter;
+  }
+
+  auto GetPrinter() const { return Printer{Self()}; }
+
+  absl::Status ValidateCorpusValue(const corpus_type& corpus_value) const {
+    for (const auto& [id, field_corpus] : corpus_value) {
+      const reflection::Field* absl_nullable field = GetFieldById(id);
+      if (field == nullptr) {
+        return absl::InvalidArgumentError(
+            absl::StrCat("Field id ", id, " is not found in the object."));
+      }
+      absl::Status result;
+      VisitFlatbufferField(schema_, field,
+                           ValidateVisitor{Self(), field_corpus, result});
+      if (!result.ok()) return result;
+    }
+    return absl::OkStatus();
+  }
+
+  value_type GetValue(const corpus_type& value) const {
+    FUZZTEST_LOG(FATAL)
+        << "GetValue is not supported for the untyped Flatbuffers domain.";
+    // Untyped domain does not support GetValue since if it is a nested object
+    // it would need the top level object corpus value to be able to build it.
+    return nullptr;
+  }
+
+  // Converts the IRObject to a corpus value.
+  std::optional<corpus_type> ParseCorpus(const IRObject& obj) const {
+    corpus_type out;
+    auto subs = obj.Subs();
+    if (!subs) {
+      return std::nullopt;
+    }
+    // Follows the structure created by `SerializeCorpus` to deserialize the
+    // IRObject.
+
+    // subs->size() represents the number of fields in the table.
+    out.reserve(subs->size());
+    for (const auto& sub : *subs) {
+      auto pair_subs = sub.Subs();
+      // Each field is represented by a pair of field id and the serialized
+      // corpus value.
+      if (!pair_subs || pair_subs->size() != 2) {
+        return std::nullopt;
+      }
+
+      // Deserialize the field id.
+      auto id = (*pair_subs)[0].GetScalar<typename corpus_type::key_type>();
+      if (!id.has_value()) {
+        return std::nullopt;
+      }
+
+      // Get information about the field from reflection.
+      const reflection::Field* absl_nullable field = GetFieldById(id.value());
+      if (field == nullptr) {
+        return std::nullopt;
+      }
+
+      if (field->type()->base_type() == reflection::BaseType::UType) {
+        // Union types are handled as part of the union field.
+        continue;
+      }
+
+      // Deserialize the field corpus value.
+      std::optional<GenericDomainCorpusType> inner_parsed;
+      VisitFlatbufferField(schema_, field,
+                           ParseVisitor{Self(), (*pair_subs)[1], inner_parsed});
+      if (!inner_parsed) {
+        return std::nullopt;
+      }
+      out[id.value()] = *std::move(inner_parsed);
+    }
+    return out;
+  }
+
+  // Converts the corpus value to an IRObject.
+  IRObject SerializeCorpus(const corpus_type& value) const {
+    IRObject out;
+    auto& subs = out.MutableSubs();
+    subs.reserve(value.size());
+
+    // Each field is represented by a pair of field id and the serialized
+    // corpus value.
+    for (const auto& [id, field_corpus] : value) {
+      // Get information about the field from reflection.
+      const reflection::Field* absl_nullable field = GetFieldById(id);
+      if (field == nullptr) {
+        continue;
+      }
+      IRObject& pair = subs.emplace_back();
+      auto& pair_subs = pair.MutableSubs();
+      pair_subs.reserve(2);
+
+      // Serialize the field id.
+      pair_subs.emplace_back(field->id());
+
+      // Serialize the field corpus value.
+      VisitFlatbufferField(
+          schema_, field,
+          SerializeVisitor{Self(), field_corpus, pair_subs.emplace_back()});
+    }
+    return out;
+  }
+
+ protected:
+  const reflection::Schema* schema_;
+  const reflection::Object* object_;
+  absl::btree_map<typename corpus_type::key_type, const reflection::Field*>
+      fields_by_id_;
+  mutable absl::Mutex mutex_;
+  mutable absl::flat_hash_map<
+      decltype(static_cast<reflection::Field*>(nullptr)->id()), CopyableAny>
+      domains_ ABSL_GUARDED_BY(mutex_);
+
+  // Helper function to downcast to the derived type
+  Derived& Self() { return static_cast<Derived&>(*this); }
+  const Derived& Self() const { return static_cast<const Derived&>(*this); }
+
+  bool IsSupportedField(const reflection::Field* absl_nonnull field) const {
+    auto base_type = field->type()->base_type();
+    // Union types are handled as part of the union field.
+    if (base_type == reflection::BaseType::UType) return false;
+    if (flatbuffers::IsScalar(base_type)) return true;
+    if (base_type == reflection::BaseType::String) return true;
+    if (base_type == reflection::BaseType::Obj) return true;
+    if (base_type == reflection::BaseType::Union) return true;
+    if (base_type == reflection::BaseType::Vector ||
+        base_type == reflection::BaseType::Vector64) {
+      auto elem_type = field->type()->element();
+      if (flatbuffers::IsScalar(elem_type)) return true;
+      if (elem_type == reflection::BaseType::String) return true;
+      if (elem_type == reflection::BaseType::Obj) return true;
+      if (elem_type == reflection::BaseType::Union) return true;
+    }
+    // TODO: Support Array fields.
+    return false;
+  }
+
+  const reflection::Field* absl_nullable GetFieldById(
+      typename corpus_type::key_type id) const {
+    if (auto it = fields_by_id_.find(id); it != fields_by_id_.end()) {
+      return it->second;
+    }
+    return nullptr;
+  }
+
+  // Returns the domain for the given field.
+  // The domain is cached, and the same instance is returned for the same
+  // field.
+  template <typename T>
+  auto& GetCachedDomain(const reflection::Field* absl_nonnull field) const {
+    using TypedDomainT = decltype(GetDefaultDomain<T>(schema_, field));
+    using DomainT = Domain<value_type_t<TypedDomainT>>;
+    // Do the operation under a lock to prevent race conditions in `const`
+    // methods.
+    absl::MutexLock l(mutex_);
+    auto it = domains_.find(field->id());
+    if (it == domains_.end()) {
+      it = domains_
+               .try_emplace(field->id(), std::in_place_type<DomainT>,
+                            DomainT{GetDefaultDomain<T>(schema_, field)})
+               .first;
+    }
+    return it->second.template GetAs<DomainT>();
+  }
+
+  struct PrinterVisitor {
+    const Derived& derived;
+    const GenericDomainCorpusType& field_corpus;
+    domain_implementor::RawSink sink;
+    domain_implementor::PrintMode mode;
+
+    template <typename T>
+    void Visit(const reflection::Field* absl_nonnull field) {
+      auto& domain = derived.template GetCachedDomain<T>(field);
+      absl::Format(sink, "%s: ", field->name()->c_str());
+      if constexpr (is_flatbuffers_container_of_v<T, uint8_t> ||
+                    is_flatbuffers_container_of_v<
+                        T, FlatbuffersEnumTag<uint8_t>>) {
+        // Handle the case where the field is a vector<uint8_t> or
+        // enum<uint8_t> since the container domain would try to print it as a
+        // string.
+        GenericDomainCorpusType object_corpus;
+        if (field_corpus
+                .Has<std::variant<std::monostate, GenericDomainCorpusType>>()) {
+          auto opt_corpus = field_corpus.GetAs<
+              std::variant<std::monostate, GenericDomainCorpusType>>();
+          if (std::holds_alternative<GenericDomainCorpusType>(opt_corpus)) {
+            object_corpus = std::get<GenericDomainCorpusType>(opt_corpus);
+            absl::Format(sink, "(");
+          } else {
+            absl::Format(sink, "std::nullopt");
+            return;
+          }
+        } else {
+          object_corpus = field_corpus;
+        }
+
+        if constexpr (is_flatbuffers_container_of_v<T, uint8_t>) {
+          auto inner_corpus = object_corpus.GetAs<corpus_type_t<
+              ContainerOfImpl<std::vector<uint8_t>, ArbitraryImpl<uint8_t>>>>();
+          auto inner_domain = Arbitrary<uint8_t>();
+          auto printer = ContainerPrinter<
+              ContainerOfImpl<std::vector<uint8_t>, ArbitraryImpl<uint8_t>>,
+              ArbitraryImpl<uint8_t>>{inner_domain};
+          printer.PrintCorpusValue(inner_corpus, sink, mode);
+        } else {  // container of FlatbuffersEnumTag<uint8_t>
+          auto inner_corpus = object_corpus.GetAs<corpus_type_t<ContainerOfImpl<
+              std::vector<uint8_t>, FlatbuffersEnumDomainImpl<uint8_t>>>>();
+          auto enum_object =
+              derived.schema_->enums()->Get(field->type()->index());
+          auto inner_domain = FlatbuffersEnumDomainImpl<uint8_t>(enum_object);
+          auto printer = ContainerPrinter<
+              ContainerOfImpl<std::vector<uint8_t>,
+                              FlatbuffersEnumDomainImpl<uint8_t>>,
+              FlatbuffersEnumDomainImpl<uint8_t>>{inner_domain};
+          printer.PrintCorpusValue(inner_corpus, sink, mode);
+        }
+
+        if (field_corpus
+                .Has<std::variant<std::monostate, GenericDomainCorpusType>>()) {
+          absl::Format(sink, ")");
+        }
+      } else {
+        domain.GetPrinter().PrintCorpusValue(field_corpus, sink, mode);
+      }
+    }
+  };
+
+  struct Printer {
+    const Derived& derived;
+
+    void PrintCorpusValue(const corpus_type& value,
+                          domain_implementor::RawSink out,
+                          domain_implementor::PrintMode mode) const {
+      std::vector<typename corpus_type::key_type> field_ids;
+      field_ids.reserve(value.size());
+      for (const auto& [id, _] : value) {
+        field_ids.push_back(id);
+      }
+      // Sort the field ids to make the output deterministic.
+      std::sort(field_ids.begin(), field_ids.end());
+
+      absl::Format(out, "{");
+      bool first = true;
+      for (const auto id : field_ids) {
+        if (!first) {
+          absl::Format(out, ", ");
+        }
+        const reflection::Field* absl_nullable field = derived.GetFieldById(id);
+        if (field == nullptr) {
+          absl::Format(out, "<unknown field: %d>", id);
+        } else {
+          VisitFlatbufferField(
+              derived.schema_, field,
+              PrinterVisitor{derived, value.at(id), out, mode});
+        }
+        first = false;
+      }
+      absl::Format(out, "}");
+    }
+  };
+
+  struct InitializeVisitor {
+    const Derived& derived;
+    absl::BitGenRef prng;
+    corpus_type& corpus;
+
+    template <typename T>
+    void Visit(const reflection::Field* absl_nonnull field) {
+      auto& domain = derived.template GetCachedDomain<T>(field);
+      corpus[field->id()] = domain.Init(prng);
+    }
+  };
+
+  struct CountNumberOfMutableFieldsVisitor {
+    const Derived& derived;
+    uint64_t& field_count;
+    corpus_type& corpus_value;
+    const bool only_shrink = false;
+
+    template <typename T>
+    void Visit(const reflection::Field* absl_nonnull field) {
+      if (!derived.IsSupportedField(field)) return;
+      auto it = corpus_value.find(field->id());
+      if (only_shrink && it == corpus_value.end()) return;
+
+      field_count++;
+
+      if constexpr (std::is_same_v<T, FlatbuffersTableTag> ||
+                    std::is_same_v<T, FlatbuffersStructTag> ||
+                    std::is_same_v<T, FlatbuffersUnionTag> ||
+                    is_flatbuffers_container_of_v<T, FlatbuffersTableTag> ||
+                    is_flatbuffers_container_of_v<T, FlatbuffersStructTag> ||
+                    is_flatbuffers_container_of_v<T, FlatbuffersUnionTag>) {
+        if (it == corpus_value.end()) return;
+        auto& domain = derived.template GetCachedDomain<T>(field);
+        // Count the fields in the corpus for domains that support it.
+        field_count += domain.CountNumberOfFields(it->second);
+      }
+    }
+  };
+
+  struct MutateSelectedFieldVisitor {
+    const Derived& derived;
+    absl::BitGenRef prng;
+    const domain_implementor::MutationMetadata& metadata;
+    const uint64_t selected_field_index;
+    const bool only_shrink;
+    corpus_type& corpus_value;
+    uint64_t& field_counter;
+
+    template <typename T>
+    void Visit(const reflection::Field* absl_nonnull field) {
+      if (!derived.IsSupportedField(field)) return;
+      auto it = corpus_value.find(field->id());
+      if (only_shrink && it == corpus_value.end()) return;
+
+      field_counter++;
+      auto& domain = derived.template GetCachedDomain<T>(field);
+      if (field_counter == selected_field_index) {
+        if (it == corpus_value.end()) {
+          it = corpus_value.try_emplace(field->id(), domain.Init(prng)).first;
+        }
+        domain.Mutate(it->second, prng, metadata, only_shrink);
+        return;
+      }
+
+      if constexpr (std::is_same_v<T, FlatbuffersTableTag> ||
+                    std::is_same_v<T, FlatbuffersStructTag> ||
+                    std::is_same_v<T, FlatbuffersUnionTag> ||
+                    is_flatbuffers_container_of_v<T, FlatbuffersTableTag> ||
+                    is_flatbuffers_container_of_v<T, FlatbuffersStructTag> ||
+                    is_flatbuffers_container_of_v<T, FlatbuffersUnionTag>) {
+        if (it == corpus_value.end()) return;
+        field_counter +=
+            domain.MutateSelectedField(it->second, prng, metadata, only_shrink,
+                                       selected_field_index - field_counter);
+      }
+    }
+  };
+
+  struct ParseVisitor {
+    const Derived& derived;
+    const IRObject& ir_object;
+    std::optional<GenericDomainCorpusType>& corpus;
+
+    template <typename T>
+    void Visit(const reflection::Field* absl_nonnull field) {
+      auto& domain = derived.template GetCachedDomain<T>(field);
+      corpus = domain.ParseCorpus(ir_object);
+    }
+  };
+
+  struct SerializeVisitor {
+    const Derived& derived;
+    const GenericDomainCorpusType& corpus;
+    IRObject& ir_object;
+
+    template <typename T>
+    void Visit(const reflection::Field* absl_nonnull field) {
+      auto& domain = derived.template GetCachedDomain<T>(field);
+      ir_object = domain.SerializeCorpus(corpus);
+    }
+  };
+
+  struct ValidateVisitor {
+    const Derived& derived;
+    const GenericDomainCorpusType& inner_corpus;
+    absl::Status& status;
+
+    template <typename T>
+    void Visit(const reflection::Field* absl_nonnull field) {
+      auto& domain = derived.template GetCachedDomain<T>(field);
+      status = domain.ValidateCorpusValue(inner_corpus);
+      if (!status.ok()) {
+        status = Prefix(status, absl::StrCat("Invalid value for field ",
+                                             field->name()->c_str()));
+      }
+    }
+  };
+};
+
+// Domain implementation for flatbuffers struct types.
+// The corpus type is a map of field ids to field values.
+class FlatbuffersStructUntypedDomainImpl
+    : public FlatbuffersUntypedObjectDomainBase<
+          /*Derived=*/FlatbuffersStructUntypedDomainImpl,
+          /*ValueType=*/const flatbuffers::Struct*> {
+ public:
+  template <typename Derived, typename ValueType>
+  friend class FlatbuffersUntypedObjectDomainBase;
+
+  using typename FlatbuffersStructUntypedDomainImpl::DomainBase::corpus_type;
+  using typename FlatbuffersStructUntypedDomainImpl::DomainBase::value_type;
+
+  explicit FlatbuffersStructUntypedDomainImpl(
+      const reflection::Schema* absl_nonnull schema,
+      const reflection::Object* absl_nonnull struct_object)
+      : FlatbuffersUntypedObjectDomainBase(schema, struct_object) {
+    FUZZTEST_CHECK(struct_object->is_struct())
+        << "Object must be a struct type.";
+  }
+
+  // Converts the struct pointer to a corpus value.
+  std::optional<corpus_type> FromValue(const value_type& value) const;
+
+  // Builds the struct in a builder.
+  std::optional<flatbuffers::uoffset_t> BuildValue(
+      const corpus_type& value,
+      flatbuffers::FlatBufferBuilder64& builder) const;
+
+  // Builds the struct in a buffer.
+  void BuildValue(const corpus_type& value, uint8_t* buf) const;
+
+ private:
+  struct FromValueVisitor {
+    const FlatbuffersStructUntypedDomainImpl& self;
+    value_type value;
+    corpus_type& out;
+
+    template <typename T>
+    void Visit(const reflection::Field* absl_nonnull field) {
+      [[maybe_unused]]
+      reflection::BaseType base_type = field->type()->base_type();
+      auto& domain = self.GetCachedDomain<T>(field);
+      std::optional<corpus_type_t<std::decay_t<decltype(domain)>>> inner_corpus;
+
+      if constexpr (is_flatbuffers_enum_tag_v<T>) {
+        auto inner_value = value->GetField<typename T::type>(field->offset());
+        inner_corpus = domain.FromValue(inner_value);
+      } else if constexpr (std::is_integral_v<T> ||
+                           std::is_floating_point_v<T>) {
+        auto inner_value = value->GetField<T>(field->offset());
+        inner_corpus = domain.FromValue(inner_value);
+      } else if constexpr (std::is_same_v<T, FlatbuffersStructTag>) {
+        auto inner_value =
+            value->GetStruct<const flatbuffers::Struct*>(field->offset());
+        inner_corpus = domain.FromValue(inner_value);
+      }
+
+      if (inner_corpus.has_value()) {
+        out[field->id()] = std::move(*inner_corpus);
+      }
+    };
+  };
+
+  struct BuildValueVisitor {
+    const FlatbuffersStructUntypedDomainImpl& self;
+    const corpus_type& corpus_value;
+    uint8_t* struct_ptr;
+
+    template <typename T>
+    void Visit(const reflection::Field* absl_nonnull field) {
+      [[maybe_unused]]
+      reflection::BaseType base_type = field->type()->base_type();
+      auto& domain = self.GetCachedDomain<T>(field);
+      if constexpr (is_flatbuffers_enum_tag_v<T> || std::is_integral_v<T> ||
+                    std::is_floating_point_v<T>) {
+        auto inner_value = domain.GetValue(corpus_value.at(field->id()));
+        if constexpr (is_flatbuffers_enum_tag_v<T>) {
+          flatbuffers::WriteScalar<typename T::type>(
+              struct_ptr + field->offset(), inner_value);
+        } else {
+          flatbuffers::WriteScalar<T>(struct_ptr + field->offset(),
+                                      inner_value);
+        }
+      } else if constexpr (std::is_same_v<T, FlatbuffersStructTag>) {
+        auto sub_object = self.schema_->objects()->Get(field->type()->index());
+        auto inner_corpus_value =
+            corpus_value.at(field->id())
+                .GetAs<FlatbuffersStructUntypedDomainImpl::corpus_type>();
+        FlatbuffersStructUntypedDomainImpl sub_domain(self.schema_, sub_object);
+        for (const auto& [_, nested_field] : sub_domain.fields_by_id_) {
+          VisitFlatbufferField(sub_domain.schema_, nested_field,
+                               BuildValueVisitor{sub_domain, inner_corpus_value,
+                                                 struct_ptr + field->offset()});
+        }
+      } else if constexpr (std::is_same_v<T, FlatbuffersArrayTag>) {
+        // TODO (b/405938558): Implement array support.
+      }
+    }
+  };
+};
+
+// From flatbuffers documentation:
+// Unions are encoded as the combination of two fields: an enum representing the
+// union choice and the offset to the actual element.
+// The type of the enum is always uint8_t as generated by the flatbuffers
+// compiler.
+using FlatbuffersUnionTypeDomainImpl = FlatbuffersEnumDomainImpl<uint8_t>;
+
+// Union domain corpus type.
+struct FlatbuffersUnionDomainCorpusType {
+  using type_type = typename FlatbuffersUnionTypeDomainImpl::corpus_type;
+  using value_type = GenericDomainCorpusType;
+
+  type_type type;
+  value_type value;
+};
+
+// Union domain value type.
+struct FlatbuffersUnionDomainValueType {
+  using type_type = typename FlatbuffersUnionTypeDomainImpl::value_type;
+  using value_type = const void*;
+
+  type_type type;
+  value_type value;
+};
+
+// Flatbuffers union domain implementation.
+class FlatbuffersUnionDomainImpl
+    : public domain_implementor::DomainBase<
+          /*Derived=*/FlatbuffersUnionDomainImpl,
+          /*ValueType=*/FlatbuffersUnionDomainValueType,
+          /*CorpusType=*/FlatbuffersUnionDomainCorpusType> {
+ public:
+  friend class FlatbuffersTableUntypedDomainImpl;
+
+  using typename FlatbuffersUnionDomainImpl::DomainBase::corpus_type;
+  using typename FlatbuffersUnionDomainImpl::DomainBase::value_type;
+
+  FlatbuffersUnionDomainImpl(const reflection::Schema* schema,
+                             const reflection::Enum* union_def);
+
+  FlatbuffersUnionDomainImpl(const FlatbuffersUnionDomainImpl& other);
+  FlatbuffersUnionDomainImpl(FlatbuffersUnionDomainImpl&& other);
+  FlatbuffersUnionDomainImpl& operator=(
+      const FlatbuffersUnionDomainImpl& other);
+  FlatbuffersUnionDomainImpl& operator=(FlatbuffersUnionDomainImpl&& other);
+
+  // Initializes the corpus value.
+  corpus_type Init(absl::BitGenRef prng);
+
+  // Mutates the corpus value.
+  void Mutate(corpus_type& corpus_value, absl::BitGenRef prng,
+              const domain_implementor::MutationMetadata& metadata,
+              bool only_shrink);
+
+  uint64_t CountNumberOfFields(corpus_type& corpus_value);
+
+  uint64_t MutateSelectedField(
+      corpus_type& corpus_value, absl::BitGenRef prng,
+      const domain_implementor::MutationMetadata& metadata, bool only_shrink,
       uint64_t selected_field_index);
 
   auto GetPrinter() const { return Printer{*this}; }
 
   absl::Status ValidateCorpusValue(const corpus_type& corpus_value) const;
 
-  value_type GetValue(const corpus_type& value) const {
-    FUZZTEST_LOG(FATAL)
-        << "GetValue is not supported for the untyped Flatbuffers domain.";
-    // Untyped domain does not support GetValue since if it is a nested table it
-    // would need the top level table corpus value to be able to build it.
-    return nullptr;
+  // UNSUPPORTED: Flatbuffers unions user values are not supported.
+  value_type GetValue(const corpus_type& corpus_value) const {
+    FUZZTEST_LOG(FATAL) << "GetValue is not supported for unions.";
   }
 
-  // Converts the table pointer to a corpus value.
+  // Gets the type of the union field.
+  auto GetType(const corpus_type& corpus_value) const {
+    return type_domain_.GetValue(corpus_value.type);
+  }
+
+  // Converts the value to a corpus value.
   std::optional<corpus_type> FromValue(const value_type& value) const;
 
   // Converts the IRObject to a corpus value.
   std::optional<corpus_type> ParseCorpus(const IRObject& obj) const;
 
   // Converts the corpus value to an IRObject.
-  IRObject SerializeCorpus(const corpus_type& value) const;
+  IRObject SerializeCorpus(const corpus_type& corpus_value) const;
 
  private:
-  const reflection::Schema* absl_nonnull schema_;
-  const reflection::Object* absl_nonnull table_object_;
+  const reflection::Schema* schema_;
+  const reflection::Enum* union_def_;
+  FlatbuffersEnumDomainImpl<typename FlatbuffersUnionTypeDomainImpl::value_type>
+      type_domain_;
   mutable absl::Mutex mutex_;
-  mutable absl::flat_hash_map<typename corpus_type::key_type, CopyableAny>
+  mutable absl::flat_hash_map<
+      typename FlatbuffersUnionTypeDomainImpl::value_type, CopyableAny>
       domains_ ABSL_GUARDED_BY(mutex_);
 
-  bool IsSupportedField(const reflection::Field* absl_nonnull field) const;
+  // Creates flatbuffer from the corpus value.
+  std::optional<flatbuffers::uoffset_t> BuildValue(
+      const corpus_type& corpus_value,
+      flatbuffers::FlatBufferBuilder64& builder) const;
 
+  // Returns the domain for the given enum value.
+  template <typename T>
+  auto& GetCachedDomain(const reflection::EnumVal& enum_value) const {
+    using DomainT = decltype(GetDefaultDomainForType<T>(enum_value));
+    absl::MutexLock l(mutex_);
+    auto [it, inserted] =
+        domains_.try_emplace(enum_value.value(), std::in_place_type<DomainT>,
+                             GetDefaultDomainForType<T>(enum_value));
+    return it->second.template GetAs<DomainT>();
+  }
+
+  // Creates new or returns existing domain for the given enum value.
+  template <typename T>
+  auto GetDefaultDomainForType(const reflection::EnumVal& enum_value) const;
+
+  struct Printer {
+    const FlatbuffersUnionDomainImpl& self;
+
+    void PrintCorpusValue(const corpus_type& value,
+                          domain_implementor::RawSink out,
+                          domain_implementor::PrintMode mode) const;
+  };
+};
+
+// Domain implementation for flatbuffers untyped tables.
+// The corpus type is a map of field ids to field values.
+class FlatbuffersTableUntypedDomainImpl
+    : public FlatbuffersUntypedObjectDomainBase<
+          /*Derived=*/FlatbuffersTableUntypedDomainImpl,
+          /*ValueType=*/const flatbuffers::Table*> {
+ public:
+  template <typename Derived, typename ValueType>
+  friend class FlatbuffersUntypedObjectDomainBase;
+  template <typename T>
+  friend class FlatbuffersTableDomainImpl;
+  friend class FlatbuffersUnionDomainImpl;
+
+  using typename FlatbuffersTableUntypedDomainImpl::DomainBase::corpus_type;
+  using typename FlatbuffersTableUntypedDomainImpl::DomainBase::value_type;
+
+  explicit FlatbuffersTableUntypedDomainImpl(
+      const reflection::Schema* absl_nonnull schema,
+      const reflection::Object* absl_nonnull table_object)
+      : FlatbuffersUntypedObjectDomainBase(schema, table_object) {}
+
+  // Converts the table pointer to a corpus value.
+  std::optional<corpus_type> FromValue(const value_type& value) const;
+
+ private:
   uint32_t BuildTable(const corpus_type& value,
                       flatbuffers::FlatBufferBuilder64& builder) const;
 
   // Returns the domain for the given field.
-  // The domain is cached, and the same instance is returned for the same field.
+  // The domain is cached, and the same instance is returned for the same
+  // field.
   template <typename T>
-  auto& GetCachedDomain(const reflection::Field* field) const {
+  auto& GetCachedDomain(const reflection::Field* absl_nonnull field) const {
     auto get_optional_domain = [this, field]() {
-      auto optional_domain = OptionalOf(GetDefaultDomain<T>(schema_, field));
-      if (!field->optional()) {
+      auto inner_domain = GetDefaultDomain<T>(schema_, field);
+      auto optional_domain = OptionalOf(inner_domain);
+      if (!field->optional() || field->required()) {
         optional_domain.SetWithoutNull();
       }
+      if constexpr (std::is_same_v<T, FlatbuffersUnionTag>) {
+        auto union_type = schema_->enums()->Get(field->type()->index());
+        // If the union has only one type (NONE), we can always return null.
+        if (union_type->values()->size() == 1) {
+          optional_domain.SetAlwaysNull();
+        }
+      }
       return Domain<value_type_t<decltype(optional_domain)>>{optional_domain};
     };
 
@@ -394,36 +1199,18 @@
     return it->second.template GetAs<DomainT>();
   }
 
-  const reflection::Field* absl_nullable GetFieldById(
-      typename corpus_type::key_type id) const {
-    const auto it =
-        absl::c_find_if(*table_object_->fields(),
-                        [id](const auto* field) { return field->id() == id; });
-    return it != table_object_->fields()->end() ? *it : nullptr;
-  }
-
-  struct SerializeVisitor {
-    const FlatbuffersTableUntypedDomainImpl& self;
-    const GenericDomainCorpusType& corpus_value;
-    IRObject& out;
-
-    template <typename T>
-    void Visit(const reflection::Field* absl_nonnull field) {
-      out = self.GetCachedDomain<T>(field).SerializeCorpus(corpus_value);
-    }
-  };
-
   struct FromValueVisitor {
     const FlatbuffersTableUntypedDomainImpl& self;
     value_type user_value;
     corpus_type& corpus_value;
 
     template <typename T>
-    void Visit(const reflection::Field* absl_nonnull field) const {
+    void Visit(const reflection::Field* absl_nonnull field) {
       [[maybe_unused]]
       reflection::BaseType base_type = field->type()->base_type();
       auto& domain = self.GetCachedDomain<T>(field);
-      value_type_t<std::decay_t<decltype(domain)>> inner_value;
+      using InnerDomain = std::decay_t<decltype(domain)>;
+      value_type_t<InnerDomain> inner_value;
 
       if constexpr (is_flatbuffers_enum_tag_v<T>) {
         if (!field->optional() || user_value->CheckField(field->offset())) {
@@ -459,13 +1246,139 @@
             << "Field must be a table type.";
         inner_value =
             user_value->GetPointer<const flatbuffers::Table*>(field->offset());
+      } else if constexpr (std::is_same_v<T, FlatbuffersStructTag>) {
+        inner_value =
+            user_value->GetStruct<const flatbuffers::Struct*>(field->offset());
+      } else if constexpr (is_any_flatbuffers_vector_tag_v<T>) {
+        using ElementType = typename T::value_type;
+        if (user_value->CheckField(field->offset())) {
+          inner_value = typename value_type_t<InnerDomain>::value_type{};
+          VisitVector<ElementType, std::decay_t<decltype(domain)>,
+                      flatbuffers_vector_tag_offset_t<T>>(field, inner_value);
+        }
+      } else if constexpr (std::is_same_v<T, FlatbuffersUnionTag>) {
+        constexpr char kUnionTypeFieldSuffix[] = "_type";
+        auto enumdef = self.schema_->enums()->Get(field->type()->index());
+        auto type_field = self.object_->fields()->LookupByKey(
+            absl::StrCat(field->name()->c_str(), kUnionTypeFieldSuffix)
+                .c_str());
+        if (type_field == nullptr) {
+          return;
+        }
+        auto union_type =
+            user_value->GetField<uint8_t>(type_field->offset(), 0);
+        if (union_type > 0 /* NONE */) {
+          auto enumval = enumdef->values()->LookupByKey(union_type);
+          auto union_object =
+              self.schema_->objects()->Get(enumval->union_type()->index());
+          if (union_object->is_struct()) {
+            auto union_value =
+                user_value->template GetPointer<flatbuffers::Struct*>(
+                    field->offset());
+            inner_value =
+                FlatbuffersUnionDomainValueType{union_type, union_value};
+          } else {
+            auto union_value =
+                user_value->GetPointer<flatbuffers::Table*>(field->offset());
+            inner_value =
+                FlatbuffersUnionDomainValueType{union_type, union_value};
+          }
+        }
       }
 
-      auto inner = domain.FromValue(inner_value);
-      if (inner) {
-        corpus_value[field->id()] = *std::move(inner);
+      if (inner_value) {
+        auto inner = domain.FromValue(inner_value);
+        if (inner) {
+          corpus_value[field->id()] = *std::move(inner);
+        }
       }
+    }
+
+    // Helper to get the flatbuffers vector type.
+    template <typename Element, typename Offset>
+    struct FlatbuffersVectorType {
+     private:
+      static_assert(std::is_same_v<Offset, flatbuffers::uoffset_t> ||
+                        std::is_same_v<Offset, flatbuffers::uoffset64_t>,
+                    "Offset must be uoffset_t or uoffset64_t.");
+      static_assert(std::is_arithmetic_v<Element> ||
+                        is_flatbuffers_enum_tag_v<Element> ||
+                        std::is_same_v<Element, std::string> ||
+                        std::is_same_v<Element, FlatbuffersTableTag> ||
+                        std::is_same_v<Element, FlatbuffersStructTag> ||
+                        std::is_same_v<Element, FlatbuffersUnionTag>,
+                    "Unsupported vector element type.");
+
+      static constexpr auto get_flatbuffers_type_pointer() {
+        if constexpr (std::is_integral_v<Element> ||
+                      std::is_floating_point_v<Element>) {
+          return static_cast<flatbuffers::Vector<Element, Offset>*>(nullptr);
+        } else if constexpr (is_flatbuffers_enum_tag_v<Element>) {
+          return static_cast<
+              flatbuffers::Vector<typename Element::type, Offset>*>(nullptr);
+        } else if constexpr (std::is_same_v<Element, std::string>) {
+          return static_cast<flatbuffers::Vector<
+              flatbuffers::Offset<flatbuffers::String>, Offset>*>(nullptr);
+        } else if constexpr (std::is_same_v<Element, FlatbuffersTableTag>) {
+          return static_cast<flatbuffers::Vector<
+              flatbuffers::Offset<flatbuffers::Table>, Offset>*>(nullptr);
+        } else if constexpr (std::is_same_v<Element, FlatbuffersStructTag>) {
+          // Struct vector are serialized inline and accessed as bytes.
+          return static_cast<const flatbuffers::Vector<uint8_t>*>(nullptr);
+        } else if constexpr (std::is_same_v<Element, FlatbuffersUnionTag>) {
+          return static_cast<flatbuffers::Vector<flatbuffers::Offset<void>>*>(
+              nullptr);
+        }
+      }
+
+     public:
+      using type =
+          std::remove_pointer_t<decltype(get_flatbuffers_type_pointer())>;
     };
+
+    template <typename Element, typename Domain, typename Offset>
+    void VisitVector(const reflection::Field* field,
+                     value_type_t<Domain>& vector_corpus) const {
+      using FlatbuffersVector =
+          typename FlatbuffersVectorType<Element, Offset>::type;
+      const FlatbuffersVector* vec;
+      if constexpr (std::is_same_v<Offset, flatbuffers::uoffset64_t>) {
+        vec =
+            user_value->GetPointer64<const FlatbuffersVector*>(field->offset());
+      } else {
+        vec = user_value->GetPointer<const FlatbuffersVector*>(field->offset());
+      }
+      vector_corpus->reserve(vec->size());
+      if constexpr (std::is_same_v<Element, FlatbuffersUnionTag>) {
+        constexpr char kUnionTypeFieldSuffix[] = "_type";
+        auto type_field = self.object_->fields()->LookupByKey(
+            absl::StrCat(field->name()->c_str(), kUnionTypeFieldSuffix)
+                .c_str());
+        FUZZTEST_CHECK(type_field != nullptr) << "Union type field not found.";
+        const auto* type_vec =
+            user_value->GetPointer<const flatbuffers::Vector<uint8_t>*>(
+                type_field->offset());
+        for (decltype(vec->size()) i = 0; i < vec->size(); ++i) {
+          vector_corpus->push_back({type_vec->Get(i), vec->Get(i)});
+        }
+      } else {
+        for (decltype(vec->size()) i = 0; i < vec->size(); ++i) {
+          if constexpr (std::is_same_v<Element, std::string>) {
+            vector_corpus->push_back(vec->Get(i)->str());
+          } else if constexpr (std::is_same_v<Element, FlatbuffersStructTag>) {
+            const reflection::Object* object_def =
+                self.schema_->objects()->Get(field->type()->index());
+            // Struct vector are serialized inline.
+            const uint8_t* struct_data_ptr =
+                vec->Data() + i * object_def->bytesize();
+            vector_corpus->push_back(
+                reinterpret_cast<const flatbuffers::Struct*>(struct_data_ptr));
+          } else {
+            vector_corpus->push_back(vec->Get(i));
+          }
+        }
+      }
+    }
   };
 
   // Create out-of-line table fields, see `BuildTable` for details.
@@ -477,7 +1390,7 @@
     const typename corpus_type::mapped_type& corpus_value;
 
     template <typename T>
-    void Visit(const reflection::Field* absl_nonnull field) const {
+    void Visit(const reflection::Field* absl_nonnull field) {
       if constexpr (std::is_same_v<T, std::string>) {
         auto& domain = self.GetCachedDomain<T>(field);
         auto user_value = domain.GetValue(corpus_value);
@@ -497,24 +1410,162 @@
       } else if constexpr (std::is_same_v<T, FlatbuffersTableTag>) {
         FlatbuffersTableUntypedDomainImpl inner_domain(
             self.schema_, self.schema_->objects()->Get(field->type()->index()));
-        auto optional_corpus = corpus_value.GetAs<
-            std::variant<std::monostate, fuzztest::GenericDomainCorpusType>>();
-        if (std::holds_alternative<fuzztest::GenericDomainCorpusType>(
-                optional_corpus)) {
-          auto inner_corpus =
-              std::get<fuzztest::GenericDomainCorpusType>(optional_corpus)
-                  .GetAs<corpus_type>();
+        auto optional_corpus =
+            corpus_value
+                .GetAs<std::variant<std::monostate, GenericDomainCorpusType>>();
+        if (std::holds_alternative<GenericDomainCorpusType>(optional_corpus)) {
+          auto inner_corpus = std::get<GenericDomainCorpusType>(optional_corpus)
+                                  .GetAs<corpus_type>();
           auto offset = inner_domain.BuildTable(inner_corpus, builder);
           offsets.insert({field->id(), offset});
         }
         // Else if the variant is std::monostate the optional field is null and
         // there is no table to build.
+      } else if constexpr (is_any_flatbuffers_vector_tag_v<T>) {
+        VisitVector<typename T::value_type, flatbuffers_vector_tag_offset_t<T>>(
+            field, self.GetCachedDomain<T>(field));
+      } else if constexpr (std::is_same_v<T, FlatbuffersUnionTag>) {
+        const reflection::Enum* union_type =
+            self.schema_->enums()->Get(field->type()->index());
+        FlatbuffersUnionDomainImpl inner_domain{self.schema_, union_type};
+        auto opt_corpus =
+            corpus_value
+                .GetAs<std::variant<std::monostate, GenericDomainCorpusType>>();
+        if (std::holds_alternative<GenericDomainCorpusType>(opt_corpus)) {
+          auto inner_corpus =
+              std::get<GenericDomainCorpusType>(opt_corpus)
+                  .GetAs<corpus_type_t<decltype(inner_domain)>>();
+          auto offset = inner_domain.BuildValue(inner_corpus, builder);
+          if (offset.has_value()) {
+            offsets.insert({field->id(), *offset});
+          }
+        }
+      }
+    }
+
+   private:
+    template <typename Element, typename Offset,
+              int&... ExplicitArgumentBarrier, typename Domain>
+    void VisitVector(const reflection::Field* field,
+                     const Domain& domain) const {
+      if constexpr (std::is_integral_v<Element> ||
+                    std::is_floating_point_v<Element> ||
+                    is_flatbuffers_enum_tag_v<Element>) {
+        auto value = domain.GetValue(corpus_value);
+        if (!value) {
+          if (field->optional()) {
+            return;
+          }
+          // Handle case where value is std::nullopt but field is required.
+          // Create an empty vector of the appropriate type.
+          value = typename decltype(value)::value_type{};
+        }
+        if constexpr (std::is_same_v<Offset, flatbuffers::uoffset_t>) {
+          offsets.insert({field->id(), builder.CreateVector(*value).o});
+        } else {
+          if constexpr (std::is_same_v<Element, bool>) {
+            // Workaround for missing overload for CreateVector64(const
+            // std::vector<T>&)
+            builder.StartVector<uint8_t, flatbuffers::Offset64>(value->size());
+            for (auto i = value->size(); i > 0;) {
+              builder.PushElement(static_cast<uint8_t>(value->at(--i)));
+            }
+            auto offset =
+                builder.EndVector<flatbuffers::uoffset64_t,
+                                  flatbuffers::uoffset64_t>(value->size());
+            offsets.insert({field->id(), offset});
+          } else {
+            offsets.insert({field->id(), builder.CreateVector64(*value).o});
+          }
+        }
+      } else if constexpr (std::is_same_v<Element, FlatbuffersTableTag> ||
+                           std::is_same_v<Element, FlatbuffersStructTag>) {
+        auto opt_corpus = corpus_value.template GetAs<
+            std::variant<std::monostate, GenericDomainCorpusType>>();
+        if (std::holds_alternative<std::monostate>(opt_corpus)) {
+          return;
+        }
+        auto container_corpus = std::get<GenericDomainCorpusType>(opt_corpus)
+                                    .GetAs<std::list<corpus_type>>();
+        auto sub_object = self.schema_->objects()->Get(field->type()->index());
+        if constexpr (std::is_same_v<Element, FlatbuffersTableTag>) {
+          std::vector<flatbuffers::Offset<flatbuffers::Table>> vec_offsets;
+          FlatbuffersTableUntypedDomainImpl domain(self.schema_, sub_object);
+          vec_offsets.reserve(container_corpus.size());
+          for (auto& inner_corpus : container_corpus) {
+            auto offset = domain.BuildTable(inner_corpus, builder);
+            vec_offsets.push_back(offset);
+          }
+          offsets.insert({field->id(), builder.CreateVector(vec_offsets).o});
+        } else {
+          uint8_t* vec_ptr = nullptr;
+          FlatbuffersStructUntypedDomainImpl inner_domain(self.schema_,
+                                                          sub_object);
+          auto vec_offset = builder.CreateUninitializedVector(
+              container_corpus.size(), sub_object->bytesize(),
+              sub_object->minalign(), &vec_ptr);
+          size_t i = 0;
+          for (const auto& inner_corpus : container_corpus) {
+            uint8_t* current_struct_ptr = vec_ptr + i * sub_object->bytesize();
+            inner_domain.BuildValue(inner_corpus, current_struct_ptr);
+            ++i;
+          }
+          offsets.insert({field->id(), vec_offset});
+        }
+      } else if constexpr (std::is_same_v<Element, std::string>) {
+        auto value = domain.GetValue(corpus_value);
+        if (!value) {
+          return;
+        }
+        std::vector<flatbuffers::Offset<flatbuffers::String>> vec_offsets;
+        vec_offsets.reserve(value->size());
+        for (const auto& str : *value) {
+          auto offset = builder.CreateString(str);
+          vec_offsets.push_back(offset);
+        }
+        offsets.insert({field->id(), builder.CreateVector(vec_offsets).o});
+      } else if constexpr (std::is_same_v<Element, FlatbuffersUnionTag>) {
+        const reflection::Enum* union_type =
+            self.schema_->enums()->Get(field->type()->index());
+        FlatbuffersUnionDomainImpl domain{self.schema_, union_type};
+        constexpr char kUnionTypeFieldSuffix[] = "_type";
+        const reflection::Field* type_field =
+            self.object_->fields()->LookupByKey(
+                absl::StrCat(field->name()->c_str(), kUnionTypeFieldSuffix)
+                    .c_str());
+
+        auto opt_corpus =
+            corpus_value
+                .GetAs<std::variant<std::monostate, GenericDomainCorpusType>>();
+        if (std::holds_alternative<std::monostate>(opt_corpus)) {
+          return;
+        }
+        FlatbuffersUnionDomainImpl inner_domain{self.schema_, union_type};
+        auto container_corpus =
+            std::get<GenericDomainCorpusType>(opt_corpus)
+                .GetAs<std::list<corpus_type_t<decltype(inner_domain)>>>();
+
+        std::vector<typename value_type_t<
+            std::decay_t<decltype(inner_domain)>>::type_type>
+            vec_types;
+        vec_types.reserve(container_corpus.size());
+        std::vector<flatbuffers::Offset<flatbuffers::Table>> vec_offsets;
+        vec_offsets.reserve(container_corpus.size());
+        for (auto& inner_corpus : container_corpus) {
+          auto offset = inner_domain.BuildValue(inner_corpus, builder);
+          if (offset.has_value()) {
+            vec_offsets.push_back(*offset);
+            vec_types.push_back(inner_domain.GetType(inner_corpus));
+          }
+        }
+        offsets.insert({field->id(), builder.CreateVector(vec_offsets).o});
+        offsets.insert({type_field->id(), builder.CreateVector(vec_types).o});
       }
     }
   };
 
-  // Create complete table: store "inline fields" values inline, and store just
-  // offsets for "out-of-line fields". See `BuildTable` for details.
+  // Create complete table: store "inline fields" values inline, and store
+  // just offsets for "out-of-line fields". See `BuildTable` for details.
   struct TableBuilderVisitor {
     const FlatbuffersTableUntypedDomainImpl& self;
     flatbuffers::FlatBufferBuilder64& builder;
@@ -523,7 +1574,7 @@
     const typename corpus_type::value_type::second_type& corpus_value;
 
     template <typename T>
-    void Visit(const reflection::Field* absl_nonnull field) const {
+    void Visit(const reflection::Field* absl_nonnull field) {
       if constexpr (std::is_integral_v<T> || std::is_floating_point_v<T> ||
                     is_flatbuffers_enum_tag_v<T>) {
         auto& domain = self.GetCachedDomain<T>(field);
@@ -533,17 +1584,27 @@
         }
         // Store "inline field" value inline.
         builder.AddElement(field->offset(), v.value());
-      } else if constexpr (std::is_same_v<T, std::string>) {
+      } else if constexpr (std::is_same_v<T, std::string> ||
+                           is_any_flatbuffers_vector_tag_v<T>) {
         // "Out-of-line field". Store just offset.
+        if constexpr (is_flatbuffers_container_of_v<T, FlatbuffersUnionTag>) {
+          constexpr char kUnionTypeFieldSuffix[] = "_type";
+          const reflection::Field* type_field =
+              self.object_->fields()->LookupByKey(
+                  absl::StrCat(field->name()->c_str(), kUnionTypeFieldSuffix)
+                      .c_str());
+          if (auto it = offsets.find(type_field->id()); it != offsets.end()) {
+            builder.AddOffset(type_field->offset(),
+                              flatbuffers::Offset<>(it->second));
+          }
+        }
         if (auto it = offsets.find(field->id()); it != offsets.end()) {
           if (field->offset64()) {
-            builder.AddOffset(
-                field->offset(),
-                flatbuffers::Offset64<flatbuffers::String>(it->second));
+            builder.AddOffset(field->offset(),
+                              flatbuffers::Offset64<>(it->second));
           } else {
-            builder.AddOffset(
-                field->offset(),
-                flatbuffers::Offset<flatbuffers::String>(it->second));
+            builder.AddOffset(field->offset(),
+                              flatbuffers::Offset<>(it->second));
           }
         }
       } else if constexpr (std::is_same_v<T, FlatbuffersTableTag>) {
@@ -553,134 +1614,49 @@
               field->offset(),
               flatbuffers::Offset<flatbuffers::Table>(it->second));
         }
-      }
-    }
-  };
-
-  struct ParseVisitor {
-    const FlatbuffersTableUntypedDomainImpl& self;
-    const IRObject& obj;
-    std::optional<GenericDomainCorpusType>& out;
-
-    template <typename T>
-    void Visit(const reflection::Field* absl_nonnull field) {
-      out = self.GetCachedDomain<T>(field).ParseCorpus(obj);
-    }
-  };
-
-  struct ValidateVisitor {
-    const FlatbuffersTableUntypedDomainImpl& self;
-    const GenericDomainCorpusType& corpus_value;
-    absl::Status& out;
-
-    template <typename T>
-    void Visit(const reflection::Field* absl_nonnull field) {
-      auto& domain = self.GetCachedDomain<T>(field);
-      out = domain.ValidateCorpusValue(corpus_value);
-      if (!out.ok()) {
-        out = Prefix(out, absl::StrCat("Invalid value for field ",
-                                       field->name()->str()));
-      }
-    }
-  };
-
-  struct InitializeVisitor {
-    FlatbuffersTableUntypedDomainImpl& self;
-    absl::BitGenRef prng;
-    corpus_type& val;
-
-    template <typename T>
-    void Visit(const reflection::Field* absl_nonnull field) {
-      auto& domain = self.GetCachedDomain<T>(field);
-      val[field->id()] = domain.Init(prng);
-    }
-  };
-
-  struct CountNumberOfMutableFieldsVisitor {
-    const FlatbuffersTableUntypedDomainImpl& self;
-    uint64_t& total_weight;
-    corpus_type& val;
-    bool only_shrink = false;
-
-    template <typename T>
-    void Visit(const reflection::Field* absl_nonnull field) const {
-      if (!self.IsSupportedField(field)) return;
-      if (only_shrink && !val.contains(field->id())) return;
-
-      // Add the weight of the field itself.
-      total_weight += 1;
-
-      auto& domain = self.GetCachedDomain<T>(field);
-      if (auto it = val.find(field->id()); it != val.end()) {
-        // Add the weight of the field corpus.
-        total_weight += domain.CountNumberOfFields(it->second);
-      }
-    }
-  };
-
-  struct MutateVisitor {
-    FlatbuffersTableUntypedDomainImpl& self;
-    absl::BitGenRef prng;
-    const domain_implementor::MutationMetadata& metadata;
-    bool only_shrink;
-    corpus_type& corpus_value;
-
-    template <typename T>
-    void Visit(const reflection::Field* absl_nonnull field) {
-      auto& domain = self.GetCachedDomain<T>(field);
-      auto it = corpus_value.find(field->id());
-      if (it == corpus_value.end()) {
-        if (only_shrink) return;
-        it = corpus_value.try_emplace(field->id(), domain.Init(prng)).first;
-      }
-      domain.Mutate(it->second, prng, metadata, only_shrink);
-    }
-  };
-
-  struct Printer {
-    const FlatbuffersTableUntypedDomainImpl& self;
-
-    void PrintCorpusValue(const corpus_type& value,
-                          domain_implementor::RawSink out,
-                          domain_implementor::PrintMode mode) const {
-      std::vector<typename corpus_type::key_type> field_ids;
-      field_ids.reserve(value.size());
-      for (const auto& [id, _] : value) {
-        field_ids.push_back(id);
-      }
-      // Sort the field ids to make the output deterministic.
-      std::sort(field_ids.begin(), field_ids.end());
-
-      absl::Format(out, "{");
-      bool first = true;
-      for (const auto id : field_ids) {
-        if (!first) {
-          absl::Format(out, ", ");
+      } else if constexpr (std::is_same_v<T, FlatbuffersStructTag>) {
+        FlatbuffersStructUntypedDomainImpl domain(
+            self.schema_, self.schema_->objects()->Get(field->type()->index()));
+        auto opt_corpus = corpus_value.template GetAs<
+            std::variant<std::monostate, GenericDomainCorpusType>>();
+        if (std::holds_alternative<std::monostate>(opt_corpus)) {
+          return;
         }
-        const reflection::Field* absl_nullable field = self.GetFieldById(id);
-        if (field == nullptr) {
-          absl::Format(out, "<unknown field: %d>", id);
-        } else {
-          VisitFlatbufferField(self.schema_, field,
-                               PrinterVisitor{self, value.at(id), out, mode});
+        auto inner_corpus =
+            std::get<GenericDomainCorpusType>(opt_corpus)
+                .template GetAs<corpus_type_t<decltype(domain)>>();
+        auto offset = domain.BuildValue(inner_corpus, builder);
+        if (offset.has_value()) {
+          builder.AddStructOffset(field->offset(), offset.value());
         }
-        first = false;
+      } else if constexpr (std::is_same_v<T, FlatbuffersUnionTag>) {
+        // From flatbuffers documentation:
+        // Unions are encoded as the combination of two fields: an enum
+        // representing the union choice and the offset to the actual element
+        const reflection::Enum* union_type =
+            self.schema_->enums()->Get(field->type()->index());
+        FlatbuffersUnionDomainImpl domain(self.schema_, union_type);
+        if (auto it = offsets.find(field->id()); it != offsets.end()) {
+          // Store just an offset to the actual union element.
+          builder.AddOffset(field->offset(),
+                            flatbuffers::Offset<void>(it->second));
+
+          constexpr char kUnionTypeFieldSuffix[] = "_type";
+          const reflection::Field* type_field =
+              self.object_->fields()->LookupByKey(
+                  absl::StrCat(field->name()->c_str(), kUnionTypeFieldSuffix)
+                      .c_str());
+          auto opt_corpus = corpus_value.GetAs<
+              std::variant<std::monostate, GenericDomainCorpusType>>();
+          if (std::holds_alternative<std::monostate>(opt_corpus)) {
+            return;
+          }
+          auto inner_corpus = std::get<GenericDomainCorpusType>(opt_corpus)
+                                  .GetAs<corpus_type_t<decltype(domain)>>();
+          uint8_t type_value = domain.GetType(inner_corpus);
+          builder.AddElement<uint8_t>(type_field->offset(), type_value, 0);
+        }
       }
-      absl::Format(out, "}");
-    }
-  };
-
-  struct PrinterVisitor {
-    const FlatbuffersTableUntypedDomainImpl& self;
-    const GenericDomainCorpusType& val;
-    domain_implementor::RawSink out;
-    domain_implementor::PrintMode mode;
-
-    template <typename T>
-    void Visit(const reflection::Field* absl_nonnull field) const {
-      auto& domain = self.GetCachedDomain<T>(field);
-      absl::Format(out, "%s: ", field->name()->str());
-      domain_implementor::PrintValue(domain, val, out, mode);
     }
   };
 };
@@ -702,14 +1678,14 @@
     auto table_object = schema->objects()->Get(field->type()->index());
     return FlatbuffersTableUntypedDomainImpl{schema, table_object};
   } else if constexpr (std::is_same_v<T, FlatbuffersStructTag>) {
-    // TODO: support structs.
-    return placeholder;
+    auto struct_object = schema->objects()->Get(field->type()->index());
+    return FlatbuffersStructUntypedDomainImpl{schema, struct_object};
   } else if constexpr (std::is_same_v<T, FlatbuffersUnionTag>) {
-    // TODO: support unions.
-    return placeholder;
-  } else if constexpr (std::is_same_v<T, FlatbuffersVectorTag>) {
-    // TODO: support vectors.
-    return placeholder;
+    auto union_type = schema->enums()->Get(field->type()->index());
+    return FlatbuffersUnionDomainImpl{schema, union_type};
+  } else if constexpr (is_any_flatbuffers_vector_tag_v<T>) {
+    auto elem_domain = GetDefaultDomain<typename T::value_type>(schema, field);
+    return VectorOf(elem_domain);
   } else {
     return Arbitrary<T>();
   }
@@ -729,7 +1705,7 @@
 // - The serialized buffer of the table.
 template <typename T>
 class FlatbuffersTableDomainImpl
-    : public fuzztest::domain_implementor::DomainBase<
+    : public domain_implementor::DomainBase<
           /*Derived=*/FlatbuffersTableDomainImpl<T>,
           /*ValueType=*/const T*,
           /*CorpusType=*/FlatbuffersTableDomainCorpusType> {
@@ -764,6 +1740,14 @@
     return inner_->CountNumberOfFields(val.untyped_corpus);
   }
 
+  uint64_t MutateSelectedField(
+      corpus_type& val, absl::BitGenRef prng,
+      const domain_implementor::MutationMetadata& metadata, bool only_shrink,
+      uint64_t selected_field_index) {
+    return inner_->MutateSelectedField(val.untyped_corpus, prng, metadata,
+                                       only_shrink, selected_field_index);
+  }
+
   // Mutates the given corpus value.
   void Mutate(corpus_type& val, absl::BitGenRef prng,
               const domain_implementor::MutationMetadata& metadata,
diff --git a/fuzztest/internal/test_flatbuffers.fbs b/fuzztest/internal/test_flatbuffers.fbs
index 02177c0..4811d9b 100644
--- a/fuzztest/internal/test_flatbuffers.fbs
+++ b/fuzztest/internal/test_flatbuffers.fbs
@@ -1,53 +1,92 @@
+// Copyright 2025 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
 namespace fuzztest.internal;
 
-enum ByteEnum: byte {
+enum ByteEnum:byte {
   First,
-  Second
-}
-enum ShortEnum: short {
-  First,
-  Second
+  Second,
 }
 
-enum IntEnum: int {
+enum ShortEnum:short {
   First,
-  Second
+  Second,
 }
 
-enum LongEnum: long {
+enum IntEnum:int {
   First,
-  Second
+  Second,
 }
 
-enum UByteEnum: ubyte {
+enum LongEnum:long {
   First,
-  Second
+  Second,
 }
 
-enum UShortEnum: ushort {
+enum UByteEnum:ubyte {
   First,
-  Second
+  Second,
 }
-enum UIntEnum: uint {
+
+enum UShortEnum:ushort {
   First,
-  Second
+  Second,
 }
-enum ULongEnum: ulong {
+
+enum UIntEnum:uint {
   First,
-  Second
+  Second,
+}
+
+enum ULongEnum:ulong {
+  First,
+  Second,
 }
 
 struct BoolStruct {
-  b: bool;
-  a_b: [bool:2];
+  b:bool;
+}
+
+struct DefaultStruct {
+  b:bool;
+  i8:byte;
+  i16:short;
+  i32:int;
+  i64:long;
+  u8:ubyte;
+  u16:ushort;
+  u32:uint;
+  u64:ulong;
+  f:float;
+  d:double;
+  ei8:ByteEnum;
+  ei16:ShortEnum;
+  ei32:IntEnum;
+  ei64:LongEnum;
+  eu8:UByteEnum;
+  eu16:UShortEnum;
+  eu32:UIntEnum;
+  eu64:ULongEnum;
+  s:BoolStruct;
 }
 
 table BoolTable {
-  b: bool;
+  b:bool;
 }
 
 table StringTable {
-  str: string;
+  str:string;
 }
 
 union Union {
@@ -56,93 +95,144 @@
   BoolStruct,
 }
 
+table UnionTable {
+  u:Union;
+}
+
 table DefaultTable {
-  b: bool;
-  i8: byte;
-  i16: short;
-  i32: int;
-  i64: long;
-  u8: ubyte;
-  u16: ushort;
-  u32: uint;
-  u64: ulong;
-  f: float;
-  d: double;
-  str: string;
-  ei8: ByteEnum;
-  ei16: ShortEnum;
-  ei32: IntEnum;
-  ei64: LongEnum;
-  eu8: UByteEnum;
-  eu16: UShortEnum;
-  eu32: UIntEnum;
-  eu64: ULongEnum;
-  t: BoolTable;
+  b:bool;
+  i8:byte;
+  i16:short;
+  i32:int;
+  i64:long;
+  u8:ubyte;
+  u16:ushort;
+  u32:uint;
+  u64:ulong;
+  f:float;
+  d:double;
+  str:string;
+  ei8:ByteEnum;
+  ei16:ShortEnum;
+  ei32:IntEnum;
+  ei64:LongEnum;
+  eu8:UByteEnum;
+  eu16:UShortEnum;
+  eu32:UIntEnum;
+  eu64:ULongEnum;
+  t:BoolTable;
+  u:Union;
+  s:DefaultStruct;
+  v_b:[bool];
+  v_i8:[byte];
+  v_i16:[short];
+  v_i32:[int];
+  v_i64:[long];
+  v_u8:[ubyte];
+  v_u16:[ushort];
+  v_u32:[uint];
+  v_u64:[ulong];
+  v_f:[float];
+  v_d:[double];
+  v_str:[string];
+  v_ei8:[ByteEnum];
+  v_ei16:[ShortEnum];
+  v_ei32:[IntEnum];
+  v_ei64:[LongEnum];
+  v_eu8:[UByteEnum];
+  v_eu16:[UShortEnum];
+  v_eu32:[UIntEnum];
+  v_eu64:[ULongEnum];
+  v_t:[BoolTable];
+  v_u:[Union];
+  v_s:[DefaultStruct];
 }
 
 table OptionalTable {
-  b: bool = null;
-  i8: byte = null;
-  i16: short = null;
-  i32: int = null;
-  i64: long = null;
-  u8: ubyte = null;
-  u16: ushort = null;
-  u32: uint = null;
-  u64: ulong = null;
-  f: float = null;
-  d: double = null;
-  str: string; // always optional, no need to specify the default value.
-  ei8: ByteEnum = null;
-  ei16: ShortEnum = null;
-  ei32: IntEnum = null;
-  ei64: LongEnum = null;
-  eu8: UByteEnum = null;
-  eu16: UShortEnum = null;
-  eu32: UIntEnum = null;
-  eu64: ULongEnum = null;
-  t: BoolTable;
+  b:bool = null;
+  i8:byte = null;
+  i16:short = null;
+  i32:int = null;
+  i64:long = null;
+  u8:ubyte = null;
+  u16:ushort = null;
+  u32:uint = null;
+  u64:ulong = null;
+  f:float = null;
+  d:double = null;
+  str:string;  // always optional, no need to specify the default value.
+  ei8:ByteEnum = null;
+  ei16:ShortEnum = null;
+  ei32:IntEnum = null;
+  ei64:LongEnum = null;
+  eu8:UByteEnum = null;
+  eu16:UShortEnum = null;
+  eu32:UIntEnum = null;
+  eu64:ULongEnum = null;
+  t:BoolTable;
+  u:Union;
+  s:DefaultStruct;
+  v_b:[bool];
+  v_i8:[byte];
+  v_i16:[short];
+  v_i32:[int];
+  v_i64:[long];
+  v_u8:[ubyte];
+  v_u16:[ushort];
+  v_u32:[uint];
+  v_u64:[ulong];
+  v_f:[float];
+  v_d:[double];
+  v_str:[string];
+  v_ei8:[ByteEnum];
+  v_ei16:[ShortEnum];
+  v_ei32:[IntEnum];
+  v_ei64:[LongEnum];
+  v_eu8:[UByteEnum];
+  v_eu16:[UShortEnum];
+  v_eu32:[UIntEnum];
+  v_eu64:[ULongEnum];
+  v_t:[BoolTable];
+  v_u:[Union];
+  v_s:[DefaultStruct];
 }
 
 table RequiredTable {
-  str: string (required);
-  t: BoolTable (required);
+  str:string (required);
+  t:BoolTable (required);
+  u:Union (required);
+  s:DefaultStruct (required);
+  v_b:[bool] (required);
+  v_i8:[byte] (required);
+  v_i16:[short] (required);
+  v_i32:[int] (required);
+  v_i64:[long] (required);
+  v_u8:[ubyte] (required);
+  v_u16:[ushort] (required);
+  v_u32:[uint] (required);
+  v_u64:[ulong] (required);
+  v_f:[float] (required);
+  v_d:[double] (required);
+  v_str:[string] (required);
+  v_ei8:[ByteEnum] (required);
+  v_ei16:[ShortEnum] (required);
+  v_ei32:[IntEnum] (required);
+  v_ei64:[LongEnum] (required);
+  v_eu8:[UByteEnum] (required);
+  v_eu16:[UShortEnum] (required);
+  v_eu32:[UIntEnum] (required);
+  v_eu64:[ULongEnum] (required);
+  v_t:[BoolTable] (required);
+  v_u:[Union] (required);
+  v_s:[DefaultStruct] (required);
 }
 
 table RecursiveTable {
-  t: NestedRecursiveTable;
+  t:NestedRecursiveTable;
 }
 
 table NestedRecursiveTable {
-  t: RecursiveTable;
-}
-
-table UnsupportedTypesTable {
-  u: Union;
-  s: BoolStruct;
-  v_b: [bool];
-  v_i8: [byte];
-  v_i16: [short];
-  v_i32: [int];
-  v_i64: [long];
-  v_u8: [ubyte];
-  v_u16: [ushort];
-  v_u32: [uint];
-  v_u64: [ulong];
-  v_f: [float];
-  v_d: [double];
-  v_str: [string];
-  v_ei8: [ByteEnum];
-  v_ei16: [ShortEnum];
-  v_ei32: [IntEnum];
-  v_ei64: [LongEnum];
-  v_eu8: [UByteEnum];
-  v_eu16: [UShortEnum];
-  v_eu32: [UIntEnum];
-  v_eu64: [ULongEnum];
-  v_t: [BoolTable];
-  v_u: [Union];
-  v_s: [BoolStruct];
+  t:RecursiveTable;
 }
 
 root_type DefaultTable;
diff --git a/fuzztest/internal/test_flatbuffers_64bits.fbs b/fuzztest/internal/test_flatbuffers_64bits.fbs
index b893c88..68e98b3 100644
--- a/fuzztest/internal/test_flatbuffers_64bits.fbs
+++ b/fuzztest/internal/test_flatbuffers_64bits.fbs
@@ -16,6 +16,7 @@
 
 table DefaultTable64 {
   str:string (offset64);
+  v_u8:[ubyte] (vector64);
 }
 
 root_type DefaultTable64;