Add array support to flatbuffers domain

PiperOrigin-RevId: 759586755
diff --git a/.github/workflows/cmake_test.yml b/.github/workflows/cmake_test.yml
index 8ad0fd9..98b1133 100644
--- a/.github/workflows/cmake_test.yml
+++ b/.github/workflows/cmake_test.yml
@@ -77,6 +77,7 @@
             -D CMAKE_CXX_COMPILER_LAUNCHER=ccache \
             -D CMAKE_BUILD_TYPE=RelWithDebug \
             -D FUZZTEST_BUILD_TESTING=on \
+            -D FUZZTEST_BUILD_FLATBUFFERS=on \
           && cmake --build build -j $(nproc) \
           && ctest --test-dir build -j $(nproc) --output-on-failure
       - name: Run all tests in default mode with gcc
@@ -90,6 +91,7 @@
             -D CMAKE_CXX_COMPILER_LAUNCHER=ccache \
             -D CMAKE_BUILD_TYPE=RelWithDebug \
             -D FUZZTEST_BUILD_TESTING=on \
+            -D FUZZTEST_BUILD_FLATBUFFERS=on \
           && cmake --build build_gcc -j $(nproc) \
           && ctest --test-dir build_gcc -j $(nproc) --output-on-failure
       - name: Run end-to-end tests in fuzzing mode
@@ -104,6 +106,7 @@
             -D CMAKE_BUILD_TYPE=RelWithDebug \
             -D FUZZTEST_FUZZING_MODE=on \
             -D FUZZTEST_BUILD_TESTING=on \
+            -D FUZZTEST_BUILD_FLATBUFFERS=on \
           && cmake --build build -j $(nproc) \
           && ctest --test-dir build -j $(nproc) --output-on-failure -R "functional_test"
       - name: Save new cache based on main
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 0d5c514..f9f71df 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -16,6 +16,7 @@
 project(fuzztest)
 
 option(FUZZTEST_BUILD_TESTING "Building the tests." OFF)
+option(FUZZTEST_BUILD_FLATBUFFERS "Building the flatbuffers support." OFF)
 option(FUZZTEST_FUZZING_MODE "Building the fuzztest in fuzzing mode." OFF)
 set(FUZZTEST_COMPATIBILITY_MODE "" CACHE STRING "Compatibility mode. Available options: <empty>, libfuzzer")
 set(CMAKE_CXX_STANDARD 17)
diff --git a/MODULE.bazel b/MODULE.bazel
index 8aa515e..f21437e 100644
--- a/MODULE.bazel
+++ b/MODULE.bazel
@@ -35,6 +35,10 @@
     name = "platforms",
     version = "0.0.10",
 )
+bazel_dep(
+    name = "flatbuffers",
+    version = "25.2.10"
+)
 # GoogleTest is not a dev dependency, because it's needed when FuzzTest is used
 # with GoogleTest integration (e.g., googletest_adaptor). Note that the FuzzTest
 # framework can be used without GoogleTest integration as well.
@@ -48,8 +52,6 @@
     name = "protobuf",
     version = "31.1",
 )
-# TODO(lszekeres): Make this a dev dependency, as the protobuf library is only
-# required for testing.
 bazel_dep(
     name = "rules_proto",
     version = "7.1.0",
diff --git a/cmake/BuildDependencies.cmake b/cmake/BuildDependencies.cmake
index de9323f..d8565c0 100644
--- a/cmake/BuildDependencies.cmake
+++ b/cmake/BuildDependencies.cmake
@@ -35,6 +35,9 @@
 set(nlohmann_json_URL https://github.com/nlohmann/json.git)
 set(nlohmann_json_TAG v3.11.3)
 
+set(flatbuffers_URL https://github.com/google/flatbuffers.git)
+set(flatbuffers_TAG v25.2.10)
+
 if(POLICY CMP0135)
 	cmake_policy(SET CMP0135 NEW)
 	set(CMAKE_POLICY_DEFAULT_CMP0135 NEW)
@@ -64,6 +67,14 @@
   URL_HASH MD5=${antlr_cpp_MD5}
 )
 
+if (FUZZTEST_BUILD_FLATBUFFERS)
+  FetchContent_Declare(
+    flatbuffers
+    GIT_REPOSITORY ${flatbuffers_URL}
+    GIT_TAG        ${flatbuffers_TAG}
+  )
+endif()
+
 if (FUZZTEST_BUILD_TESTING)
 
   FetchContent_Declare(
@@ -101,3 +112,9 @@
   FetchContent_MakeAvailable(nlohmann_json)
 
 endif ()
+
+if (FUZZTEST_BUILD_FLATBUFFERS)
+  set(FLATBUFFERS_BUILD_TESTS OFF)
+  set(FLATBUFFERS_BUILD_INSTALL OFF)
+  FetchContent_MakeAvailable(flatbuffers)
+endif()
diff --git a/cmake/FuzzTestHelpers.cmake b/cmake/FuzzTestHelpers.cmake
index 31a7db4..2869a77 100644
--- a/cmake/FuzzTestHelpers.cmake
+++ b/cmake/FuzzTestHelpers.cmake
@@ -338,4 +338,191 @@
 
   add_library(fuzztest::${FUZZTEST_PROTO_LIB_NAME} ALIAS ${_NAME})
 
-endfunction()
\ No newline at end of file
+endfunction()
+
+# fuzztest_flatbuffers_generate_headers()
+#
+# Modified version of `flatbuffers_generate_headers`
+# from https://github.com/google/flatbuffers/blob/master/CMake/BuildFlatBuffers.cmake
+# Supports having the embedded schema header in the output set.
+#
+# Creates a target that can be linked against that generates flatbuffer headers.
+#
+# This function takes a target name and a list of schemas. You can also specify
+# other flagc flags using the FLAGS option to change the behavior of the flatc
+# tool.
+#
+# When the target_link_libraries is done within a different directory than
+# flatbuffers_generate_headers is called, then the target should also be dependent
+# the custom generation target called fuzztest_GENERATE_<TARGET>.
+#
+# Arguments:
+#   TARGET: The name of the target to generate.
+#   SCHEMAS: The list of schema files to generate code for.
+#   BINARY_SCHEMAS_DIR: Optional. The directory in which to generate binary
+#       schemas. Binary schemas will only be generated if a path is provided.
+#   INCLUDE: Optional. Search for includes in the specified paths. (Use this
+#       instead of "-I <path>" and the FLAGS option so that CMake is aware of
+#       the directories that need to be searched).
+#   INCLUDE_PREFIX: Optional. The directory in which to place the generated
+#       files. Use this instead of the --include-prefix option.
+#   FLAGS: Optional. A list of any additional flags that you would like to pass
+#       to flatc.
+#   TESTONLY: When added, this target will only be built if both
+#             BUILD_TESTING=ON and FUZZTEST_BUILD_TESTING=ON.
+#
+# Example:
+#
+#     fuzztest_flatbuffers_library(
+#         TARGET my_generated_headers_target
+#         INCLUDE_PREFIX ${MY_INCLUDE_PREFIX}"
+#         SCHEMAS ${MY_SCHEMA_FILES}
+#         BINARY_SCHEMAS_DIR "${MY_BINARY_SCHEMA_DIRECTORY}"
+#         FLAGS --gen-object-api)
+#
+#     target_link_libraries(MyExecutableTarget
+#         PRIVATE fuzztest_my_generated_headers_target
+#     )
+#
+# Optional (only needed within different directory):
+#     add_dependencies(app fuzztest_GENERATE_my_generated_headers_target)
+function(fuzztest_flatbuffers_generate_headers)
+  # Parse function arguments.
+  set(options TESTONLY)
+  set(one_value_args
+    "TARGET"
+    "INCLUDE_PREFIX"
+    "BINARY_SCHEMAS_DIR")
+  set(multi_value_args
+    "SCHEMAS"
+    "INCLUDE"
+    "FLAGS")
+  cmake_parse_arguments(
+    PARSE_ARGV 0
+    FLATBUFFERS_GENERATE_HEADERS
+    "${options}"
+    "${one_value_args}"
+    "${multi_value_args}")
+
+  if(FLATBUFFERS_GENERATE_HEADERS_TESTONLY AND NOT (BUILD_TESTING AND FUZZTEST_BUILD_TESTING))
+    return()
+  endif()
+
+  # Test if including from FindFlatBuffers
+  if(FLATBUFFERS_FLATC_EXECUTABLE)
+    set(FLATC_TARGET "")
+    set(FLATC ${FLATBUFFERS_FLATC_EXECUTABLE})
+  elseif(TARGET flatbuffers::flatc)
+    set(FLATC_TARGET flatbuffers::flatc)
+    set(FLATC flatbuffers::flatc)
+  else()
+    set(FLATC_TARGET flatc)
+    set(FLATC flatc)
+  endif()
+
+  set(working_dir "${CMAKE_CURRENT_SOURCE_DIR}")
+
+  # Generate the include files parameters.
+  set(include_params "")
+  foreach (include_dir ${FLATBUFFERS_GENERATE_HEADERS_INCLUDE})
+    set(include_params -I ${include_dir} ${include_params})
+  endforeach()
+
+  # Create a directory to place the generated code.
+  set(generated_target_dir "${CMAKE_CURRENT_BINARY_DIR}")
+  set(generated_include_dir "${generated_target_dir}")
+  if (NOT ${FLATBUFFERS_GENERATE_HEADERS_INCLUDE_PREFIX} STREQUAL "")
+    set(generated_include_dir "${generated_include_dir}/${FLATBUFFERS_GENERATE_HEADERS_INCLUDE_PREFIX}")
+    list(APPEND FLATBUFFERS_GENERATE_HEADERS_FLAGS 
+         "--include-prefix" ${FLATBUFFERS_GENERATE_HEADERS_INCLUDE_PREFIX})
+  endif()
+
+  set(generated_custom_commands)
+
+  # Create rules to generate the code for each schema.
+  foreach(schema ${FLATBUFFERS_GENERATE_HEADERS_SCHEMAS})
+    get_filename_component(filename ${schema} NAME_WE)
+    set(generated_include "${generated_include_dir}/${filename}_generated.h")
+    # Add the embedded schema header in the output set if requested.
+    if("${FLATBUFFERS_GENERATE_HEADERS_FLAGS}" MATCHES "--bfbs-gen-embed")
+      list(APPEND generated_include "${generated_include_dir}/${filename}_bfbs_generated.h")
+    endif()
+
+    # Generate files for grpc if needed
+    set(generated_source_file)
+    if("${FLATBUFFERS_GENERATE_HEADERS_FLAGS}" MATCHES "--grpc")
+      # Check if schema file contain a rpc_service definition
+      file(STRINGS ${schema} has_grpc REGEX "rpc_service")
+      if(has_grpc)
+        list(APPEND generated_include "${generated_include_dir}/${filename}.grpc.fb.h")
+        set(generated_source_file "${generated_include_dir}/${filename}.grpc.fb.cc")
+      endif()
+    endif()
+
+    add_custom_command(
+      OUTPUT ${generated_include} ${generated_source_file}
+      COMMAND ${FLATC} ${FLATC_ARGS}
+      -o ${generated_include_dir}
+      ${include_params}
+      -c ${schema}
+      ${FLATBUFFERS_GENERATE_HEADERS_FLAGS}
+      DEPENDS ${FLATC_TARGET} ${schema}
+      WORKING_DIRECTORY "${working_dir}"
+      COMMENT "Building ${schema} flatbuffers...")
+    list(APPEND all_generated_header_files ${generated_include})
+    list(APPEND all_generated_source_files ${generated_source_file})
+    list(APPEND generated_custom_commands "${generated_include}" "${generated_source_file}")
+
+    # Generate the binary flatbuffers schemas if instructed to.
+    if (NOT ${FLATBUFFERS_GENERATE_HEADERS_BINARY_SCHEMAS_DIR} STREQUAL "")
+      set(binary_schema
+          "${FLATBUFFERS_GENERATE_HEADERS_BINARY_SCHEMAS_DIR}/${filename}.bfbs")
+      add_custom_command(
+        OUTPUT ${binary_schema}
+        COMMAND ${FLATC} -b --schema
+        -o ${FLATBUFFERS_GENERATE_HEADERS_BINARY_SCHEMAS_DIR}
+        ${include_params}
+        ${schema}
+        DEPENDS ${FLATC_TARGET} ${schema}
+        WORKING_DIRECTORY "${working_dir}")
+      list(APPEND generated_custom_commands "${binary_schema}")
+      list(APPEND all_generated_binary_files ${binary_schema})
+    endif()
+  endforeach()
+
+  # Create an additional target as add_custom_command scope is only within same directory (CMakeFile.txt)
+  set(generate_target fuzztest_GENERATE_${FLATBUFFERS_GENERATE_HEADERS_TARGET})
+  add_custom_target(${generate_target} ALL
+                    DEPENDS ${generated_custom_commands}
+                    COMMENT "Generating flatbuffer target fuzztest_${FLATBUFFERS_GENERATE_HEADERS_TARGET}")
+
+  # Set up interface library
+  add_library(fuzztest_${FLATBUFFERS_GENERATE_HEADERS_TARGET} INTERFACE)
+  add_dependencies(
+    fuzztest_${FLATBUFFERS_GENERATE_HEADERS_TARGET}
+    ${FLATC}
+    ${FLATBUFFERS_GENERATE_HEADERS_SCHEMAS})
+  target_include_directories(
+    fuzztest_${FLATBUFFERS_GENERATE_HEADERS_TARGET}
+    INTERFACE ${generated_target_dir})
+
+  # Organize file layout for IDEs.
+  source_group(
+    TREE "${generated_target_dir}"
+    PREFIX "Flatbuffers/Generated/Headers Files"
+    FILES ${all_generated_header_files})
+  source_group(
+    TREE "${generated_target_dir}"
+    PREFIX "Flatbuffers/Generated/Source Files"
+    FILES ${all_generated_source_files})
+  source_group(
+    TREE ${working_dir}
+    PREFIX "Flatbuffers/Schemas"
+    FILES ${FLATBUFFERS_GENERATE_HEADERS_SCHEMAS})
+  if (NOT ${FLATBUFFERS_GENERATE_HEADERS_BINARY_SCHEMAS_DIR} STREQUAL "")
+    source_group(
+      TREE "${FLATBUFFERS_GENERATE_HEADERS_BINARY_SCHEMAS_DIR}"
+      PREFIX "Flatbuffers/Generated/Binary Schemas"
+      FILES ${all_generated_binary_files})
+  endif()
+endfunction()
diff --git a/cmake/generate_cmake_from_bazel.py b/cmake/generate_cmake_from_bazel.py
index 1816c62..86bfd59 100755
--- a/cmake/generate_cmake_from_bazel.py
+++ b/cmake/generate_cmake_from_bazel.py
@@ -66,6 +66,7 @@
     "@abseil-cpp//absl/types:optional": "absl::optional",
     "@abseil-cpp//absl/types:span": "absl::span",
     "@abseil-cpp//absl/types:variant": "absl::variant",
+    "@flatbuffers//:runtime_cc": "flatbuffers",
     "@googletest//:gtest": "GTest::gtest",
     "@googletest//:gtest_main": "GTest::gmock_main",
     "@protobuf//:protobuf": "protobuf::libprotobuf",
diff --git a/domain_tests/BUILD b/domain_tests/BUILD
index a2584b1..0f1b7dc 100644
--- a/domain_tests/BUILD
+++ b/domain_tests/BUILD
@@ -38,6 +38,25 @@
 )
 
 cc_test(
+    name = "arbitrary_domains_flatbuffers_test",
+    srcs = ["arbitrary_domains_flatbuffers_test.cc"],
+    deps = [
+        ":domain_testing",
+        "@abseil-cpp//absl/container:flat_hash_map",
+        "@abseil-cpp//absl/log:check",
+        "@abseil-cpp//absl/random",
+        "@com_google_fuzztest//fuzztest:domain",
+        "@com_google_fuzztest//fuzztest:flatbuffers",
+        "@com_google_fuzztest//fuzztest:fuzztest_macros",
+        "@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",
+    ],
+)
+
+cc_test(
     name = "arbitrary_domains_protobuf_test",
     srcs = ["arbitrary_domains_protobuf_test.cc"],
     deps = [
diff --git a/domain_tests/CMakeLists.txt b/domain_tests/CMakeLists.txt
index 4a858b0..a9f9bb7 100644
--- a/domain_tests/CMakeLists.txt
+++ b/domain_tests/CMakeLists.txt
@@ -18,6 +18,30 @@
     GTest::gmock_main
 )
 
+if (FUZZTEST_BUILD_FLATBUFFERS)
+  fuzztest_cc_test(
+    NAME
+      arbitrary_domains_flatbuffers_test
+    SRCS
+      "arbitrary_domains_flatbuffers_test.cc"
+    DEPS
+      absl::flat_hash_set
+      absl::random_bit_gen_ref
+      absl::random_random
+      absl::strings
+      flatbuffers
+      fuzztest::domain
+      fuzztest::domain_testing
+      fuzztest::flatbuffers
+      fuzztest::fuzztest_macros
+      GTest::gmock_main
+      fuzztest_test_flatbuffers_headers
+  )
+  add_dependencies(fuzztest_arbitrary_domains_flatbuffers_test
+    fuzztest_GENERATE_test_flatbuffers_headers
+  )
+endif()
+
 fuzztest_cc_test(
   NAME
     arbitrary_domains_protobuf_test
diff --git a/domain_tests/arbitrary_domains_flatbuffers_test.cc b/domain_tests/arbitrary_domains_flatbuffers_test.cc
new file mode 100644
index 0000000..7f5bdf0
--- /dev/null
+++ b/domain_tests/arbitrary_domains_flatbuffers_test.cc
@@ -0,0 +1,1312 @@
+// 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.
+
+#include <array>
+#include <cassert>
+#include <cstddef>
+#include <cstdint>
+#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 "flatbuffers/array.h"
+#include "flatbuffers/base.h"
+#include "flatbuffers/buffer.h"
+#include "flatbuffers/flatbuffer_builder.h"
+#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/fuzztest_macros.h"
+#include "./fuzztest/internal/meta.h"
+#include "./fuzztest/internal/serialization.h"
+#include "./fuzztest/internal/test_flatbuffers_generated.h"
+
+namespace fuzztest {
+namespace {
+
+using ::fuzztest::internal::BoolStruct;
+using ::fuzztest::internal::BoolTable;
+using ::fuzztest::internal::ByteEnum;
+using ::fuzztest::internal::DefaultStruct;
+using ::fuzztest::internal::DefaultTable;
+using ::fuzztest::internal::IntEnum;
+using ::fuzztest::internal::LongEnum;
+using ::fuzztest::internal::OptionalTable;
+using ::fuzztest::internal::RequiredTable;
+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;
+using ::testing::HasSubstr;
+using ::testing::IsFalse;
+using ::testing::IsTrue;
+using ::testing::NotNull;
+using ::testing::Pair;
+using ::testing::ResultOf;
+
+template <typename T>
+inline bool Eq(const T& lhs, const T& rhs) {
+  return rhs == lhs;
+}
+template <typename T>
+inline bool Eq(const T* lhs, const T* rhs) {
+  if (lhs == nullptr && rhs == nullptr) return true;
+  if (lhs == nullptr || rhs == nullptr) return false;
+  return Eq(*lhs, *rhs);
+}
+
+template <typename T, uint16_t Size>
+inline bool ArrayEq(const flatbuffers::Array<T, Size>* lhs,
+                    const flatbuffers::Array<T, Size>* rhs) {
+  if (lhs == nullptr && rhs == nullptr) return true;
+  if (lhs == nullptr || rhs == nullptr) return false;
+  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<BoolStruct>(const BoolStruct& lhs, const BoolStruct& rhs) {
+  return Eq(lhs.b(), rhs.b());
+}
+
+template <>
+inline bool Eq<DefaultStruct>(const DefaultStruct& lhs,
+                              const DefaultStruct& rhs) {
+  bool eq_b = lhs.b() == rhs.b();
+  bool eq_i8 = lhs.i8() == rhs.i8();
+  bool eq_i16 = lhs.i16() == rhs.i16();
+  bool eq_i32 = lhs.i32() == rhs.i32();
+  bool eq_i64 = lhs.i64() == rhs.i64();
+  bool eq_u8 = lhs.u8() == rhs.u8();
+  bool eq_u16 = lhs.u16() == rhs.u16();
+  bool eq_u32 = lhs.u32() == rhs.u32();
+  bool eq_u64 = lhs.u64() == rhs.u64();
+  bool eq_f = lhs.f() == rhs.f();
+  bool eq_d = lhs.d() == rhs.d();
+  bool eq_ei8 = lhs.ei8() == rhs.ei8();
+  bool eq_ei16 = lhs.ei16() == rhs.ei16();
+  bool eq_ei32 = lhs.ei32() == rhs.ei32();
+  bool eq_ei64 = lhs.ei64() == rhs.ei64();
+  bool eq_eu8 = lhs.eu8() == rhs.eu8();
+  bool eq_eu16 = lhs.eu16() == rhs.eu16();
+  bool eq_eu32 = lhs.eu32() == rhs.eu32();
+  bool eq_eu64 = lhs.eu64() == rhs.eu64();
+  bool eq_s = Eq(lhs.s(), rhs.s());
+  bool eq_a_b = ArrayEq(lhs.a_b(), rhs.a_b());
+  bool eq_a_i8 = ArrayEq(lhs.a_i8(), rhs.a_i8());
+  bool eq_a_i16 = ArrayEq(lhs.a_i16(), rhs.a_i16());
+  bool eq_a_i32 = ArrayEq(lhs.a_i32(), rhs.a_i32());
+  bool eq_a_i64 = ArrayEq(lhs.a_i64(), rhs.a_i64());
+  bool eq_a_u8 = ArrayEq(lhs.a_u8(), rhs.a_u8());
+  bool eq_a_u16 = ArrayEq(lhs.a_u16(), rhs.a_u16());
+  bool eq_a_u32 = ArrayEq(lhs.a_u32(), rhs.a_u32());
+  bool eq_a_u64 = ArrayEq(lhs.a_u64(), rhs.a_u64());
+  bool eq_a_f = ArrayEq(lhs.a_f(), rhs.a_f());
+  bool eq_a_d = ArrayEq(lhs.a_d(), rhs.a_d());
+  bool eq_a_ei8 = ArrayEq(lhs.a_ei8(), rhs.a_ei8());
+  bool eq_a_ei16 = ArrayEq(lhs.a_ei16(), rhs.a_ei16());
+  bool eq_a_ei32 = ArrayEq(lhs.a_ei32(), rhs.a_ei32());
+  bool eq_a_ei64 = ArrayEq(lhs.a_ei64(), rhs.a_ei64());
+  bool eq_a_eu8 = ArrayEq(lhs.a_eu8(), rhs.a_eu8());
+  bool eq_a_eu16 = ArrayEq(lhs.a_eu16(), rhs.a_eu16());
+  bool eq_a_eu32 = ArrayEq(lhs.a_eu32(), rhs.a_eu32());
+  bool eq_a_eu64 = ArrayEq(lhs.a_eu64(), rhs.a_eu64());
+  bool eq_a_s = ArrayEq(lhs.a_s(), rhs.a_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 && eq_a_b &&
+         eq_a_i8 && eq_a_i16 && eq_a_i32 && eq_a_i64 && eq_a_u8 && eq_a_u16 &&
+         eq_a_u32 && eq_a_u64 && eq_a_f && eq_a_d && eq_a_ei8 && eq_a_ei16 &&
+         eq_a_ei32 && eq_a_ei64 && eq_a_eu8 && eq_a_eu16 && eq_a_eu32 &&
+         eq_a_eu64 && eq_a_s;
+}
+
+template <>
+inline bool Eq<flatbuffers::String>(const flatbuffers::String& lhs,
+                                    const flatbuffers::String& rhs) {
+  if (lhs.size() != rhs.size()) return false;
+  return memcmp(lhs.data(), rhs.data(), lhs.size()) == 0;
+}
+
+template <>
+inline bool Eq<BoolTable>(const BoolTable& lhs, const BoolTable& rhs) {
+  return lhs.b() == rhs.b();
+}
+
+template <typename T>
+inline bool VectorEq(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 VectorEq(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 VectorEq(*lhs, *rhs);
+}
+
+inline bool VectorUnionEq(
+    const flatbuffers::Vector<uint8_t>* lhs_type,
+    const flatbuffers::Vector<::flatbuffers::Offset<void>>* lhs,
+    const flatbuffers::Vector<uint8_t>* rhs_type,
+    const flatbuffers::Vector<::flatbuffers::Offset<void>>* rhs) {
+  if (!VectorEq(lhs_type, rhs_type)) return false;
+  if (lhs == nullptr && rhs == nullptr) return true;
+  if (lhs == nullptr || rhs == nullptr) return false;
+  if (lhs->size() != rhs->size()) return false;
+
+  for (int i = 0; i < lhs->size(); ++i) {
+    if (!Eq(std::pair(lhs_type->Get(i), lhs->Get(i)),
+            std::pair(rhs_type->Get(i), rhs->Get(i)))) {
+      return false;
+    }
+  }
+  return true;
+}
+
+template <>
+inline bool Eq<DefaultTable>(const DefaultTable& lhs, const DefaultTable& rhs) {
+  bool eq_b = lhs.b() == rhs.b();
+  bool eq_i8 = lhs.i8() == rhs.i8();
+  bool eq_i16 = lhs.i16() == rhs.i16();
+  bool eq_i32 = lhs.i32() == rhs.i32();
+  bool eq_i64 = lhs.i64() == rhs.i64();
+  bool eq_u8 = lhs.u8() == rhs.u8();
+  bool eq_u16 = lhs.u16() == rhs.u16();
+  bool eq_u32 = lhs.u32() == rhs.u32();
+  bool eq_u64 = lhs.u64() == rhs.u64();
+  bool eq_f = lhs.f() == rhs.f();
+  bool eq_d = lhs.d() == rhs.d();
+  bool eq_str = Eq(lhs.str(), rhs.str());
+  bool eq_ei8 = lhs.ei8() == rhs.ei8();
+  bool eq_ei16 = lhs.ei16() == rhs.ei16();
+  bool eq_ei32 = lhs.ei32() == rhs.ei32();
+  bool eq_ei64 = lhs.ei64() == rhs.ei64();
+  bool eq_eu8 = lhs.eu8() == rhs.eu8();
+  bool eq_eu16 = lhs.eu16() == rhs.eu16();
+  bool eq_eu32 = lhs.eu32() == rhs.eu32();
+  bool eq_eu64 = lhs.eu64() == rhs.eu64();
+  bool eq_t = Eq(lhs.t(), rhs.t());
+  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()));
+  bool eq_s = Eq(lhs.s(), rhs.s());
+  bool eq_v_b = VectorEq(lhs.v_b(), rhs.v_b());
+  bool eq_v_i8 = VectorEq(lhs.v_i8(), rhs.v_i8());
+  bool eq_v_i16 = VectorEq(lhs.v_i16(), rhs.v_i16());
+  bool eq_v_i32 = VectorEq(lhs.v_i32(), rhs.v_i32());
+  bool eq_v_i64 = VectorEq(lhs.v_i64(), rhs.v_i64());
+  bool eq_v_u8 = VectorEq(lhs.v_u8(), rhs.v_u8());
+  bool eq_v_u16 = VectorEq(lhs.v_u16(), rhs.v_u16());
+  bool eq_v_u32 = VectorEq(lhs.v_u32(), rhs.v_u32());
+  bool eq_v_u64 = VectorEq(lhs.v_u64(), rhs.v_u64());
+  bool eq_v_f = VectorEq(lhs.v_f(), rhs.v_f());
+  bool eq_v_d = VectorEq(lhs.v_d(), rhs.v_d());
+  bool eq_v_str = VectorEq(lhs.v_str(), rhs.v_str());
+  bool eq_v_ei8 = VectorEq(lhs.v_ei8(), rhs.v_ei8());
+  bool eq_v_ei16 = VectorEq(lhs.v_ei16(), rhs.v_ei16());
+  bool eq_v_ei32 = VectorEq(lhs.v_ei32(), rhs.v_ei32());
+  bool eq_v_ei64 = VectorEq(lhs.v_ei64(), rhs.v_ei64());
+  bool eq_v_eu8 = VectorEq(lhs.v_eu8(), rhs.v_eu8());
+  bool eq_v_eu16 = VectorEq(lhs.v_eu16(), rhs.v_eu16());
+  bool eq_v_eu32 = VectorEq(lhs.v_eu32(), rhs.v_eu32());
+  bool eq_v_eu64 = VectorEq(lhs.v_eu64(), rhs.v_eu64());
+  bool eq_v_t = VectorEq(lhs.v_t(), rhs.v_t());
+  bool eq_v_u_type = VectorEq(lhs.v_u_type(), rhs.v_u_type());
+  bool eq_v_u =
+      VectorUnionEq(lhs.v_u_type(), lhs.v_u(), rhs.v_u_type(), rhs.v_u());
+  bool eq_v_s = VectorEq(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_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, std::array<uint8_t, 2>{true, false}},  // s
+      std::array<uint8_t, 2>{true, false},                    // a_b
+      std::array<int8_t, 2>{11, 12},                          // a_i8
+      std::array<int16_t, 2>{13, 14},                         // a_i16
+      std::array<int32_t, 2>{15, 16},                         // a_i32
+      std::array<int64_t, 2>{17, 18},                         // a_i64
+      std::array<uint8_t, 2>{19, 20},                         // a_u8
+      std::array<uint16_t, 2>{21, 22},                        // a_u16
+      std::array<uint32_t, 2>{23, 24},                        // a_u32
+      std::array<uint64_t, 2>{25, 26},                        // a_u64
+      std::array<float, 2>{27.0f, 28.0f},                     // a_f
+      std::array<double, 2>{29.0, 30.0},                      // a_d
+      std::array<internal::ByteEnum, 2>{internal::ByteEnum_First,
+                                        internal::ByteEnum_Second},  // a_ei8
+      std::array<internal::ShortEnum, 2>{internal::ShortEnum_First,
+                                         internal::ShortEnum_Second},  // a_ei16
+      std::array<internal::IntEnum, 2>{internal::IntEnum_First,
+                                       internal::IntEnum_Second},  // a_ei32
+      std::array<internal::LongEnum, 2>{internal::LongEnum_First,
+                                        internal::LongEnum_Second},  // a_ei64
+      std::array<internal::UByteEnum, 2>{internal::UByteEnum_First,
+                                         internal::UByteEnum_Second},  // a_eu8
+      std::array<internal::UShortEnum, 2>{
+          internal::UShortEnum_First, internal::UShortEnum_Second},  // a_eu16
+      std::array<internal::UIntEnum, 2>{internal::UIntEnum_First,
+                                        internal::UIntEnum_Second},  // a_eu32
+      std::array<internal::ULongEnum, 2>{internal::ULongEnum_First,
+                                         internal::ULongEnum_Second},  // a_eu64
+      std::array<BoolStruct, 2>{
+          BoolStruct(true, std::array<uint8_t, 2>{true, false}),
+          BoolStruct(false, std::array<uint8_t, 2>{false, true})}  // a_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,
+                                         /*i8=*/1,
+                                         /*i16=*/2,
+                                         /*i32=*/3,
+                                         /*i64=*/4,
+                                         /*u8=*/5,
+                                         /*u16=*/6,
+                                         /*u32=*/7,
+                                         /*u64=*/8,
+                                         /*f=*/9.0,
+                                         /*d=*/10.0,
+                                         /*str=*/"foo bar baz",
+                                         /*ei8=*/internal::ByteEnum_Second,
+                                         /*ei16=*/internal::ShortEnum_Second,
+                                         /*ei32=*/internal::IntEnum_Second,
+                                         /*ei64=*/internal::LongEnum_Second,
+                                         /*eu8=*/internal::UByteEnum_Second,
+                                         /*eu16=*/internal::UShortEnum_Second,
+                                         /*eu32=*/internal::UIntEnum_Second,
+                                         /*eu64=*/internal::ULongEnum_Second,
+                                         /*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());
+}
+
+template <typename Domain>
+std::vector<typename Domain::corpus_type> GenerateNonUniqueCorpusValues(
+    Domain domain, int num_seeds = 10, int num_mutations = 100,
+    const domain_implementor::MutationMetadata& metadata = {},
+    bool only_shrink = false) {
+  using CorpusT = typename Domain::corpus_type;
+  absl::BitGen bitgen;
+
+  std::vector<CorpusT> seeds;
+  seeds.reserve(num_seeds);
+  while (seeds.size() < num_seeds) {
+    seeds.push_back(domain.Init(bitgen));
+  }
+
+  std::vector<CorpusT> values = seeds;
+
+  for (const auto& seed : seeds) {
+    CorpusT value = seed;
+    std::vector<CorpusT> mutations;
+    mutations.reserve(num_mutations);
+    while (mutations.size() < num_mutations) {
+      domain.Mutate(value, bitgen, metadata, only_shrink);
+      mutations.push_back(value);
+    }
+    values.insert(values.end(), mutations.begin(), mutations.end());
+  }
+
+  return values;
+};
+
+template <typename Domain>
+std::vector<typename Domain::corpus_type> GenerateInitialCorpusValues(
+    Domain domain, int n) {
+  std::vector<typename Domain::corpus_type> values;
+  absl::BitGen bitgen;
+  values.reserve(n);
+  for (int i = 0; i < n; ++i) {
+    values.push_back(domain.Init(bitgen));
+  }
+  return values;
+}
+
+TEST(FlatbuffersMetaTest, IsFlatbuffersTable) {
+  static_assert(internal::is_flatbuffers_table_v<DefaultTable>);
+  static_assert(!internal::is_flatbuffers_table_v<int>);
+  static_assert(!internal::is_flatbuffers_table_v<std::optional<bool>>);
+}
+
+TEST(FlatbuffersTableDomainImplTest, DefaultTableValueRoundTrip) {
+  flatbuffers::FlatBufferBuilder fbb;
+  auto table = CreateDefaultTable(fbb);
+
+  auto domain = Arbitrary<const DefaultTable*>();
+  auto corpus = domain.FromValue(table);
+  ASSERT_TRUE(corpus.has_value());
+  ASSERT_OK(domain.ValidateCorpusValue(*corpus));
+
+  auto ir = domain.SerializeCorpus(corpus.value());
+
+  auto new_corpus = domain.ParseCorpus(ir);
+  ASSERT_TRUE(new_corpus.has_value());
+  ASSERT_OK(domain.ValidateCorpusValue(*new_corpus));
+
+  auto new_table = domain.GetValue(*new_corpus);
+  EXPECT_EQ(new_table->b(), true);
+  EXPECT_EQ(new_table->i8(), 1);
+  EXPECT_EQ(new_table->i16(), 2);
+  EXPECT_EQ(new_table->i32(), 3);
+  EXPECT_EQ(new_table->i64(), 4);
+  EXPECT_EQ(new_table->u8(), 5);
+  EXPECT_EQ(new_table->u16(), 6);
+  EXPECT_EQ(new_table->u32(), 7);
+  EXPECT_EQ(new_table->u64(), 8);
+  EXPECT_EQ(new_table->f(), 9.0);
+  EXPECT_EQ(new_table->d(), 10.0);
+  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);
+  EXPECT_EQ(new_table->ei32(), internal::IntEnum_Second);
+  EXPECT_EQ(new_table->ei64(), internal::LongEnum_Second);
+  EXPECT_EQ(new_table->eu8(), internal::UByteEnum_Second);
+  EXPECT_EQ(new_table->eu16(), internal::UShortEnum_Second);
+  EXPECT_EQ(new_table->eu32(), internal::UIntEnum_Second);
+  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);
+  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);
+  EXPECT_EQ(new_table->s()->s().a_b()->size(), 2);
+  EXPECT_EQ(new_table->s()->s().a_b()->Get(0), true);
+  EXPECT_EQ(new_table->s()->s().a_b()->Get(1), false);
+  EXPECT_EQ(new_table->s()->a_b()->size(), 2);
+  EXPECT_EQ(new_table->s()->a_b()->Get(0), true);
+  EXPECT_EQ(new_table->s()->a_b()->Get(1), false);
+  EXPECT_EQ(new_table->s()->a_i8()->size(), 2);
+  EXPECT_EQ(new_table->s()->a_i8()->Get(0), 11);
+  EXPECT_EQ(new_table->s()->a_i8()->Get(1), 12);
+  EXPECT_EQ(new_table->s()->a_i16()->size(), 2);
+  EXPECT_EQ(new_table->s()->a_i16()->Get(0), 13);
+  EXPECT_EQ(new_table->s()->a_i16()->Get(1), 14);
+  EXPECT_EQ(new_table->s()->a_i32()->size(), 2);
+  EXPECT_EQ(new_table->s()->a_i32()->Get(0), 15);
+  EXPECT_EQ(new_table->s()->a_i32()->Get(1), 16);
+  EXPECT_EQ(new_table->s()->a_i64()->size(), 2);
+  EXPECT_EQ(new_table->s()->a_i64()->Get(0), 17);
+  EXPECT_EQ(new_table->s()->a_i64()->Get(1), 18);
+  EXPECT_EQ(new_table->s()->a_u8()->size(), 2);
+  EXPECT_EQ(new_table->s()->a_u8()->Get(0), 19);
+  EXPECT_EQ(new_table->s()->a_u8()->Get(1), 20);
+  EXPECT_EQ(new_table->s()->a_u16()->size(), 2);
+  EXPECT_EQ(new_table->s()->a_u16()->Get(0), 21);
+  EXPECT_EQ(new_table->s()->a_u16()->Get(1), 22);
+  EXPECT_EQ(new_table->s()->a_u32()->size(), 2);
+  EXPECT_EQ(new_table->s()->a_u32()->Get(0), 23);
+  EXPECT_EQ(new_table->s()->a_u32()->Get(1), 24);
+  EXPECT_EQ(new_table->s()->a_u64()->size(), 2);
+  EXPECT_EQ(new_table->s()->a_u64()->Get(0), 25);
+  EXPECT_EQ(new_table->s()->a_u64()->Get(1), 26);
+  EXPECT_EQ(new_table->s()->a_f()->size(), 2);
+  EXPECT_EQ(new_table->s()->a_f()->Get(0), 27);
+  EXPECT_EQ(new_table->s()->a_f()->Get(1), 28);
+  EXPECT_EQ(new_table->s()->a_d()->size(), 2);
+  EXPECT_EQ(new_table->s()->a_d()->Get(0), 29);
+  EXPECT_EQ(new_table->s()->a_d()->Get(1), 30);
+  EXPECT_EQ(new_table->s()->a_ei8()->size(), 2);
+  EXPECT_EQ(new_table->s()->a_ei8()->Get(0), internal::ByteEnum_First);
+  EXPECT_EQ(new_table->s()->a_ei8()->Get(1), internal::ByteEnum_Second);
+  EXPECT_EQ(new_table->s()->a_ei16()->size(), 2);
+  EXPECT_EQ(new_table->s()->a_ei16()->Get(0), internal::ShortEnum_First);
+  EXPECT_EQ(new_table->s()->a_ei16()->Get(1), internal::ShortEnum_Second);
+  EXPECT_EQ(new_table->s()->a_ei32()->size(), 2);
+  EXPECT_EQ(new_table->s()->a_ei32()->Get(0), internal::IntEnum_First);
+  EXPECT_EQ(new_table->s()->a_ei32()->Get(1), internal::IntEnum_Second);
+  EXPECT_EQ(new_table->s()->a_ei64()->size(), 2);
+  EXPECT_EQ(new_table->s()->a_ei64()->Get(0), internal::LongEnum_First);
+  EXPECT_EQ(new_table->s()->a_ei64()->Get(1), internal::LongEnum_Second);
+  EXPECT_EQ(new_table->s()->a_eu8()->size(), 2);
+  EXPECT_EQ(new_table->s()->a_eu8()->Get(0), internal::UByteEnum_First);
+  EXPECT_EQ(new_table->s()->a_eu8()->Get(1), internal::UByteEnum_Second);
+  EXPECT_EQ(new_table->s()->a_eu16()->size(), 2);
+  EXPECT_EQ(new_table->s()->a_eu16()->Get(0), internal::UShortEnum_First);
+  EXPECT_EQ(new_table->s()->a_eu16()->Get(1), internal::UShortEnum_Second);
+  EXPECT_EQ(new_table->s()->a_eu32()->size(), 2);
+  EXPECT_EQ(new_table->s()->a_eu32()->Get(0), internal::UIntEnum_First);
+  EXPECT_EQ(new_table->s()->a_eu32()->Get(1), internal::UIntEnum_Second);
+  EXPECT_EQ(new_table->s()->a_eu64()->size(), 2);
+  EXPECT_EQ(new_table->s()->a_eu64()->Get(0), internal::ULongEnum_First);
+  EXPECT_EQ(new_table->s()->a_eu64()->Get(1), internal::ULongEnum_Second);
+  EXPECT_EQ(new_table->s()->a_s()->size(), 2);
+  EXPECT_EQ(new_table->s()->a_s()->Get(0)->b(), true);
+  EXPECT_EQ(new_table->s()->a_s()->Get(0)->a_b()->size(), 2);
+  EXPECT_EQ(new_table->s()->a_s()->Get(0)->a_b()->Get(0), true);
+  EXPECT_EQ(new_table->s()->a_s()->Get(0)->a_b()->Get(1), false);
+  EXPECT_EQ(new_table->s()->a_s()->Get(1)->b(), false);
+  EXPECT_EQ(new_table->s()->a_s()->Get(1)->a_b()->size(), 2);
+  EXPECT_EQ(new_table->s()->a_s()->Get(1)->a_b()->Get(0), false);
+  EXPECT_EQ(new_table->s()->a_s()->Get(1)->a_b()->Get(1), true);
+  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());
+  EXPECT_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());
+  EXPECT_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);
+  EXPECT_EQ(v_s_0->s().a_b()->size(), 2);
+  EXPECT_EQ(v_s_0->s().a_b()->Get(0), true);
+  EXPECT_EQ(v_s_0->s().a_b()->Get(1), false);
+  EXPECT_EQ(v_s_0->a_b()->size(), 2);
+  EXPECT_EQ(v_s_0->a_b()->Get(0), true);
+  EXPECT_EQ(v_s_0->a_b()->Get(1), false);
+  EXPECT_EQ(v_s_0->a_i8()->size(), 2);
+  EXPECT_EQ(v_s_0->a_i8()->Get(0), 11);
+  EXPECT_EQ(v_s_0->a_i8()->Get(1), 12);
+  EXPECT_EQ(v_s_0->a_i16()->size(), 2);
+  EXPECT_EQ(v_s_0->a_i16()->Get(0), 13);
+  EXPECT_EQ(v_s_0->a_i16()->Get(1), 14);
+  EXPECT_EQ(v_s_0->a_i32()->size(), 2);
+  EXPECT_EQ(v_s_0->a_i32()->Get(0), 15);
+  EXPECT_EQ(v_s_0->a_i32()->Get(1), 16);
+  EXPECT_EQ(v_s_0->a_i64()->size(), 2);
+  EXPECT_EQ(v_s_0->a_i64()->Get(0), 17);
+  EXPECT_EQ(v_s_0->a_i64()->Get(1), 18);
+  EXPECT_EQ(v_s_0->a_u8()->size(), 2);
+  EXPECT_EQ(v_s_0->a_u8()->Get(0), 19);
+  EXPECT_EQ(v_s_0->a_u8()->Get(1), 20);
+  EXPECT_EQ(v_s_0->a_u16()->size(), 2);
+  EXPECT_EQ(v_s_0->a_u16()->Get(0), 21);
+  EXPECT_EQ(v_s_0->a_u16()->Get(1), 22);
+  EXPECT_EQ(v_s_0->a_u32()->size(), 2);
+  EXPECT_EQ(v_s_0->a_u32()->Get(0), 23);
+  EXPECT_EQ(v_s_0->a_u32()->Get(1), 24);
+  EXPECT_EQ(v_s_0->a_u64()->size(), 2);
+  EXPECT_EQ(v_s_0->a_u64()->Get(0), 25);
+  EXPECT_EQ(v_s_0->a_f()->size(), 2);
+  EXPECT_EQ(v_s_0->a_f()->Get(0), 27);
+  EXPECT_EQ(v_s_0->a_f()->Get(1), 28);
+  EXPECT_EQ(v_s_0->a_d()->size(), 2);
+  EXPECT_EQ(v_s_0->a_d()->Get(0), 29);
+  EXPECT_EQ(v_s_0->a_d()->Get(1), 30);
+  EXPECT_EQ(v_s_0->a_ei8()->size(), 2);
+  EXPECT_EQ(v_s_0->a_ei8()->Get(0), internal::ByteEnum_First);
+  EXPECT_EQ(v_s_0->a_ei8()->Get(1), internal::ByteEnum_Second);
+  EXPECT_EQ(v_s_0->a_ei16()->size(), 2);
+  EXPECT_EQ(v_s_0->a_ei16()->Get(0), internal::ShortEnum_First);
+  EXPECT_EQ(v_s_0->a_ei16()->Get(1), internal::ShortEnum_Second);
+  EXPECT_EQ(v_s_0->a_ei32()->size(), 2);
+  EXPECT_EQ(v_s_0->a_ei32()->Get(0), internal::IntEnum_First);
+  EXPECT_EQ(v_s_0->a_ei32()->Get(1), internal::IntEnum_Second);
+  EXPECT_EQ(v_s_0->a_ei64()->size(), 2);
+  EXPECT_EQ(v_s_0->a_ei64()->Get(0), internal::LongEnum_First);
+  EXPECT_EQ(v_s_0->a_ei64()->Get(1), internal::LongEnum_Second);
+  EXPECT_EQ(v_s_0->a_eu8()->size(), 2);
+  EXPECT_EQ(v_s_0->a_eu8()->Get(0), internal::UByteEnum_First);
+  EXPECT_EQ(v_s_0->a_eu8()->Get(1), internal::UByteEnum_Second);
+  EXPECT_EQ(v_s_0->a_eu16()->size(), 2);
+  EXPECT_EQ(v_s_0->a_eu16()->Get(0), internal::UShortEnum_First);
+  EXPECT_EQ(v_s_0->a_eu16()->Get(1), internal::UShortEnum_Second);
+  EXPECT_EQ(v_s_0->a_eu32()->size(), 2);
+  EXPECT_EQ(v_s_0->a_eu32()->Get(0), internal::UIntEnum_First);
+  EXPECT_EQ(v_s_0->a_eu32()->Get(1), internal::UIntEnum_Second);
+  EXPECT_EQ(v_s_0->a_eu64()->size(), 2);
+  EXPECT_EQ(v_s_0->a_eu64()->Get(0), internal::ULongEnum_First);
+  EXPECT_EQ(v_s_0->a_eu64()->Get(1), internal::ULongEnum_Second);
+  EXPECT_EQ(v_s_0->a_s()->size(), 2);
+  EXPECT_EQ(v_s_0->a_s()->Get(0)->b(), true);
+  EXPECT_EQ(v_s_0->a_s()->Get(0)->a_b()->size(), 2);
+  EXPECT_EQ(v_s_0->a_s()->Get(0)->a_b()->Get(0), true);
+  EXPECT_EQ(v_s_0->a_s()->Get(0)->a_b()->Get(1), false);
+  EXPECT_EQ(v_s_0->a_s()->Get(1)->b(), false);
+  EXPECT_EQ(v_s_0->a_s()->Get(1)->a_b()->size(), 2);
+  EXPECT_EQ(v_s_0->a_s()->Get(1)->a_b()->Get(0), false);
+  EXPECT_EQ(v_s_0->a_s()->Get(1)->a_b()->Get(1), true);
+}
+
+TEST(FlatbuffersTableDomainImplTest, InitGeneratesSeeds) {
+  flatbuffers::FlatBufferBuilder fbb;
+  auto table = CreateDefaultTable(fbb);
+
+  auto domain = Arbitrary<const DefaultTable*>().WithSeeds({table});
+
+  EXPECT_THAT(GenerateInitialCorpusValues(domain, IterationsToHitAll(1, 0.5)),
+              Contains(ResultOf(
+                  [table, &domain](
+                      const typename decltype(domain)::corpus_type& corpus) {
+                    return Eq(domain.GetValue(corpus), table);
+                  },
+                  IsTrue())));
+}
+
+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},
+      {"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*>();
+
+  absl::BitGen bitgen;
+  for (size_t i = 0; i < IterationsToHitAll(mutated_fields.size(),
+                                            1.0 / mutated_fields.size());
+       ++i) {
+    Value initial_val(domain, bitgen);
+    Value val(initial_val);
+    val.Mutate(domain, bitgen, {}, false);
+    const auto& mut = val.user_value;
+    const auto& init = initial_val.user_value;
+
+    mutated_fields["b"] |= mut->b() != init->b();
+    mutated_fields["i8"] |= mut->i8() != init->i8();
+    mutated_fields["i16"] |= mut->i16() != init->i16();
+    mutated_fields["i32"] |= mut->i32() != init->i32();
+    mutated_fields["i64"] |= mut->i64() != init->i64();
+    mutated_fields["u8"] |= mut->u8() != init->u8();
+    mutated_fields["u16"] |= mut->u16() != init->u16();
+    mutated_fields["u32"] |= mut->u32() != init->u32();
+    mutated_fields["u64"] |= mut->u64() != init->u64();
+    mutated_fields["f"] |= mut->f() != init->f();
+    mutated_fields["d"] |= mut->d() != init->d();
+    mutated_fields["str"] |= !Eq(mut->str(), init->str());
+    mutated_fields["ei8"] |= mut->ei8() != init->ei8();
+    mutated_fields["ei16"] |= mut->ei16() != init->ei16();
+    mutated_fields["ei32"] |= mut->ei32() != init->ei32();
+    mutated_fields["ei64"] |= mut->ei64() != init->ei64();
+    mutated_fields["eu8"] |= mut->eu8() != init->eu8();
+    mutated_fields["eu16"] |= mut->eu16() != init->eu16();
+    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"] |= !VectorEq(mut->v_b(), init->v_b());
+    mutated_fields["v_i8"] |= !VectorEq(mut->v_i8(), init->v_i8());
+    mutated_fields["v_i16"] |= !VectorEq(mut->v_i16(), init->v_i16());
+    mutated_fields["v_i32"] |= !VectorEq(mut->v_i32(), init->v_i32());
+    mutated_fields["v_i64"] |= !VectorEq(mut->v_i64(), init->v_i64());
+    mutated_fields["v_u8"] |= !VectorEq(mut->v_u8(), init->v_u8());
+    mutated_fields["v_u16"] |= !VectorEq(mut->v_u16(), init->v_u16());
+    mutated_fields["v_u32"] |= !VectorEq(mut->v_u32(), init->v_u32());
+    mutated_fields["v_u64"] |= !VectorEq(mut->v_u64(), init->v_u64());
+    mutated_fields["v_f"] |= !VectorEq(mut->v_f(), init->v_f());
+    mutated_fields["v_d"] |= !VectorEq(mut->v_d(), init->v_d());
+    mutated_fields["v_str"] |= !VectorEq(mut->v_str(), init->v_str());
+    mutated_fields["v_ei8"] |= !VectorEq(mut->v_ei8(), init->v_ei8());
+    mutated_fields["v_ei16"] |= !VectorEq(mut->v_ei16(), init->v_ei16());
+    mutated_fields["v_ei32"] |= !VectorEq(mut->v_ei32(), init->v_ei32());
+    mutated_fields["v_ei64"] |= !VectorEq(mut->v_ei64(), init->v_ei64());
+    mutated_fields["v_eu8"] |= !VectorEq(mut->v_eu8(), init->v_eu8());
+    mutated_fields["v_eu16"] |= !VectorEq(mut->v_eu16(), init->v_eu16());
+    mutated_fields["v_eu32"] |= !VectorEq(mut->v_eu32(), init->v_eu32());
+    mutated_fields["v_eu64"] |= !VectorEq(mut->v_eu64(), init->v_eu64());
+    mutated_fields["v_t"] |= !VectorEq(mut->v_str(), init->v_str());
+    mutated_fields["v_u_type"] |= !VectorEq(mut->v_u_type(), init->v_u_type());
+    mutated_fields["v_u"] |= !VectorUnionEq(mut->v_u_type(), mut->v_u(),
+                                            init->v_u_type(), init->v_u());
+    mutated_fields["v_s"] |= !VectorEq(mut->v_s(), init->v_s());
+
+    if (std::all_of(mutated_fields.begin(), mutated_fields.end(),
+                    [](const auto& p) { return p.second; })) {
+      break;
+    }
+  }
+
+  EXPECT_THAT(mutated_fields, Each(Pair(_, true)));
+}
+
+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
+                                          1,                            // i8
+                                          2,                            // i16
+                                          3,                            // i32
+                                          4,                            // i64
+                                          5,                            // u8
+                                          6,                            // u16
+                                          7,                            // u32
+                                          8,                            // u64
+                                          9.0,                          // f
+                                          10.0,                         // d
+                                          "foo bar baz",                // str
+                                          internal::ByteEnum_Second,    // ei8
+                                          internal::ShortEnum_Second,   // ei16
+                                          internal::IntEnum_Second,     // ei32
+                                          internal::LongEnum_Second,    // ei64
+                                          internal::UByteEnum_Second,   // eu8
+                                          internal::UShortEnum_Second,  // eu16
+                                          internal::UIntEnum_Second,    // eu32
+                                          internal::ULongEnum_Second,   // eu64
+                                          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());
+
+  auto domain = Arbitrary<const OptionalTable*>();
+  Value val(domain, table);
+  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},
+      {"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},
+  };
+
+  for (size_t i = 0; i < 10'000'000; ++i) {
+    val.Mutate(domain, bitgen, {}, true);
+    const auto& v = val.user_value;
+
+    null_fields["b"] |= !v->b().has_value();
+    null_fields["i8"] |= !v->i8().has_value();
+    null_fields["i16"] |= !v->i16().has_value();
+    null_fields["i32"] |= !v->i32().has_value();
+    null_fields["i64"] |= !v->i64().has_value();
+    null_fields["u8"] |= !v->u8().has_value();
+    null_fields["u16"] |= !v->u16().has_value();
+    null_fields["u32"] |= !v->u32().has_value();
+    null_fields["u64"] |= !v->u64().has_value();
+    null_fields["f"] |= !v->f().has_value();
+    null_fields["d"] |= !v->d().has_value();
+    null_fields["str"] |= v->str() == nullptr;
+    null_fields["ei8"] |= !v->ei8().has_value();
+    null_fields["ei16"] |= !v->ei16().has_value();
+    null_fields["ei32"] |= !v->ei32().has_value();
+    null_fields["ei64"] |= !v->ei64().has_value();
+    null_fields["eu8"] |= !v->eu8().has_value();
+    null_fields["eu16"] |= !v->eu16().has_value();
+    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; })) {
+      break;
+    }
+  }
+
+  EXPECT_THAT(null_fields, Each(Pair(_, true)));
+}
+
+TEST(FlatbuffersTableDomainImplTest, RequiredTableFieldsAlwaysSet) {
+  auto domain = Arbitrary<const RequiredTable*>();
+
+  EXPECT_THAT(GenerateNonUniqueCorpusValues(
+                  domain,
+                  /*num_seeds=*/1,
+                  /*num_mutations=*/IterationsToHitAll(1, 1.0 / 100), {},
+                  /*only_shrink=*/true),
+              Each(ResultOf(
+                  [&](const typename decltype(domain)::corpus_type& corpus) {
+                    auto value = domain.GetValue(corpus);
+                    return value->str() == nullptr && value->t() == nullptr;
+                  },
+                  IsFalse())));
+}
+
+TEST(FlatbuffersTableDomainImplTest, Printer) {
+  flatbuffers::FlatBufferBuilder fbb;
+  auto table = CreateDefaultTable(fbb);
+  auto domain = Arbitrary<const DefaultTable*>();
+  auto corpus = domain.FromValue(table);
+  ASSERT_TRUE(corpus.has_value());
+
+  auto printer = domain.GetPrinter();
+  std::string out;
+  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
+            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, a_b: {true, false}}, a_b: "
+                "{true, false}, a_i8: {11, 12}, a_i16: {13, 14}, a_i32: {15, "
+                "16}, a_i64: {17, 18}, a_u8: {19, 20}, a_u16: {21, 22}, a_u32: "
+                "{23, 24}, a_u64: {25, 26}, a_f: {27.f, 28.f}, a_d: {29., "
+                "30.}, a_ei8: {First, Second}, a_ei16: {First, Second}, "
+                "a_ei32: {First, Second}, a_ei64: {First, Second}, a_eu8: "
+                "{First, Second}, a_eu16: {First, Second}, a_eu32: {First, "
+                "Second}, a_eu64: {First, Second}, a_s: {{b: true, a_b: {true, "
+                "false}}, {b: false, a_b: {false, 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, a_b: {true, false}}, a_b: "
+                "{true, false}, a_i8: {11, 12}, a_i16: {13, 14}, a_i32: {15, "
+                "16}, a_i64: {17, 18}, a_u8: {19, 20}, a_u16: {21, 22}, a_u32: "
+                "{23, 24}, a_u64: {25, 26}, a_f: {27.f, 28.f}, a_d: {29., "
+                "30.}, a_ei8: {First, Second}, a_ei16: {First, Second}, "
+                "a_ei32: {First, Second}, a_ei64: {First, Second}, a_eu8: "
+                "{First, Second}, a_eu16: {First, Second}, a_eu32: {First, "
+                "Second}, a_eu64: {First, Second}, a_s: {{b: true, a_b: {true, "
+                "false}}, {b: false, a_b: {false, true}}}}})}")  // v_s
+            ));
+}
+
+TEST(FlatbuffersTableDomainImplTest, MutateAlwaysChangesValues) {
+  auto domain = Arbitrary<const DefaultTable*>();
+  const reflection::Schema* schema =
+      reflection::GetSchema(DefaultTable::BinarySchema::data());
+  const reflection::Object* object =
+      schema->objects()->LookupByKey(DefaultTable::GetFullyQualifiedName());
+
+  absl::BitGen bitgen;
+  size_t iterations = IterationsToHitAll(
+      object->fields()->size(), 1.0 / (object->fields()->size() * 0.5));
+  std::vector<typename decltype(domain)::corpus_type> corpus_values{iterations};
+  corpus_values.emplace_back(domain.Init(bitgen));
+  while (corpus_values.size() < iterations) {
+    auto corpus_value = corpus_values.back();
+    domain.Mutate(corpus_value, bitgen, {}, false);
+    corpus_values.push_back(corpus_value);
+  }
+
+  std::vector<bool> has_changed;
+  has_changed.reserve(corpus_values.size() - 1);
+  for (size_t i = 0; i < corpus_values.size() - 2; ++i) {
+    has_changed.push_back(Eq(domain.GetValue(corpus_values[i]),
+                             domain.GetValue(corpus_values[i + 1])));
+  }
+
+  EXPECT_THAT(has_changed, Each(IsTrue()));
+}
+
+// Check that the domain is properly deduced by the macros.
+void FnUnderTest(const DefaultTable* table) { EXPECT_THAT(table, NotNull()); }
+FUZZ_TEST(MacrosTest, FnUnderTest);
+
+TEST(FlatbuffersTableDomainImplTest, CountNumberOfFieldsWithNull) {
+  flatbuffers::FlatBufferBuilder fbb;
+  auto table_offset = internal::CreateOptionalTableDirect(fbb);
+  fbb.Finish(table_offset);
+  auto table = flatbuffers::GetRoot<OptionalTable>(fbb.GetBufferPointer());
+
+  auto domain = Arbitrary<const OptionalTable*>();
+  auto corpus = domain.FromValue(table);
+  ASSERT_TRUE(corpus.has_value());
+  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());
+  }
+}
+
+}  // namespace
+}  // namespace fuzztest
diff --git a/e2e_tests/functional_test.cc b/e2e_tests/functional_test.cc
index 9ee676e..b9b8ec8 100644
--- a/e2e_tests/functional_test.cc
+++ b/e2e_tests/functional_test.cc
@@ -1857,6 +1857,33 @@
   ExpectTargetAbort(status, std_err);
 }
 
+TEST_P(FuzzingModeCrashFindingTest, FlatbuffersFailsWhenFieldsAreNotDefault) {
+  TempDir out_dir;
+  auto [status, std_out, std_err] =
+      Run("MySuite.FlatbuffersFailsWhenFieldsAreNotDefault");
+  EXPECT_THAT(std_err, HasSubstr("argument 0: {b: ("));
+  EXPECT_THAT(std_err, HasSubstr("i8: ("));
+  EXPECT_THAT(std_err, HasSubstr("i16: ("));
+  EXPECT_THAT(std_err, HasSubstr("i32: ("));
+  EXPECT_THAT(std_err, HasSubstr("i64: ("));
+  EXPECT_THAT(std_err, HasSubstr("u8: ("));
+  EXPECT_THAT(std_err, HasSubstr("u16: ("));
+  EXPECT_THAT(std_err, HasSubstr("u32: ("));
+  EXPECT_THAT(std_err, HasSubstr("u64: ("));
+  EXPECT_THAT(std_err, HasSubstr("f: ("));
+  EXPECT_THAT(std_err, HasSubstr("d: ("));
+  EXPECT_THAT(std_err, HasSubstr("str: "));
+  EXPECT_THAT(std_err, HasSubstr("ei8: ("));
+  EXPECT_THAT(std_err, HasSubstr("ei16: ("));
+  EXPECT_THAT(std_err, HasSubstr("ei32: ("));
+  EXPECT_THAT(std_err, HasSubstr("ei64: ("));
+  EXPECT_THAT(std_err, HasSubstr("eu8: ("));
+  EXPECT_THAT(std_err, HasSubstr("eu16: ("));
+  EXPECT_THAT(std_err, HasSubstr("eu32: ("));
+  EXPECT_THAT(std_err, HasSubstr("eu64: ("));
+  ExpectTargetAbort(status, std_err);
+}
+
 INSTANTIATE_TEST_SUITE_P(FuzzingModeCrashFindingTestWithExecutionModel,
                          FuzzingModeCrashFindingTest,
                          testing::ValuesIn(GetAvailableExecutionModels()));
diff --git a/e2e_tests/testdata/BUILD b/e2e_tests/testdata/BUILD
index 3c623f3..2abbffa 100644
--- a/e2e_tests/testdata/BUILD
+++ b/e2e_tests/testdata/BUILD
@@ -101,9 +101,11 @@
         "@abseil-cpp//absl/time",
         "@abseil-cpp//absl/types:span",
         "@com_google_fuzztest//fuzztest",
+        "@com_google_fuzztest//fuzztest:flatbuffers",
         "@com_google_fuzztest//fuzztest:fuzztest_gtest_main",
         "@com_google_fuzztest//fuzztest:googletest_fixture_adapter",
         "@com_google_fuzztest//fuzztest/internal:logging",
+        "@com_google_fuzztest//fuzztest/internal:test_flatbuffers_cc_fbs",
         "@com_google_fuzztest//fuzztest/internal:test_protobuf_cc_proto",
         "@protobuf",
         "@re2",
diff --git a/e2e_tests/testdata/CMakeLists.txt b/e2e_tests/testdata/CMakeLists.txt
index 4e802b1..946d373 100644
--- a/e2e_tests/testdata/CMakeLists.txt
+++ b/e2e_tests/testdata/CMakeLists.txt
@@ -39,8 +39,10 @@
   absl::strings
   absl::time
   re2::re2
+  fuzztest_flatbuffers
   fuzztest_googletest_fixture_adapter
   fuzztest_logging
+  fuzztest_test_flatbuffers_headers
 )
 link_fuzztest(fuzz_tests_for_functional_testing.stripped)
 set_target_properties(
diff --git a/e2e_tests/testdata/fuzz_tests_for_functional_testing.cc b/e2e_tests/testdata/fuzz_tests_for_functional_testing.cc
index 8049505..b23a49a 100644
--- a/e2e_tests/testdata/fuzz_tests_for_functional_testing.cc
+++ b/e2e_tests/testdata/fuzz_tests_for_functional_testing.cc
@@ -30,6 +30,7 @@
 #include <utility>
 #include <vector>
 
+#include "./fuzztest/flatbuffers.h"  // IWYU pragma: keep
 #include "./fuzztest/fuzztest.h"
 #include "absl/algorithm/container.h"
 #include "absl/functional/function_ref.h"
@@ -39,6 +40,7 @@
 #include "absl/time/clock.h"
 #include "absl/time/time.h"
 #include "./fuzztest/internal/logging.h"
+#include "./fuzztest/internal/test_flatbuffers_generated.h"
 #include "./fuzztest/internal/test_protobuf.pb.h"
 #include "google/protobuf/descriptor.h"
 #include "google/protobuf/message.h"
@@ -56,6 +58,7 @@
 using ::fuzztest::StructOf;
 using ::fuzztest::TupleOf;
 using ::fuzztest::VectorOf;
+using ::fuzztest::internal::DefaultTable;
 using ::fuzztest::internal::ProtoExtender;
 using ::fuzztest::internal::SingleInt32Field;
 using ::fuzztest::internal::TestProtobuf;
@@ -847,4 +850,68 @@
 };
 FUZZ_TEST_F(FaultySetupTest, NoOp);
 
+void FlatbuffersFailsWhenFieldsAreNotDefault(const DefaultTable* table) {
+  // Abort if any of the fields are not set to their default values.
+  if (table->b() != false) {
+    std::abort();
+  }
+  if (table->i8() != 0) {
+    std::abort();
+  }
+  if (table->i16() != 0) {
+    std::abort();
+  }
+  if (table->i32() != 0) {
+    std::abort();
+  }
+  if (table->i64() != 0) {
+    std::abort();
+  }
+  if (table->u8() != 0) {
+    std::abort();
+  }
+  if (table->u16() != 0) {
+    std::abort();
+  }
+  if (table->u32() != 0) {
+    std::abort();
+  }
+  if (table->u64() != 0) {
+    std::abort();
+  }
+  if (table->f() != 0.0f) {
+    std::abort();
+  }
+  if (table->d() != 0.0) {
+    std::abort();
+  }
+  if (table->str() != nullptr) {
+    std::abort();
+  }
+  if (table->ei8() != fuzztest::internal::ByteEnum_First) {
+    std::abort();
+  }
+  if (table->ei16() != fuzztest::internal::ShortEnum_First) {
+    std::abort();
+  }
+  if (table->ei32() != fuzztest::internal::IntEnum_First) {
+    std::abort();
+  }
+  if (table->ei64() != fuzztest::internal::LongEnum_First) {
+    std::abort();
+  }
+  if (table->eu8() != fuzztest::internal::UByteEnum_First) {
+    std::abort();
+  }
+  if (table->eu16() != fuzztest::internal::UShortEnum_First) {
+    std::abort();
+  }
+  if (table->eu32() != fuzztest::internal::UIntEnum_First) {
+    std::abort();
+  }
+  if (table->eu64() != fuzztest::internal::ULongEnum_First) {
+    std::abort();
+  }
+}
+FUZZ_TEST(MySuite, FlatbuffersFailsWhenFieldsAreNotDefault);
 }  // namespace
diff --git a/fuzztest/BUILD b/fuzztest/BUILD
index 7dc2a14..d184dc1 100644
--- a/fuzztest/BUILD
+++ b/fuzztest/BUILD
@@ -205,6 +205,14 @@
 )
 
 cc_library(
+    name = "flatbuffers",
+    hdrs = ["flatbuffers.h"],
+    deps = [
+        "@com_google_fuzztest//fuzztest/internal/domains:flatbuffers_domain_impl",
+    ],
+)
+
+cc_library(
     name = "fuzzing_bit_gen",
     srcs = ["fuzzing_bit_gen.cc"],
     hdrs = ["fuzzing_bit_gen.h"],
diff --git a/fuzztest/CMakeLists.txt b/fuzztest/CMakeLists.txt
index 7d191f7..5a664f2 100644
--- a/fuzztest/CMakeLists.txt
+++ b/fuzztest/CMakeLists.txt
@@ -88,6 +88,40 @@
     fuzztest::fuzztest_macros
 )
 
+if (FUZZTEST_BUILD_FLATBUFFERS)
+  fuzztest_cc_library(
+    NAME
+      flatbuffers
+    HDRS
+      "flatbuffers.h"
+      "internal/domains/flatbuffers_domain_impl.h"
+    SRCS
+      "internal/domains/flatbuffers_domain_impl.cc"
+    DEPS
+      absl::algorithm_container
+      absl::core_headers
+      absl::flat_hash_map
+      absl::flat_hash_set
+      absl::nullability
+      absl::random_bit_gen_ref
+      absl::random_distributions
+      absl::random_random
+      absl::status
+      absl::statusor
+      absl::str_format
+      absl::strings
+      absl::synchronization
+      flatbuffers
+      fuzztest::any
+      fuzztest::domain_core
+      fuzztest::logging
+      fuzztest::meta
+      fuzztest::serialization
+      fuzztest::status
+      fuzztest::type_support
+  )
+endif()
+
 fuzztest_cc_library(
   NAME
     fuzztest_gtest_main
diff --git a/fuzztest/flatbuffers.h b/fuzztest/flatbuffers.h
new file mode 100644
index 0000000..b70ed36
--- /dev/null
+++ b/fuzztest/flatbuffers.h
@@ -0,0 +1,19 @@
+// 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.
+
+#ifndef FUZZTEST_FUZZTEST_FLATBUFFERS_H_
+#define FUZZTEST_FUZZTEST_FLATBUFFERS_H_
+
+#include "./fuzztest/internal/domains/flatbuffers_domain_impl.h"  // IWYU pragma: export
+#endif  // FUZZTEST_FUZZTEST_FLATBUFFERS_H_
diff --git a/fuzztest/internal/BUILD b/fuzztest/internal/BUILD
index 5f88da0..c79706d 100644
--- a/fuzztest/internal/BUILD
+++ b/fuzztest/internal/BUILD
@@ -12,6 +12,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+load("@flatbuffers//:build_defs.bzl", "flatbuffer_library_public")
 load("@rules_proto//proto:defs.bzl", "proto_library")
 
 package(default_visibility = ["@com_google_fuzztest//:__subpackages__"])
@@ -539,3 +540,35 @@
         "@protobuf",
     ],
 )
+
+flatbuffer_library_public(
+    name = "test_flatbuffers_fbs",
+    srcs = ["test_flatbuffers.fbs"],
+    outs = [
+        "test_flatbuffers_bfbs_generated.h",
+        "test_flatbuffers_generated.h",
+    ],
+    flatc_args = [
+        "--no-union-value-namespacing",
+        "--gen-name-strings",
+        "--bfbs-gen-embed",
+    ],
+    language_flag = "-c",
+)
+
+cc_library(
+    name = "test_flatbuffers_cc_fbs",
+    srcs = [":test_flatbuffers_fbs"],
+    hdrs = [":test_flatbuffers_fbs"],
+    features = ["-parse_headers"],
+    deps = ["@flatbuffers//:runtime_cc"],
+)
+
+# Stops build_cleaner from generating flatbuffers_library for the test flatbuffers schema.
+filegroup(
+    name = "build_cleaner_ignore",
+    srcs = [
+        "test_flatbuffers_fbs",
+    ],
+    tags = ["ignore_srcs"],
+)
diff --git a/fuzztest/internal/CMakeLists.txt b/fuzztest/internal/CMakeLists.txt
index 8fd2e4e..61f6da8 100644
--- a/fuzztest/internal/CMakeLists.txt
+++ b/fuzztest/internal/CMakeLists.txt
@@ -494,6 +494,21 @@
   TESTONLY
 )
 
+if (FUZZTEST_BUILD_FLATBUFFERS)
+  fuzztest_flatbuffers_generate_headers(
+    TARGET 
+      test_flatbuffers_headers
+    SCHEMAS
+      "test_flatbuffers.fbs"
+    FLAGS 
+      --bfbs-gen-embed --gen-name-strings
+    TESTONLY
+  )
+  add_dependencies(fuzztest_test_flatbuffers_headers
+    fuzztest_GENERATE_test_flatbuffers_headers
+  )
+endif()
+
 fuzztest_cc_library(
   NAME
     type_support
diff --git a/fuzztest/internal/domains/BUILD b/fuzztest/internal/domains/BUILD
index 5a1d738..e15e2cc 100644
--- a/fuzztest/internal/domains/BUILD
+++ b/fuzztest/internal/domains/BUILD
@@ -188,6 +188,33 @@
 )
 
 cc_library(
+    name = "flatbuffers_domain_impl",
+    srcs = ["flatbuffers_domain_impl.cc"],
+    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:flat_hash_map",
+        "@abseil-cpp//absl/random:bit_gen_ref",
+        "@abseil-cpp//absl/random:distributions",
+        "@abseil-cpp//absl/status",
+        "@abseil-cpp//absl/strings",
+        "@abseil-cpp//absl/strings:str_format",
+        "@abseil-cpp//absl/synchronization",
+        "@com_google_fuzztest//fuzztest:domain_core",
+        "@com_google_fuzztest//fuzztest/internal:any",
+        "@com_google_fuzztest//fuzztest/internal:logging",
+        "@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",
+    ],
+)
+
+cc_library(
     name = "regexp_dfa",
     srcs = ["regexp_dfa.cc"],
     hdrs = ["regexp_dfa.h"],
diff --git a/fuzztest/internal/domains/arbitrary_impl.h b/fuzztest/internal/domains/arbitrary_impl.h
index d151d50..f6b1702 100644
--- a/fuzztest/internal/domains/arbitrary_impl.h
+++ b/fuzztest/internal/domains/arbitrary_impl.h
@@ -458,7 +458,9 @@
                         // Monostates have their own domain.
                         !is_monostate_v<T> &&
                         // std::array uses the Tuple domain.
-                        !is_array_v<T>>>
+                        !is_array_v<T> &&
+                        // Flatbuffers tables have their own domain.
+                        !is_flatbuffers_table_v<T>>>
     : public decltype(DetectAggregateOfImpl<T>()) {};
 
 // Arbitrary for std::pair.
diff --git a/fuzztest/internal/domains/flatbuffers_domain_impl.cc b/fuzztest/internal/domains/flatbuffers_domain_impl.cc
new file mode 100644
index 0000000..4c0bb8c
--- /dev/null
+++ b/fuzztest/internal/domains/flatbuffers_domain_impl.cc
@@ -0,0 +1,483 @@
+// 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.
+
+#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 "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 "./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 {
+
+// 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});
+}
+
+// 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});
+}
+
+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;
+
+  // Prepare `union_choice`.
+  auto selected_type_enumval_index =
+      absl::Uniform(prng, 0ul, union_def_->values()->size());
+  auto type_enumval = union_def_->values()->Get(selected_type_enumval_index);
+  if (type_enumval == nullptr) {
+    return val;
+  }
+  auto type_value = type_domain_.FromValue(type_enumval->value());
+  if (!type_value.has_value()) {
+    return val;
+  }
+  val.type = *type_value;
+
+  // FlatBuffers reserves the enumeration constant NONE (encoded as 0) to mean
+  // that the union field is not set.
+  if (type_enumval->value() == 0 /* NONE */) {
+    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 FlatbuffersUnionDomainImpl::Mutate(
+    corpus_type& corpus_value, absl::BitGenRef prng,
+    const domain_implementor::MutationMetadata& metadata, bool only_shrink) {
+  auto total_weight = CountNumberOfFields(corpus_value);
+  auto selected_weight = absl::Uniform(prng, 0ul, total_weight);
+  if (selected_weight == 0) {
+    // Mutate both type and value.
+
+    // Deal with the type.
+    type_domain_.Mutate(corpus_value.type, prng, metadata, only_shrink);
+    corpus_value.value =
+        GenericDomainCorpusType(std::in_place_type<void*>, nullptr);
+    auto type_value = type_domain_.GetValue(corpus_value.type);
+    if (type_value == 0 /* NONE */) {
+      // NONE is a special value, it means that the union is not set.
+      return;
+    }
+    auto type_enumval = union_def_->values()->LookupByKey(type_value);
+    if (type_enumval == nullptr) {
+      return;
+    }
+
+    // Deal with the value.
+    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);
+      corpus_value.value = std::move(inner_val);
+    } else {
+      auto inner_val =
+          GetCachedDomain<FlatbuffersTableTag>(*type_enumval).Init(prng);
+      corpus_value.value = std::move(inner_val);
+    }
+  } else {
+    // Keep the type, mutate the value.
+    auto type_value = type_domain_.GetValue(corpus_value.type);
+    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.MutateSelectedField(corpus_value.value, prng, metadata,
+                                 only_shrink, selected_weight - 1);
+    } else {
+      auto domain = GetCachedDomain<FlatbuffersTableTag>(*type_enumval);
+      domain.MutateSelectedField(corpus_value.value, prng, metadata,
+                                 only_shrink, selected_weight - 1);
+    }
+  }
+}
+
+uint64_t FlatbuffersUnionDomainImpl::CountNumberOfFields(
+    corpus_type& corpus_value) {
+  // Unions are encoded as the combination of two fields: an enum representing
+  // the union choice and the offset to the actual element.
+  //
+  // In turn, count starts with 1 to take care of the first field.
+  uint64_t count = 1;
+  auto type_value = type_domain_.GetValue(corpus_value.type);
+  if (type_value == 0 /* NONE */) {
+    // Union field is not set.
+    return count;
+  }
+  auto type_enumval = union_def_->values()->LookupByKey(type_value);
+  if (type_enumval == nullptr) {
+    return count;
+  }
+  const reflection::Object* object =
+      schema_->objects()->Get(type_enumval->union_type()->index());
+  if (object->is_struct()) {
+    auto domain = GetCachedDomain<FlatbuffersStructTag>(*type_enumval);
+    count += domain.CountNumberOfFields(corpus_value.value);
+  } else {
+    auto domain = GetCachedDomain<FlatbuffersTableTag>(*type_enumval);
+    count += domain.CountNumberOfFields(corpus_value.value);
+  }
+  return count;
+}
+
+absl::Status FlatbuffersUnionDomainImpl::ValidateCorpusValue(
+    const corpus_type& corpus_value) const {
+  // 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);
+  if (type_value == 0 /* NONE */) {
+    // Union field is not set.
+    return absl::OkStatus();
+  }
+  auto type_enumval = union_def_->values()->LookupByKey(type_value);
+  if (type_enumval == nullptr) {
+    return absl::InvalidArgumentError(
+        absl::StrCat("Invalid union type: ", type_value));
+  }
+
+  // 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_value = type_domain_.FromValue(value.type);
+  if (type_value.has_value()) {
+    out->type = *type_value;
+  }
+  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() ||
+      !type_domain_.ValidateCorpusValue(*type_corpus).ok()) {
+    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);
+  if (type_value == 0 /* NONE */) {
+    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));
+
+  auto type_enumval = union_def_->values()->LookupByKey(type_value);
+  if (type_enumval == nullptr) {
+    return out;
+  }
+
+  // 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::FlatBufferBuilder& 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 || type_value == 0 /* NONE */ ||
+      !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());
+  if (type_value == 0 /* NONE */) {
+    absl::Format(out, "NONE");
+  } else {
+    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 ret;
+  for (const auto* field : *object_->fields()) {
+    VisitFlatbufferField(schema_, field, FromValueVisitor{*this, value, ret});
+  }
+  return ret;
+}
+
+std::optional<flatbuffers::uoffset_t>
+FlatbuffersStructUntypedDomainImpl::BuildValue(
+    const corpus_type& value, flatbuffers::FlatBufferBuilder& 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 : *object_->fields()) {
+    VisitFlatbufferField(schema_, field, BuildValueVisitor{*this, value, buf});
+  }
+}
+
+// Converts the table pointer to a corpus value.
+std::optional<FlatbuffersTableUntypedDomainImpl::corpus_type>
+FlatbuffersTableUntypedDomainImpl::FromValue(const value_type& value) const {
+  if (value == nullptr) {
+    return std::nullopt;
+  }
+  corpus_type ret;
+  for (const auto* field : *object_->fields()) {
+    VisitFlatbufferField(schema_, field, FromValueVisitor{*this, value, ret});
+  }
+  return ret;
+}
+
+uint32_t FlatbuffersTableUntypedDomainImpl::BuildTable(
+    const corpus_type& value, flatbuffers::FlatBufferBuilder& builder) const {
+  // Add all the fields to the builder.
+
+  // Offsets is the map of field id to its offset in the table.
+  absl::flat_hash_map<typename corpus_type::key_type, flatbuffers::uoffset_t>
+      offsets;
+
+  // Some fields are stored inline in the flatbuffer table itself (a.k.a
+  // "inline fields") and some are referenced by their offsets (a.k.a. "out of
+  // line fields").
+  //
+  // "Out of line fields" shall be added to the builder first, so that we can
+  // refer to them in the final table.
+  for (const auto& [id, field_corpus] : value) {
+    const reflection::Field* absl_nullable field = GetFieldById(id);
+    if (field == nullptr) {
+      continue;
+    }
+    // Take care of strings, and tables.
+    VisitFlatbufferField(
+        schema_, field,
+        TableFieldBuilderVisitor{*this, builder, offsets, field_corpus});
+  }
+
+  // Now it is time to build the final table.
+  uint32_t table_start = builder.StartTable();
+  for (const auto& [id, field_corpus] : value) {
+    const reflection::Field* absl_nullable field = GetFieldById(id);
+    if (field == nullptr) {
+      continue;
+    }
+
+    // Visit all fields.
+    //
+    // Inline fields will be stored in the table itself, out of line fields
+    // will be referenced by their offsets.
+    VisitFlatbufferField(
+        schema_, field,
+        TableBuilderVisitor{*this, builder, offsets, field_corpus});
+  }
+  return builder.EndTable(table_start);
+}
+}  // namespace fuzztest::internal
diff --git a/fuzztest/internal/domains/flatbuffers_domain_impl.h b/fuzztest/internal/domains/flatbuffers_domain_impl.h
new file mode 100644
index 0000000..767a8e1
--- /dev/null
+++ b/fuzztest/internal/domains/flatbuffers_domain_impl.h
@@ -0,0 +1,2031 @@
+// 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.
+
+#ifndef FUZZTEST_FUZZTEST_INTERNAL_DOMAINS_FLATBUFFERS_DOMAIN_IMPL_H_
+#define FUZZTEST_FUZZTEST_INTERNAL_DOMAINS_FLATBUFFERS_DOMAIN_IMPL_H_
+
+#include <algorithm>
+#include <cstddef>
+#include <cstdint>
+#include <limits>
+#include <list>
+#include <optional>
+#include <string>
+#include <type_traits>
+#include <utility>
+#include <variant>
+#include <vector>
+
+#include "absl/algorithm/container.h"
+#include "absl/base/nullability.h"
+#include "absl/base/thread_annotations.h"
+#include "absl/container/flat_hash_map.h"
+#include "absl/random/bit_gen_ref.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/string.h"
+#include "flatbuffers/struct.h"
+#include "flatbuffers/table.h"
+#include "flatbuffers/vector.h"
+#include "flatbuffers/verifier.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/logging.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 {
+
+//
+// Flatbuffers enum detection.
+//
+template <typename Underlying,
+          typename = std::enable_if_t<std::is_integral_v<Underlying> &&
+                                      !std::is_same_v<Underlying, bool>>>
+struct FlatbuffersEnumTag {
+  using type = Underlying;
+};
+
+template <typename T>
+struct is_flatbuffers_enum_tag : std::false_type {};
+
+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;
+
+//
+// Flatbuffers array detection.
+//
+template <typename T>
+struct FlatbuffersArrayTag {
+  using value_type = T;
+};
+
+template <typename T>
+struct is_flatbuffers_array_tag : std::false_type {};
+
+template <typename T>
+struct is_flatbuffers_array_tag<FlatbuffersArrayTag<T>> : std::true_type {};
+
+template <typename T>
+inline constexpr bool is_flatbuffers_array_tag_v =
+    is_flatbuffers_array_tag<T>::value;
+
+struct FlatbuffersTableTag;
+struct FlatbuffersStructTag;
+struct FlatbuffersUnionTag;
+
+// Dynamic to static dispatch visitor pattern for flatbuffers container
+// elements.
+template <template <typename> typename ContainerTag, typename Visitor>
+auto VisitFlatbufferContainerElementField(const reflection::Schema* schema,
+                                          const reflection::Field* field,
+                                          Visitor visitor) {
+  auto field_index = field->type()->index();
+  auto element_type = field->type()->element();
+  switch (element_type) {
+    case reflection::BaseType::Bool:
+      visitor.template Visit<ContainerTag<bool>>(field);
+      break;
+    case reflection::BaseType::Byte:
+      if (field_index >= 0) {
+        visitor.template Visit<ContainerTag<FlatbuffersEnumTag<int8_t>>>(field);
+      } else {
+        visitor.template Visit<ContainerTag<int8_t>>(field);
+      }
+      break;
+    case reflection::BaseType::Short:
+      if (field_index >= 0) {
+        visitor.template Visit<ContainerTag<FlatbuffersEnumTag<int16_t>>>(
+            field);
+      } else {
+        visitor.template Visit<ContainerTag<int16_t>>(field);
+      }
+      break;
+    case reflection::BaseType::Int:
+      if (field_index >= 0) {
+        visitor.template Visit<ContainerTag<FlatbuffersEnumTag<int32_t>>>(
+            field);
+      } else {
+        visitor.template Visit<ContainerTag<int32_t>>(field);
+      }
+      break;
+    case reflection::BaseType::Long:
+      if (field_index >= 0) {
+        visitor.template Visit<ContainerTag<FlatbuffersEnumTag<int64_t>>>(
+            field);
+      } else {
+        visitor.template Visit<ContainerTag<int64_t>>(field);
+      }
+      break;
+    case reflection::BaseType::UByte:
+      if (field_index >= 0) {
+        visitor.template Visit<ContainerTag<FlatbuffersEnumTag<uint8_t>>>(
+            field);
+      } else {
+        visitor.template Visit<ContainerTag<uint8_t>>(field);
+      }
+      break;
+    case reflection::BaseType::UShort:
+      if (field_index >= 0) {
+        visitor.template Visit<ContainerTag<FlatbuffersEnumTag<uint16_t>>>(
+            field);
+      } else {
+        visitor.template Visit<ContainerTag<uint16_t>>(field);
+      }
+      break;
+    case reflection::BaseType::UInt:
+      if (field_index >= 0) {
+        visitor.template Visit<ContainerTag<FlatbuffersEnumTag<uint32_t>>>(
+            field);
+      } else {
+        visitor.template Visit<ContainerTag<uint32_t>>(field);
+      }
+      break;
+    case reflection::BaseType::ULong:
+      if (field_index >= 0) {
+        visitor.template Visit<ContainerTag<FlatbuffersEnumTag<uint64_t>>>(
+            field);
+      } else {
+        visitor.template Visit<ContainerTag<uint64_t>>(field);
+      }
+      break;
+    case reflection::BaseType::Float:
+      visitor.template Visit<ContainerTag<float>>(field);
+      break;
+    case reflection::BaseType::Double:
+      visitor.template Visit<ContainerTag<double>>(field);
+      break;
+    case reflection::BaseType::String:
+      if constexpr (is_flatbuffers_vector_tag_v<ContainerTag<std::string>>) {
+        visitor.template Visit<ContainerTag<std::string>>(field);
+      } else if constexpr (is_flatbuffers_array_tag_v<
+                               ContainerTag<std::string>>) {
+        FUZZTEST_INTERNAL_CHECK(false,
+                                "Strings are not supported as array elements");
+      }
+      break;
+    case reflection::BaseType::Obj: {
+      auto sub_object = schema->objects()->Get(field_index);
+      if (sub_object->is_struct()) {
+        visitor.template Visit<ContainerTag<FlatbuffersStructTag>>(field);
+      } else if constexpr (is_flatbuffers_vector_tag_v<
+                               ContainerTag<FlatbuffersTableTag>>) {
+        visitor.template Visit<ContainerTag<FlatbuffersTableTag>>(field);
+      } else if constexpr (is_flatbuffers_array_tag_v<
+                               ContainerTag<FlatbuffersTableTag>>) {
+        FUZZTEST_INTERNAL_CHECK(false,
+                                "Tables are not supported as array elements");
+      }
+      break;
+    }
+    case reflection::BaseType::Union:
+      if constexpr (is_flatbuffers_vector_tag_v<
+                        ContainerTag<FlatbuffersUnionTag>>) {
+        visitor.template Visit<ContainerTag<FlatbuffersUnionTag>>(field);
+      } else if constexpr (is_flatbuffers_array_tag_v<
+                               ContainerTag<FlatbuffersUnionTag>>) {
+        FUZZTEST_INTERNAL_CHECK(false,
+                                "Unions are not supported as array elements");
+      }
+      break;
+    case reflection::BaseType::UType:
+      // Noop: Union types are visited at the same time as their corresponding
+      // union values.
+      break;
+    default:
+      FUZZTEST_INTERNAL_CHECK(false, "Unsupported container element type");
+  }
+}
+
+// Dynamic to static dispatch visitor pattern.
+template <typename Visitor>
+auto 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()) {
+    case reflection::BaseType::Bool:
+      visitor.template Visit<bool>(field);
+      break;
+    case reflection::BaseType::Byte:
+      if (field_index >= 0) {
+        visitor.template Visit<FlatbuffersEnumTag<int8_t>>(field);
+      } else {
+        visitor.template Visit<int8_t>(field);
+      }
+      break;
+    case reflection::BaseType::Short:
+      if (field_index >= 0) {
+        visitor.template Visit<FlatbuffersEnumTag<int16_t>>(field);
+      } else {
+        visitor.template Visit<int16_t>(field);
+      }
+      break;
+    case reflection::BaseType::Int:
+      if (field_index >= 0) {
+        visitor.template Visit<FlatbuffersEnumTag<int32_t>>(field);
+      } else {
+        visitor.template Visit<int32_t>(field);
+      }
+      break;
+    case reflection::BaseType::Long:
+      if (field_index >= 0) {
+        visitor.template Visit<FlatbuffersEnumTag<int64_t>>(field);
+      } else {
+        visitor.template Visit<int64_t>(field);
+      }
+      break;
+    case reflection::BaseType::UByte:
+      if (field_index >= 0) {
+        visitor.template Visit<FlatbuffersEnumTag<uint8_t>>(field);
+      } else {
+        visitor.template Visit<uint8_t>(field);
+      }
+      break;
+    case reflection::BaseType::UShort:
+      if (field_index >= 0) {
+        visitor.template Visit<FlatbuffersEnumTag<uint16_t>>(field);
+      } else {
+        visitor.template Visit<uint16_t>(field);
+      }
+      break;
+    case reflection::BaseType::UInt:
+      if (field_index >= 0) {
+        visitor.template Visit<FlatbuffersEnumTag<uint32_t>>(field);
+      } else {
+        visitor.template Visit<uint32_t>(field);
+      }
+      break;
+    case reflection::BaseType::ULong:
+      if (field_index >= 0) {
+        visitor.template Visit<FlatbuffersEnumTag<uint64_t>>(field);
+      } else {
+        visitor.template Visit<uint64_t>(field);
+      }
+      break;
+    case reflection::BaseType::Float:
+      visitor.template Visit<float>(field);
+      break;
+    case reflection::BaseType::Double:
+      visitor.template Visit<double>(field);
+      break;
+    case reflection::BaseType::String:
+      visitor.template Visit<std::string>(field);
+      break;
+    case reflection::BaseType::Vector:
+    case reflection::BaseType::Vector64:
+      VisitFlatbufferContainerElementField<FlatbuffersVectorTag, Visitor>(
+          schema, field, visitor);
+      break;
+    case reflection::BaseType::Array:
+      VisitFlatbufferContainerElementField<FlatbuffersArrayTag, Visitor>(
+          schema, field, visitor);
+      break;
+    case reflection::BaseType::Obj: {
+      auto sub_object = schema->objects()->Get(field->type()->index());
+      if (sub_object->is_struct()) {
+        visitor.template Visit<FlatbuffersStructTag>(field);
+      } else {
+        visitor.template Visit<FlatbuffersTableTag>(field);
+      }
+      break;
+    }
+    case reflection::BaseType::Union:
+      visitor.template Visit<FlatbuffersUnionTag>(field);
+      break;
+    case reflection::BaseType::UType:
+      // Noop: Union types are visited at the same time as their corresponding
+      // union values.
+      break;
+    default:
+      FUZZTEST_INTERNAL_CHECK(false, absl::StrCat("Unsupported base type: ",
+                                                  field->type()->base_type()));
+  }
+}
+
+// Forward declaration of the domain factory for flatbuffers fields.
+template <typename T>
+auto GetDefaultDomain(const reflection::Schema* absl_nonnull schema,
+                      const reflection::Field* absl_nonnull field);
+
+// Flatbuffers enum domain implementation.
+template <typename Underlaying>
+class FlatbuffersEnumDomainImpl
+    : public domain_implementor::DomainBase<
+          /*Derived=*/FlatbuffersEnumDomainImpl<Underlaying>,
+          /*ValueType=*/Underlaying,
+          /*CorpusType=*/ElementOfImplCorpusType> {
+ public:
+  using typename FlatbuffersEnumDomainImpl::DomainBase::corpus_type;
+  using typename FlatbuffersEnumDomainImpl::DomainBase::value_type;
+
+  explicit FlatbuffersEnumDomainImpl(const reflection::Enum* enum_def)
+      : enum_def_(enum_def), inner_(GetEnumValues(enum_def)) {}
+
+  corpus_type Init(absl::BitGenRef prng) {
+    if (auto seed = this->MaybeGetRandomSeed(prng)) return *seed;
+    return inner_.Init(prng);
+  }
+
+  void Mutate(corpus_type& val, absl::BitGenRef prng,
+              const domain_implementor::MutationMetadata& metadata,
+              bool only_shrink) {
+    inner_.Mutate(val, prng, metadata, only_shrink);
+  }
+
+  value_type GetValue(corpus_type value) const {
+    return inner_.GetValue(value);
+  }
+
+  std::optional<corpus_type> FromValue(const value_type& v) const {
+    return inner_.FromValue(v);
+  }
+
+  std::optional<corpus_type> ParseCorpus(const IRObject& obj) const {
+    return inner_.ParseCorpus(obj);
+  }
+
+  IRObject SerializeCorpus(const corpus_type& v) const {
+    return inner_.SerializeCorpus(v);
+  }
+
+  absl::Status ValidateCorpusValue(const corpus_type& corpus_value) const {
+    return inner_.ValidateCorpusValue(corpus_value);
+  }
+
+  auto GetPrinter() const { return Printer{*this}; }
+
+ private:
+  const reflection::Enum* enum_def_;
+  ElementOfImpl<Underlaying> inner_;
+
+  static std::vector<value_type> GetEnumValues(
+      const reflection::Enum* enum_def) {
+    std::vector<value_type> values;
+    values.reserve(enum_def->values()->size());
+    for (const auto* value : *enum_def->values()) {
+      FUZZTEST_INTERNAL_CHECK(
+          value->value() >= std::numeric_limits<value_type>::min() &&
+              value->value() <= std::numeric_limits<value_type>::max(),
+          "Enum value from reflection is out of range for the target type.");
+      values.push_back(static_cast<value_type>(value->value()));
+    }
+    return values;
+  }
+
+  struct Printer {
+    const FlatbuffersEnumDomainImpl& self;
+    void PrintCorpusValue(const corpus_type& value,
+                          domain_implementor::RawSink out,
+                          domain_implementor::PrintMode mode) const {
+      if (mode == domain_implementor::PrintMode::kHumanReadable) {
+        auto user_value = self.GetValue(value);
+        absl::Format(
+            out, "%s",
+            self.enum_def_->values()->LookupByKey(user_value)->name()->str());
+      } else {
+        absl::Format(out, "%d", value);
+      }
+    }
+  };
+};
+
+// Forward declaration of the domain factory for flatbuffers fields.
+template <typename T>
+auto GetDefaultDomain(const reflection::Schema* absl_nonnull schema,
+                      const reflection::Field* absl_nonnull field);
+
+// 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>;
+
+// Base class for flatbuffers struct and table domain implementations.
+// The corpus type is a map of field ids to field values.
+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:
+  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;
+
+  FlatbuffersUntypedObjectDomainBase(
+      const reflection::Schema* absl_nonnull schema,
+      const reflection::Object* absl_nonnull object)
+      : schema_(schema), object_(object) {}
+
+  virtual ~FlatbuffersUntypedObjectDomainBase() = default;
+
+  FlatbuffersUntypedObjectDomainBase(
+      const FlatbuffersUntypedObjectDomainBase& other)
+      : DomainBase(other), schema_(other.schema_), object_(other.object_) {
+    absl::MutexLock l(&mutex_);
+    absl::MutexLock l_other(&other.mutex_);
+    domains_ = other.domains_;
+  }
+
+  FlatbuffersUntypedObjectDomainBase& operator=(
+      const FlatbuffersUntypedObjectDomainBase& other) {
+    DomainBase::operator=(other);
+    schema_ = other.schema_;
+    object_ = other.object_;
+    absl::MutexLock l(&mutex_);
+    absl::MutexLock l_other(&other.mutex_);
+    domains_ = other.domains_;
+    return *this;
+  }
+
+  FlatbuffersUntypedObjectDomainBase(FlatbuffersUntypedObjectDomainBase&& other)
+      : DomainBase(std::move(other)),
+        schema_(other.schema_),
+        object_(other.object_) {
+    absl::MutexLock l(&mutex_);
+    absl::MutexLock l_other(&other.mutex_);
+    domains_ = std::move(other.domains_);
+  }
+
+  FlatbuffersUntypedObjectDomainBase& operator=(
+      FlatbuffersUntypedObjectDomainBase&& other) {
+    DomainBase::operator=(std::move(other));
+    schema_ = other.schema_;
+    object_ = other.object_;
+    absl::MutexLock l(&mutex_);
+    absl::MutexLock l_other(&other.mutex_);
+    domains_ = std::move(other.domains_);
+    return *this;
+  }
+
+  // Initializes the corpus value.
+  corpus_type Init(absl::BitGenRef prng) {
+    if (auto seed = this->MaybeGetRandomSeed(prng)) {
+      return *seed;
+    }
+    corpus_type val;
+    for (const auto* field : *object_->fields()) {
+      VisitFlatbufferField(schema_, field, InitializeVisitor{*this, prng, val});
+    }
+    return val;
+  }
+
+  // Mutates the corpus value.
+  void Mutate(corpus_type& val, absl::BitGenRef prng,
+              const domain_implementor::MutationMetadata& metadata,
+              bool only_shrink) {
+    auto field_count = CountNumberOfFields(val);
+    auto selected_field_index = absl::Uniform(prng, 0ul, field_count);
+
+    MutateSelectedField(val, prng, metadata, only_shrink, selected_field_index);
+  }
+
+  // Counts the number of fields that can be mutated.
+  // 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 : *object_->fields()) {
+      VisitFlatbufferField(
+          schema_, field,
+          CountNumberOfMutableFieldsVisitor{*this, 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;
+    for (const auto* field : *object_->fields()) {
+      if (IsSupportedField(field)) {
+        if (only_shrink && !val.contains(field->id())) continue;
+        ++field_counter;
+      }
+
+      if (field_counter == selected_field_index + 1) {
+        VisitFlatbufferField(
+            schema_, field,
+            MutateVisitor{*this, prng, metadata, only_shrink, val});
+        return field_counter;
+      }
+
+      auto base_type = field->type()->base_type();
+      if (base_type == reflection::BaseType::Obj) {
+        auto sub_object = schema_->objects()->Get(field->type()->index());
+        if (sub_object->is_struct()) {
+          field_counter +=
+              derived()
+                  .template GetCachedDomain<FlatbuffersStructTag>(field)
+                  .MutateSelectedField(val[field->id()], prng, metadata,
+                                       only_shrink,
+                                       selected_field_index - field_counter);
+        } else {
+          field_counter +=
+              derived()
+                  .template GetCachedDomain<FlatbuffersTableTag>(field)
+                  .MutateSelectedField(val[field->id()], prng, metadata,
+                                       only_shrink,
+                                       selected_field_index - field_counter);
+        }
+      }
+
+      if (base_type == reflection::BaseType::Vector ||
+          base_type == reflection::BaseType::Vector64) {
+        auto elem_type = field->type()->element();
+        if (elem_type == reflection::BaseType::Obj) {
+          auto sub_object = schema_->objects()->Get(field->type()->index());
+          if (!sub_object->is_struct()) {
+            field_counter +=
+                derived()
+                    .template GetCachedDomain<
+                        FlatbuffersVectorTag<FlatbuffersTableTag>>(field)
+                    .MutateSelectedField(val[field->id()], prng, metadata,
+                                         only_shrink,
+                                         selected_field_index - field_counter);
+          } else {
+            field_counter +=
+                derived()
+                    .template GetCachedDomain<
+                        FlatbuffersVectorTag<FlatbuffersStructTag>>(field)
+                    .MutateSelectedField(val[field->id()], prng, metadata,
+                                         only_shrink,
+                                         selected_field_index - field_counter);
+          }
+        } else if (elem_type == reflection::BaseType::Union) {
+          field_counter +=
+              derived()
+                  .template GetCachedDomain<
+                      FlatbuffersVectorTag<FlatbuffersUnionTag>>(field)
+                  .MutateSelectedField(val[field->id()], prng, metadata,
+                                       only_shrink,
+                                       selected_field_index - field_counter);
+        }
+      }
+
+      if (base_type == reflection::BaseType::Union) {
+        field_counter +=
+            derived()
+                .template GetCachedDomain<FlatbuffersUnionTag>(field)
+                .MutateSelectedField(val[field->id()], prng, metadata,
+                                     only_shrink,
+                                     selected_field_index - field_counter);
+      }
+
+      if (field_counter > selected_field_index) {
+        return field_counter;
+      }
+    }
+    return field_counter;
+  }
+
+  auto GetPrinter() const { return Printer{*this}; }
+
+  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{*this, field_corpus, result});
+      if (!result.ok()) return result;
+    }
+    return absl::OkStatus();
+  }
+
+  value_type GetValue(const corpus_type& value) const {
+    FUZZTEST_INTERNAL_CHECK(
+        false, "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{*this, (*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{*this, field_corpus, pair_subs.emplace_back()});
+    }
+    return out;
+  }
+
+ protected:
+  const reflection::Schema* schema_;
+  const reflection::Object* object_;
+  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& derived() { return static_cast<Derived&>(*this); }
+  const Derived& derived() const { return static_cast<const Derived&>(*this); }
+
+  bool IsSupportedField(const reflection::Field* absl_nonnull field) const {
+    auto base_type = field->type()->base_type();
+    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;
+    }
+    if (base_type == reflection::BaseType::Array) {
+      auto elem_type = field->type()->element();
+      if (flatbuffers::IsScalar(elem_type)) return true;
+      if (elem_type == reflection::BaseType::Obj &&
+          schema_->objects()->Get(field->type()->index())->is_struct())
+        return true;
+    }
+    return false;
+  }
+
+  const reflection::Field* absl_nullable GetFieldById(
+      typename corpus_type::key_type id) const {
+    const auto it =
+        absl::c_find_if(*object_->fields(),
+                        [id](const auto* field) { return field->id() == id; });
+    return it != object_->fields()->end() ? *it : 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 FlatbuffersUntypedObjectDomainBase<Derived, ValueType>& self;
+    const GenericDomainCorpusType& field_corpus;
+    domain_implementor::RawSink sink;
+    domain_implementor::PrintMode mode;
+
+    template <typename T>
+    void Visit(const reflection::Field* absl_nonnull field) const {
+      auto& domain = self.derived().template GetCachedDomain<T>(field);
+      absl::Format(sink, "%s: ", field->name()->str());
+      if constexpr (std::is_same_v<T, FlatbuffersVectorTag<uint8_t>> ||
+                    std::is_same_v<
+                        T, FlatbuffersVectorTag<FlatbuffersEnumTag<uint8_t>>> ||
+                    std::is_same_v<T, FlatbuffersArrayTag<uint8_t>> ||
+                    std::is_same_v<
+                        T, FlatbuffersArrayTag<FlatbuffersEnumTag<uint8_t>>>) {
+        // Handle the case where the field is a vector or array of 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 (std::is_same_v<T, FlatbuffersVectorTag<uint8_t>> ||
+                      std::is_same_v<T, FlatbuffersArrayTag<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 if constexpr (
+            std::is_same_v<T,
+                           FlatbuffersVectorTag<FlatbuffersEnumTag<uint8_t>>> ||
+            std::is_same_v<T,
+                           FlatbuffersArrayTag<FlatbuffersEnumTag<uint8_t>>>) {
+          auto inner_corpus = object_corpus.GetAs<corpus_type_t<ContainerOfImpl<
+              std::vector<uint8_t>, FlatbuffersEnumDomainImpl<uint8_t>>>>();
+          auto enum_object = self.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 FlatbuffersUntypedObjectDomainBase<Derived, ValueType>& 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;
+      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 = 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});
+        }
+        first = false;
+      }
+      absl::Format(out, "}");
+    }
+  };
+
+  struct InitializeVisitor {
+    const FlatbuffersUntypedObjectDomainBase<Derived, ValueType>& self;
+    absl::BitGenRef prng;
+    corpus_type& corpus;
+
+    template <typename T>
+    void Visit(const reflection::Field* absl_nonnull field) {
+      auto& domain = self.derived().template GetCachedDomain<T>(field);
+      corpus[field->id()] = domain.Init(prng);
+    }
+  };
+
+  struct CountNumberOfMutableFieldsVisitor {
+    const FlatbuffersUntypedObjectDomainBase<Derived, ValueType>& self;
+    uint64_t& field_count;
+    corpus_type& corpus;
+    bool only_shrink = false;
+
+    template <typename T>
+    void Visit(const reflection::Field* absl_nonnull field) const {
+      if (!self.derived().IsSupportedField(field)) return;
+      if (only_shrink && !corpus.contains(field->id())) return;
+
+      field_count++;
+
+      if constexpr (!std::is_integral_v<T> && !std::is_floating_point_v<T> &&
+                    !is_flatbuffers_enum_tag_v<T>) {
+        // Count the number of fields in sub-domain.
+        auto& domain = self.derived().template GetCachedDomain<T>(field);
+        if (auto it = corpus.find(field->id()); it != corpus.end()) {
+          field_count += domain.CountNumberOfFields(it->second);
+        }
+      }
+    }
+  };
+
+  struct MutateVisitor {
+    const FlatbuffersUntypedObjectDomainBase<Derived, ValueType>& self;
+    absl::BitGenRef prng;
+    const domain_implementor::MutationMetadata& metadata;
+    bool only_shrink;
+    corpus_type& corpus;
+
+    template <typename T>
+    void Visit(const reflection::Field* absl_nonnull field) {
+      auto& domain = self.derived().template GetCachedDomain<T>(field);
+      if (auto it = corpus.find(field->id()); it != corpus.end()) {
+        domain.Mutate(it->second, prng, metadata, only_shrink);
+      } else if (!only_shrink) {
+        corpus[field->id()] = domain.Init(prng);
+      }
+    }
+  };
+
+  struct ParseVisitor {
+    const FlatbuffersUntypedObjectDomainBase<Derived, ValueType>& self;
+    const IRObject& ir_object;
+    std::optional<GenericDomainCorpusType>& corpus;
+
+    template <typename T>
+    void Visit(const reflection::Field* absl_nonnull field) {
+      auto& domain = self.derived().template GetCachedDomain<T>(field);
+      corpus = domain.ParseCorpus(ir_object);
+    }
+  };
+
+  struct SerializeVisitor {
+    const FlatbuffersUntypedObjectDomainBase<Derived, ValueType>& self;
+    const GenericDomainCorpusType& corpus;
+    IRObject& ir_object;
+
+    template <typename T>
+    void Visit(const reflection::Field* absl_nonnull field) {
+      auto& domain = self.derived().template GetCachedDomain<T>(field);
+      ir_object = domain.SerializeCorpus(corpus);
+    }
+  };
+
+  struct ValidateVisitor {
+    const FlatbuffersUntypedObjectDomainBase<Derived, ValueType>& self;
+    const GenericDomainCorpusType& inner_corpus;
+    absl::Status& status;
+
+    template <typename T>
+    void Visit(const reflection::Field* absl_nonnull field) {
+      auto& domain = self.derived().template GetCachedDomain<T>(field);
+      status = domain.ValidateCorpusValue(inner_corpus);
+      if (!status.ok()) {
+        status = Prefix(status, absl::StrCat("Invalid value for field ",
+                                             field->name()->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_INTERNAL_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::FlatBufferBuilder& 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) const {
+      [[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>) {
+        FUZZTEST_INTERNAL_CHECK(base_type >= reflection::BaseType::Byte &&
+                                    base_type <= reflection::BaseType::ULong &&
+                                    field->type()->index() >= 0,
+                                "Field must be an enum type.");
+        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>) {
+        FUZZTEST_INTERNAL_CHECK(flatbuffers::IsScalar(base_type),
+                                "Field must be an scalar type.");
+        auto inner_value = value->GetField<T>(field->offset());
+        inner_corpus = domain.FromValue(inner_value);
+      } else if constexpr (std::is_same_v<T, FlatbuffersStructTag>) {
+        auto sub_object = self.schema_->objects()->Get(field->type()->index());
+        FUZZTEST_INTERNAL_CHECK(
+            base_type == reflection::BaseType::Obj && sub_object->is_struct(),
+            "Field must be a struct type.");
+        auto inner_value =
+            value->GetStruct<const flatbuffers::Struct*>(field->offset());
+        inner_corpus = domain.FromValue(inner_value);
+      } else if constexpr (is_flatbuffers_array_tag_v<T>) {
+        using ValueT = typename T::value_type;
+        auto element_type = field->type()->element();
+        if constexpr (std::is_integral_v<ValueT> ||
+                      std::is_floating_point_v<ValueT>) {
+          FUZZTEST_INTERNAL_CHECK(flatbuffers::IsScalar(element_type),
+                                  "Field element type must be an scalar type.");
+          std::vector<ValueT> inner_values;
+          for (size_t i = 0; i < field->type()->fixed_length(); ++i) {
+            auto inner_value = value->GetField<ValueT>(
+                field->offset() + i * field->type()->element_size());
+            inner_values.push_back(inner_value);
+          }
+          inner_corpus = domain.FromValue(inner_values);
+        } else if constexpr (is_flatbuffers_enum_tag_v<ValueT>) {
+          FUZZTEST_INTERNAL_CHECK(flatbuffers::IsScalar(element_type) &&
+                                      element_type != reflection::Bool,
+                                  "Field element type must be an scalar type.");
+          std::vector<typename ValueT::type> inner_values;
+          for (size_t i = 0; i < field->type()->fixed_length(); ++i) {
+            auto inner_value = value->GetField<typename ValueT::type>(
+                field->offset() + i * field->type()->element_size());
+            inner_values.push_back(inner_value);
+          }
+          inner_corpus = domain.FromValue(inner_values);
+        } else if constexpr (std::is_same_v<ValueT, FlatbuffersStructTag>) {
+          auto sub_object =
+              self.schema_->objects()->Get(field->type()->index());
+          FUZZTEST_INTERNAL_CHECK(element_type == reflection::BaseType::Obj &&
+                                      sub_object->is_struct(),
+                                  "Field element type must be a struct type.");
+          std::vector<const flatbuffers::Struct*> inner_values;
+          for (size_t i = 0; i < field->type()->fixed_length(); ++i) {
+            auto inner_value = value->GetStruct<const flatbuffers::Struct*>(
+                field->offset() + i * sub_object->bytesize());
+            inner_values.push_back(inner_value);
+          }
+          inner_corpus = domain.FromValue(inner_values);
+        }
+      }
+
+      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) const {
+      [[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>) {
+        FUZZTEST_INTERNAL_CHECK(flatbuffers::IsScalar(base_type),
+                                "Field must be an scalar type.");
+        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());
+        FUZZTEST_INTERNAL_CHECK(
+            base_type == reflection::BaseType::Obj && sub_object->is_struct(),
+            "Field must be a struct type.");
+        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_object->fields()) {
+          VisitFlatbufferField(sub_domain.schema_, nested_field,
+                               BuildValueVisitor{sub_domain, inner_corpus_value,
+                                                 struct_ptr + field->offset()});
+        }
+      } else if constexpr (is_flatbuffers_array_tag_v<T>) {
+        if constexpr (is_flatbuffers_enum_tag_v<typename T::value_type> ||
+                      std::is_integral_v<typename T::value_type> ||
+                      std::is_floating_point_v<typename T::value_type>) {
+          FUZZTEST_INTERNAL_CHECK(
+              flatbuffers::IsScalar(field->type()->element()),
+              "Field must be an scalar type.");
+          auto inner_values = domain.GetValue(corpus_value.at(field->id()));
+          for (size_t i = 0; i < field->type()->fixed_length(); ++i) {
+            auto offset = field->offset() + i * field->type()->element_size();
+            if constexpr (is_flatbuffers_enum_tag_v<typename T::value_type>) {
+              flatbuffers::WriteScalar<typename T::value_type::type>(
+                  struct_ptr + offset, inner_values[i]);
+            } else {
+              flatbuffers::WriteScalar<typename T::value_type>(
+                  struct_ptr + offset, inner_values[i]);
+            }
+          }
+        } else if constexpr (std::is_same_v<typename T::value_type,
+                                            FlatbuffersStructTag>) {
+          auto sub_object =
+              self.schema_->objects()->Get(field->type()->index());
+          FUZZTEST_INTERNAL_CHECK(
+              field->type()->element() == reflection::BaseType::Obj &&
+                  sub_object->is_struct(),
+              "Field must be a struct type.");
+          auto container_corpus =
+              corpus_value.at(field->id()).GetAs<std::list<corpus_type>>();
+          FlatbuffersStructUntypedDomainImpl sub_domain(self.schema_,
+                                                        sub_object);
+          size_t i = 0;
+          for (const auto& element_corpus : container_corpus) {
+            for (const auto* nested_field : *sub_object->fields()) {
+              VisitFlatbufferField(
+                  sub_domain.schema_, nested_field,
+                  BuildValueVisitor{sub_domain, element_corpus,
+                                    struct_ptr + field->offset() +
+                                        i * sub_object->bytesize()});
+            }
+            ++i;
+          }
+        }
+      }
+    }
+  };
+};
+
+// 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)
+      : schema_(schema), union_def_(union_def), type_domain_(union_def) {}
+
+  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_);
+    domains_ = other.domains_;
+  }
+
+  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_);
+    domains_ = std::move(other.domains_);
+  }
+
+  FlatbuffersUnionDomainImpl& operator=(
+      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_);
+    domains_ = other.domains_;
+    return *this;
+  }
+
+  FlatbuffersUnionDomainImpl& operator=(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_);
+    domains_ = std::move(other.domains_);
+    return *this;
+  }
+
+  // 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);
+
+  auto GetPrinter() const { return Printer{*this}; }
+
+  absl::Status ValidateCorpusValue(const corpus_type& corpus_value) const;
+
+  // UNSUPPORTED: Flatbuffers unions user values are not supported.
+  value_type GetValue(const corpus_type& corpus_value) const {
+    FUZZTEST_INTERNAL_CHECK(false, "GetValue is not supported for unions.");
+  }
+
+  // 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& corpus_value) const;
+
+ private:
+  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 FlatbuffersUnionTypeDomainImpl::value_type, CopyableAny>
+      domains_ ABSL_GUARDED_BY(mutex_);
+
+  // Creates flatbuffer from the corpus value.
+  std::optional<flatbuffers::uoffset_t> BuildValue(
+      const corpus_type& corpus_value,
+      flatbuffers::FlatBufferBuilder& 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 = domains_.find(enum_value.value());
+    if (it == domains_.end()) {
+      it = domains_
+               .try_emplace(enum_value.value(), std::in_place_type<DomainT>,
+                            GetDefaultDomainForType<T>(enum_value))
+               .first;
+    }
+    return it->second.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::FlatBufferBuilder& builder) const;
+
+  // 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 {
+    auto get_opt_domain = [this, field]() {
+      auto opt_domain = OptionalOf(GetDefaultDomain<T>(schema_, field));
+      if (!field->optional()) opt_domain.SetWithoutNull();
+      return Domain<value_type_t<decltype(opt_domain)>>{opt_domain};
+    };
+
+    using DomainT = decltype(get_opt_domain());
+    // 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>,
+                            get_opt_domain())
+               .first;
+    }
+    return it->second.template GetAs<DomainT>();
+  }
+
+  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 {
+      [[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;
+
+      if constexpr (is_flatbuffers_enum_tag_v<T>) {
+        if (!field->optional() || user_value->CheckField(field->offset())) {
+          inner_value = user_value->GetField<typename T::type>(
+              field->offset(), field->default_integer());
+        }
+      } else if constexpr (std::is_integral_v<T>) {
+        if (!field->optional() || user_value->CheckField(field->offset())) {
+          inner_value = std::optional(user_value->GetField<T>(
+              field->offset(), field->default_integer()));
+        }
+      } else if constexpr (std::is_floating_point_v<T>) {
+        if (!field->optional() || user_value->CheckField(field->offset())) {
+          inner_value = std::optional(
+              user_value->GetField<T>(field->offset(), field->default_real()));
+        }
+      } else if constexpr (std::is_same_v<T, std::string>) {
+        if (user_value->CheckField(field->offset())) {
+          inner_value = std::optional(
+              user_value->GetPointer<flatbuffers::String*>(field->offset())
+                  ->str());
+        }
+      } else if constexpr (std::is_same_v<T, FlatbuffersTableTag>) {
+        auto sub_object = self.schema_->objects()->Get(field->type()->index());
+        FUZZTEST_INTERNAL_CHECK(
+            base_type == reflection::BaseType::Obj && !sub_object->is_struct(),
+            "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>) {
+        auto sub_object = self.schema_->objects()->Get(field->type()->index());
+        FUZZTEST_INTERNAL_CHECK(
+            base_type == reflection::BaseType::Obj && sub_object->is_struct(),
+            "Field must be a struct type.");
+        inner_value =
+            user_value->GetStruct<const flatbuffers::Struct*>(field->offset());
+      } else if constexpr (is_flatbuffers_vector_tag_v<T>) {
+        FUZZTEST_INTERNAL_CHECK(base_type == reflection::BaseType::Vector ||
+                                    base_type == reflection::BaseType::Vector64,
+                                "Field must be a vector type.");
+        if (!user_value->CheckField(field->offset())) {
+          inner_value = std::nullopt;
+        } else {
+          VisitVector<typename T::value_type, std::decay_t<decltype(domain)>>(
+              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(
+            (field->name()->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.value());
+      }
+    };
+
+    template <typename ElementType, typename Domain>
+    void VisitVector(const reflection::Field* field,
+                     value_type_t<Domain>& inner_value) const {
+      if constexpr (std::is_integral_v<ElementType> ||
+                    std::is_floating_point_v<ElementType>) {
+        auto vec = user_value->GetPointer<flatbuffers::Vector<ElementType>*>(
+            field->offset());
+        inner_value = std::optional(std::vector<ElementType>());
+        inner_value->reserve(vec->size());
+        for (auto i = 0; i < vec->size(); ++i) {
+          inner_value->push_back(vec->Get(i));
+        }
+      } else if constexpr (is_flatbuffers_enum_tag_v<ElementType>) {
+        using Underlaying = typename ElementType::type;
+        auto vec = user_value->GetPointer<flatbuffers::Vector<Underlaying>*>(
+            field->offset());
+        inner_value = std::optional(std::vector<Underlaying>());
+        inner_value->reserve(vec->size());
+        for (auto i = 0; i < vec->size(); ++i) {
+          inner_value->push_back(vec->Get(i));
+        }
+      } else if constexpr (std::is_same_v<ElementType, std::string>) {
+        auto vec = user_value->GetPointer<
+            flatbuffers::Vector<flatbuffers::Offset<flatbuffers::String>>*>(
+            field->offset());
+        inner_value = std::optional(std::vector<std::string>());
+        inner_value->reserve(vec->size());
+        for (auto i = 0; i < vec->size(); ++i) {
+          inner_value->push_back(vec->Get(i)->str());
+        }
+      } else if constexpr (std::is_same_v<ElementType, FlatbuffersTableTag>) {
+        auto vec = user_value->GetPointer<
+            flatbuffers::Vector<flatbuffers::Offset<flatbuffers::Table>>*>(
+            field->offset());
+        inner_value = std::optional(std::vector<const flatbuffers::Table*>());
+        inner_value->reserve(vec->size());
+        for (auto i = 0; i < vec->size(); ++i) {
+          inner_value->push_back(vec->Get(i));
+        }
+      } else if constexpr (std::is_same_v<ElementType, FlatbuffersStructTag>) {
+        const reflection::Object* struct_object =
+            self.schema_->objects()->Get(field->type()->index());
+        auto vec =
+            user_value
+                ->template GetPointer<const flatbuffers::Vector<uint8_t>*>(
+                    field->offset());
+        if (vec == nullptr) {
+          return;
+        }
+        inner_value =
+            std::make_optional(std::vector<const flatbuffers::Struct*>());
+        inner_value->reserve(vec->size());
+        for (std::remove_pointer_t<decltype(vec)>::size_type i = 0;
+             i < vec->size(); ++i) {
+          const uint8_t* struct_data_ptr =
+              vec->Data() + i * struct_object->bytesize();
+          inner_value->push_back(
+              reinterpret_cast<const flatbuffers::Struct*>(struct_data_ptr));
+        }
+      } else if constexpr (std::is_same_v<ElementType, FlatbuffersUnionTag>) {
+        constexpr char kUnionTypeFieldSuffix[] = "_type";
+        auto type_field = self.object_->fields()->LookupByKey(
+            (field->name()->str() + kUnionTypeFieldSuffix).c_str());
+        if (type_field == nullptr) {
+          return;
+        }
+        auto type_vec = user_value->GetPointer<flatbuffers::Vector<uint8_t>*>(
+            type_field->offset());
+        auto value_vec =
+            user_value
+                ->GetPointer<flatbuffers::Vector<flatbuffers::Offset<void>>*>(
+                    field->offset());
+        inner_value =
+            std::optional(typename value_type_t<Domain>::value_type{});
+        inner_value->reserve(value_vec->size());
+        for (auto i = 0; i < value_vec->size(); ++i) {
+          inner_value->emplace_back(type_vec->Get(i), value_vec->Get(i));
+        }
+      }
+    }
+  };
+
+  // Create out-of-line table fields, see `BuildTable` for details.
+  struct TableFieldBuilderVisitor {
+    const FlatbuffersTableUntypedDomainImpl& self;
+    flatbuffers::FlatBufferBuilder& builder;
+    absl::flat_hash_map<typename corpus_type::key_type, flatbuffers::uoffset_t>&
+        offsets;
+    const typename corpus_type::mapped_type& corpus_value;
+
+    template <typename T>
+    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);
+        if (user_value.has_value()) {
+          auto offset =
+              builder.CreateString(user_value->data(), user_value->size()).o;
+          offsets.insert({field->id(), offset});
+        }
+      } else if constexpr (std::is_same_v<T, FlatbuffersTableTag>) {
+        FlatbuffersTableUntypedDomainImpl inner_domain(
+            self.schema_, self.schema_->objects()->Get(field->type()->index()));
+        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>();
+          auto offset = inner_domain.BuildTable(inner_corpus, builder);
+          offsets.insert({field->id(), offset});
+        }
+      } else if constexpr (is_flatbuffers_vector_tag_v<T>) {
+        VisitVector<typename T::value_type>(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 Domain,
+              std::enable_if_t<std::is_integral_v<Element> ||
+                                   std::is_floating_point_v<Element> ||
+                                   is_flatbuffers_enum_tag_v<Element>,
+                               int> = 0>
+    void VisitVector(const reflection::Field* field, const Domain& domain) {
+      auto value = domain.GetValue(corpus_value);
+      if (value && (!value->empty() || !field->optional())) {
+        offsets.insert({field->id(), builder.CreateVector(*value).o});
+      } else if (!value && !field->optional()) {
+        // Handle case where value is std::nullopt but field is not optional
+        // Create an empty vector of the appropriate type.
+        if constexpr (is_flatbuffers_enum_tag_v<Element>) {
+          offsets.insert(
+              {field->id(),
+               builder.CreateVector(std::vector<typename Element::type>()).o});
+        } else {
+          offsets.insert(
+              {field->id(), builder.CreateVector(std::vector<Element>()).o});
+        }
+      }
+    }
+
+    template <
+        typename Element, typename Domain,
+        std::enable_if_t<std::is_same_v<Element, FlatbuffersTableTag>, int> = 0>
+    void VisitVector(const reflection::Field* field, const Domain& domain) {
+      auto opt_corpus =
+          corpus_value
+              .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>>();
+      if (field->optional() && container_corpus.empty()) {
+        return;
+      }
+
+      FlatbuffersTableUntypedDomainImpl inner_domain(
+          self.schema_, self.schema_->objects()->Get(field->type()->index()));
+      std::vector<flatbuffers::Offset<flatbuffers::Table>> vec_offsets;
+      vec_offsets.reserve(container_corpus.size());
+      for (auto& inner_corpus : container_corpus) {
+        auto offset = inner_domain.BuildTable(inner_corpus, builder);
+        vec_offsets.push_back(offset);
+      }
+      offsets.insert({field->id(), builder.CreateVector(vec_offsets).o});
+    }
+
+    template <typename Element, typename Domain,
+              std::enable_if_t<std::is_same_v<Element, FlatbuffersStructTag>,
+                               int> = 0>
+    void VisitVector(const reflection::Field* field, const Domain& domain) {
+      auto struct_object = 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;
+      }
+      auto container_corpus = std::get<GenericDomainCorpusType>(opt_corpus)
+                                  .template GetAs<std::list<corpus_type>>();
+      uint8_t* vec_ptr = nullptr;
+      FlatbuffersStructUntypedDomainImpl inner_domain(self.schema_,
+                                                      struct_object);
+      auto vec_offset = builder.CreateUninitializedVector(
+          container_corpus.size(), struct_object->bytesize(),
+          struct_object->minalign(), &vec_ptr);
+      size_t i = 0;
+      for (const auto& inner_corpus : container_corpus) {
+        uint8_t* current_struct_ptr = vec_ptr + i * struct_object->bytesize();
+        inner_domain.BuildValue(inner_corpus, current_struct_ptr);
+        ++i;
+      }
+      offsets.insert({field->id(), vec_offset});
+    }
+
+    template <typename Element, typename Domain,
+              std::enable_if_t<std::is_same_v<Element, std::string>, int> = 0>
+    void VisitVector(const reflection::Field* field, const Domain& domain) {
+      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});
+    }
+
+    template <
+        typename Element, typename Domain,
+        std::enable_if_t<std::is_same_v<Element, FlatbuffersUnionTag>, int> = 0>
+    void VisitVector(const reflection::Field* field, const Domain& domain) {
+      const reflection::Enum* union_type =
+          self.schema_->enums()->Get(field->type()->index());
+      constexpr char kUnionTypeFieldSuffix[] = "_type";
+      const reflection::Field* type_field = self.object_->fields()->LookupByKey(
+          (field->name()->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.
+  struct TableBuilderVisitor {
+    const FlatbuffersTableUntypedDomainImpl& self;
+    flatbuffers::FlatBufferBuilder& builder;
+    absl::flat_hash_map<typename corpus_type::key_type, flatbuffers::uoffset_t>&
+        offsets;
+    const typename corpus_type::value_type::second_type& corpus_value;
+
+    template <typename T>
+    void Visit(const reflection::Field* absl_nonnull field) const {
+      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);
+        auto v = domain.GetValue(corpus_value);
+        if (!v) {
+          return;
+        }
+        // Store "inline field" value inline.
+        builder.AddElement(field->offset(), v.value());
+      } else if constexpr (std::is_same_v<T, std::string> ||
+                           is_flatbuffers_vector_tag_v<T>) {
+        // "Out-of-line field". Store just offset.
+        if constexpr (is_flatbuffers_vector_tag_v<T>) {
+          if constexpr (std::is_same_v<typename T::value_type,
+                                       FlatbuffersUnionTag>) {
+            constexpr char kUnionTypeFieldSuffix[] = "_type";
+            const reflection::Field* type_field =
+                self.object_->fields()->LookupByKey(
+                    (field->name()->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()) {
+          builder.AddOffset(
+              field->offset(),
+              flatbuffers::Offset<flatbuffers::String>(it->second));
+        }
+      } else if constexpr (std::is_same_v<T, FlatbuffersTableTag>) {
+        // "Out-of-line field". Store just offset.
+        if (auto it = offsets.find(field->id()); it != offsets.end()) {
+          builder.AddOffset(
+              field->offset(),
+              flatbuffers::Offset<flatbuffers::Table>(it->second));
+        }
+      } 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;
+        }
+        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());
+        }
+      } 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(
+                  (field->name()->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)>>();
+          auto type_value = domain.GetType(inner_corpus);
+          auto size = flatbuffers::GetTypeSize(type_field->type()->base_type());
+          // Store the type value inline.
+          builder.Align(size);
+          builder.PushBytes(reinterpret_cast<const uint8_t*>(&type_value),
+                            size);
+          builder.TrackField(type_field->offset(), builder.GetSize());
+        }
+      }
+    }
+  };
+};
+
+// Domain factory for flatbuffers fields.
+template <typename T>
+auto GetDefaultDomain(const reflection::Schema* absl_nonnull schema,
+                      const reflection::Field* absl_nonnull field) {
+  // Used to satisfy the compiler return type deduction rules.
+  auto placeholder = Arbitrary<bool>();
+
+  if constexpr (is_flatbuffers_array_tag_v<T>) {
+    using ElementT = typename T::value_type;
+    auto size = field->type()->fixed_length();
+    if constexpr (is_flatbuffers_enum_tag_v<ElementT>) {
+      auto enum_object = schema->enums()->Get(field->type()->index());
+      return VectorOf(FlatbuffersEnumDomainImpl<typename ElementT::type>(
+                          enum_object))
+          .WithSize(size);
+    } else if constexpr (std::is_same_v<ElementT, FlatbuffersStructTag>) {
+      const reflection::Object* sub_object =
+          schema->objects()->Get(field->type()->index());
+      return VectorOf(FlatbuffersStructUntypedDomainImpl{schema, sub_object})
+          .WithSize(size);
+    } else if constexpr (std::is_integral_v<ElementT> ||
+                         std::is_floating_point_v<ElementT>) {
+      return VectorOf(Arbitrary<ElementT>()).WithSize(size);
+    } else {
+      FUZZTEST_INTERNAL_CHECK(false,
+                              "Unsupported type: ", field->type()->element());
+      return VectorOf(placeholder).WithSize(0);
+    }
+  } else if constexpr (is_flatbuffers_enum_tag_v<T>) {
+    auto enum_object = schema->enums()->Get(field->type()->index());
+    return FlatbuffersEnumDomainImpl<typename T::type>(enum_object);
+  } else if constexpr (std::is_same_v<T, FlatbuffersTableTag>) {
+    auto table_object = schema->objects()->Get(field->type()->index());
+    return FlatbuffersTableUntypedDomainImpl{schema, table_object};
+  } else if constexpr (std::is_same_v<T, FlatbuffersStructTag>) {
+    auto struct_object = schema->objects()->Get(field->type()->index());
+    return FlatbuffersStructUntypedDomainImpl{schema, struct_object};
+  } else if constexpr (std::is_same_v<T, FlatbuffersUnionTag>) {
+    auto union_type = schema->enums()->Get(field->type()->index());
+    return FlatbuffersUnionDomainImpl{schema, union_type};
+  } else if constexpr (is_flatbuffers_vector_tag_v<T>) {
+    return VectorOf(GetDefaultDomain<typename T::value_type>(schema, field))
+        .WithMaxSize(std::numeric_limits<flatbuffers::uoffset_t>::max());
+  } else {
+    return Arbitrary<T>();
+  }
+}
+
+// Corpus type for the table domain
+struct FlatbuffersTableDomainCorpusType {
+  // Map of field ids to field values.
+  typename FlatbuffersTableUntypedDomainImpl::corpus_type untyped_corpus;
+  // Serialized flatbuffer.
+  mutable std::vector<uint8_t> buffer;
+};
+
+// Domain implementation for flatbuffers generated table classes.
+// The corpus type is a pair of:
+// - A map of field ids to field values.
+// - The serialized buffer of the table.
+template <typename T>
+class FlatbuffersTableDomainImpl
+    : public domain_implementor::DomainBase<
+          /*Derived=*/FlatbuffersTableDomainImpl<T>,
+          /*ValueType=*/const T*,
+          /*CorpusType=*/FlatbuffersTableDomainCorpusType> {
+ public:
+  using typename FlatbuffersTableDomainImpl::DomainBase::corpus_type;
+  using typename FlatbuffersTableDomainImpl::DomainBase::value_type;
+
+  FlatbuffersTableDomainImpl() {
+    flatbuffers::Verifier verifier(T::BinarySchema::data(),
+                                   T::BinarySchema::size());
+    FUZZTEST_INTERNAL_CHECK(reflection::VerifySchemaBuffer(verifier),
+                            "Invalid schema for flatbuffers table.");
+    auto schema = reflection::GetSchema(T::BinarySchema::data());
+    auto table_object =
+        schema->objects()->LookupByKey(T::GetFullyQualifiedName());
+    inner_ = FlatbuffersTableUntypedDomainImpl{schema, table_object};
+  }
+
+  // Initializes the table with random values.
+  corpus_type Init(absl::BitGenRef prng) {
+    if (auto seed = this->MaybeGetRandomSeed(prng)) {
+      return *seed;
+    }
+    // Create new map of field ids to field values
+    auto val = inner_->Init(prng);
+    // Return corpus value: pair of the map and the serialized buffer.
+    return FlatbuffersTableDomainCorpusType{val, {}};
+  }
+
+  // Returns the number of fields in the table.
+  uint64_t CountNumberOfFields(corpus_type& val) {
+    return inner_->CountNumberOfFields(val.untyped_corpus);
+  }
+
+  // Mutates the given corpus value.
+  void Mutate(corpus_type& val, absl::BitGenRef prng,
+              const domain_implementor::MutationMetadata& metadata,
+              bool only_shrink) {
+    // Modify values in the map.
+    inner_->Mutate(val.untyped_corpus, prng, metadata, only_shrink);
+  }
+
+  // Converts corpus value into the exact flatbuffer.
+  value_type GetValue(const corpus_type& value) const {
+    value.buffer = BuildBuffer(value.untyped_corpus);
+    return flatbuffers::GetRoot<T>(value.buffer.data());
+  }
+
+  // Creates corpus value from the exact flatbuffer.
+  std::optional<corpus_type> FromValue(const value_type& value) const {
+    auto val = inner_->FromValue((const flatbuffers::Table*)value);
+    if (!val.has_value()) return std::nullopt;
+    return std::optional(
+        FlatbuffersTableDomainCorpusType{*val, BuildBuffer(*val)});
+  }
+
+  // Returns the printer for the table.
+  auto GetPrinter() const { return Printer{*inner_}; }
+
+  // Returns the parsed corpus value.
+  std::optional<corpus_type> ParseCorpus(const IRObject& obj) const {
+    auto val = inner_->ParseCorpus(obj);
+    if (!val.has_value()) return std::nullopt;
+    return std::optional(
+        FlatbuffersTableDomainCorpusType{*val, BuildBuffer(*val)});
+  }
+
+  // Returns the serialized corpus value.
+  IRObject SerializeCorpus(const corpus_type& corpus_value) const {
+    return inner_->SerializeCorpus(corpus_value.untyped_corpus);
+  }
+
+  // Returns the status of the given corpus value.
+  absl::Status ValidateCorpusValue(const corpus_type& corpus_value) const {
+    return inner_->ValidateCorpusValue(corpus_value.untyped_corpus);
+  }
+
+ private:
+  std::optional<FlatbuffersTableUntypedDomainImpl> inner_;
+
+  struct Printer {
+    const FlatbuffersTableUntypedDomainImpl& inner;
+
+    void PrintCorpusValue(const corpus_type& value,
+                          domain_implementor::RawSink out,
+                          domain_implementor::PrintMode mode) const {
+      inner.GetPrinter().PrintCorpusValue(value.untyped_corpus, out, mode);
+    }
+  };
+
+  std::vector<uint8_t> BuildBuffer(
+      const corpus_type_t<FlatbuffersTableUntypedDomainImpl>& val) const {
+    flatbuffers::FlatBufferBuilder builder;
+    auto offset = inner_->BuildTable(val, builder);
+    builder.Finish(flatbuffers::Offset<flatbuffers::Table>(offset));
+    auto buffer =
+        std::vector<uint8_t>(builder.GetBufferPointer(),
+                             builder.GetBufferPointer() + builder.GetSize());
+    return buffer;
+  }
+};
+
+template <typename T>
+class ArbitraryImpl<const T*, std::enable_if_t<is_flatbuffers_table_v<T>>>
+    : public FlatbuffersTableDomainImpl<T> {};
+
+}  // namespace fuzztest::internal
+#endif  // FUZZTEST_FUZZTEST_INTERNAL_DOMAINS_FLATBUFFERS_DOMAIN_IMPL_H_
diff --git a/fuzztest/internal/meta.h b/fuzztest/internal/meta.h
index 4ddada1..c36d80b 100644
--- a/fuzztest/internal/meta.h
+++ b/fuzztest/internal/meta.h
@@ -200,6 +200,22 @@
 inline constexpr bool is_protocol_buffer_enum_v =
     IsProtocolBufferEnumImpl<T>(true);
 
+template <typename, typename = void>
+inline constexpr bool is_flatbuffers_table_v = false;
+
+// Flatbuffers tables generated structs do not have a public base class, so we
+// check for a few specific methods:
+//  - T is a struct.
+//  - T has a `Builder` type.
+//  - T has a `BinarySchema` type with a static method `data()` (only available
+//    when passing `--bfbs-gen-embed` to the flatbuffer compiler).
+//  - T has a static method called `GetFullyQualifiedName` (only available when
+//    passing `--gen-name-strings` to the flatbuffer compiler).
+template <typename T>
+inline constexpr bool is_flatbuffers_table_v<
+    T, std::void_t<typename T::Builder, decltype(T::BinarySchema::data()),
+                   decltype(T::GetFullyQualifiedName())>> = true;
+
 template <typename T>
 inline constexpr bool has_size_v =
     Requires<T>([](auto v) -> decltype(v.size()) {});
diff --git a/fuzztest/internal/test_flatbuffers.fbs b/fuzztest/internal/test_flatbuffers.fbs
new file mode 100644
index 0000000..515def1
--- /dev/null
+++ b/fuzztest/internal/test_flatbuffers.fbs
@@ -0,0 +1,234 @@
+namespace fuzztest.internal;
+
+enum ByteEnum: byte {
+  First,
+  Second
+}
+enum ShortEnum: short {
+  First,
+  Second
+}
+
+enum IntEnum: int {
+  First,
+  Second
+}
+
+enum LongEnum: long {
+  First,
+  Second
+}
+
+enum UByteEnum: ubyte {
+  First,
+  Second
+}
+
+enum UShortEnum: ushort {
+  First,
+  Second
+}
+enum UIntEnum: uint {
+  First,
+  Second
+}
+enum ULongEnum: ulong {
+  First,
+  Second
+}
+
+struct BoolStruct {
+  b: bool;
+  a_b: [bool:2];
+}
+
+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;
+  a_b: [bool:2];
+  a_i8: [byte:2];
+  a_i16: [short:2];
+  a_i32: [int:2];
+  a_i64: [long:2];
+  a_u8: [ubyte:2];
+  a_u16: [ushort:2];
+  a_u32: [uint:2];
+  a_u64: [ulong:2];
+  a_f: [float:2];
+  a_d: [double:2];
+  a_ei8: [ByteEnum:2];
+  a_ei16: [ShortEnum:2];
+  a_ei32: [IntEnum:2];
+  a_ei64: [LongEnum:2];
+  a_eu8: [UByteEnum:2];
+  a_eu16: [UShortEnum:2];
+  a_eu32: [UIntEnum:2];
+  a_eu64: [ULongEnum:2];
+  a_s: [BoolStruct:2];
+}
+
+table BoolTable {
+  b: bool;
+}
+
+table StringTable {
+  str: string;
+}
+
+union Union {
+  BoolTable,
+  StringTable,
+  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;
+  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;
+  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);
+  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);
+}
+
+root_type DefaultTable;