From 4b9b10c3a74fb6b904898531a6857cfc5c3474f9 Mon Sep 17 00:00:00 2001 From: RenzoMXD <170978465+RenzoMXD@users.noreply.github.com> Date: Tue, 10 Mar 2026 21:28:15 +0100 Subject: [PATCH 1/2] feat(rust) Add serde Deserialize support for Object API types --- rust/flatbuffers/Cargo.toml | 2 +- src/idl_gen_rust.cpp | 47 +++++++++++++++++++++++++++---------- 2 files changed, 36 insertions(+), 13 deletions(-) diff --git a/rust/flatbuffers/Cargo.toml b/rust/flatbuffers/Cargo.toml index 9dba11a542f..5a19d4ee346 100644 --- a/rust/flatbuffers/Cargo.toml +++ b/rust/flatbuffers/Cargo.toml @@ -14,7 +14,7 @@ rust-version = "1.51" [features] default = ["std"] std = [] -serialize = ["serde"] +serialize = ["serde", "serde/derive"] [dependencies] bitflags = "2.8.0" diff --git a/src/idl_gen_rust.cpp b/src/idl_gen_rust.cpp index 3430a3486c6..4f467957c1d 100644 --- a/src/idl_gen_rust.cpp +++ b/src/idl_gen_rust.cpp @@ -854,13 +854,18 @@ class RustGenerator : public BaseGenerator { code_ += "}"; code_ += ""; - if (!IsBitFlagsEnum(enum_def)) { - code_ += "impl<'de> serde::Deserialize<'de> for {{ENUM_TY}} {"; + code_ += "impl<'de> serde::Deserialize<'de> for {{ENUM_TY}} {"; + code_ += + " fn deserialize(deserializer: D) -> Result"; + code_ += " where"; + code_ += " D: serde::Deserializer<'de>,"; + code_ += " {"; + if (IsBitFlagsEnum(enum_def)) { code_ += - " fn deserialize(deserializer: D) -> Result"; - code_ += " where"; - code_ += " D: serde::Deserializer<'de>,"; - code_ += " {"; + " let bits = <{{BASE_TYPE}} as " + "serde::Deserialize>::deserialize(deserializer)?;"; + code_ += " Ok(Self::from_bits_retain(bits as {{BASE_TYPE}}))"; + } else { code_ += " let s = String::deserialize(deserializer)?;"; code_ += " for item in {{ENUM_TY}}::ENUM_VALUES {"; code_ += @@ -874,10 +879,10 @@ class RustGenerator : public BaseGenerator { code_ += " Err(serde::de::Error::custom(format!("; code_ += " \"Unknown {{ENUM_TY}} variant: {s}\""; code_ += " )))"; - code_ += " }"; - code_ += "}"; - code_ += ""; } + code_ += " }"; + code_ += "}"; + code_ += ""; } // Generate Follow and Push so we can serialize and stuff. @@ -982,7 +987,13 @@ class RustGenerator : public BaseGenerator { code_ += "#[allow(clippy::upper_case_acronyms)]"; // NONE's spelling is // intended. code_ += "#[non_exhaustive]"; - code_ += "#[derive(Debug, Clone, PartialEq)]"; + if (parser_.opts.rust_serialize) { + code_ += + "#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]"; + code_ += "#[serde(tag = \"type\", content = \"value\")]"; + } else { + code_ += "#[derive(Debug, Clone, PartialEq)]"; + } code_ += "{{ACCESS_TYPE}} enum {{ENUM_OTY}} {"; code_ += " NONE,"; ForAllUnionObjectVariantsBesidesNone(enum_def, [&] { @@ -2322,12 +2333,20 @@ class RustGenerator : public BaseGenerator { // Generate the native object. code_ += "#[non_exhaustive]"; - code_ += "#[derive(Debug, Clone, PartialEq)]"; + if (parser_.opts.rust_serialize) { + code_ += "#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]"; + } else { + code_ += "#[derive(Debug, Clone, PartialEq)]"; + } code_ += "{{ACCESS_TYPE}} struct {{STRUCT_OTY}} {"; ForAllObjectTableFields(table, [&](const FieldDef& field) { // Union objects combine both the union discriminant and value, so we // skip making a field for the discriminant. if (field.value.type.base_type == BASE_TYPE_UTYPE) return; + if (parser_.opts.rust_serialize && field.IsOptional() && + !IsUnion(field.value.type)) { + code_ += "#[serde(default, skip_serializing_if = \"Option::is_none\")]"; + } code_ += "pub {{FIELD}}: {{FIELD_OTY}},"; }); code_ += "}"; @@ -3037,7 +3056,11 @@ class RustGenerator : public BaseGenerator { if (parser_.opts.generate_object_based_api) { // Struct declaration code_ += ""; - code_ += "#[derive(Debug, Clone, PartialEq)]"; + if (parser_.opts.rust_serialize) { + code_ += "#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]"; + } else { + code_ += "#[derive(Debug, Clone, PartialEq)]"; + } code_ += "{{ACCESS_TYPE}} struct {{STRUCT_OTY}} {"; ForAllStructFields(struct_def, [&](const FieldDef& field) { (void)field; // unused. From 38c2865fd54d9e49eac37427fa015f7a8748751d Mon Sep 17 00:00:00 2001 From: RenzoMXD <170978465+RenzoMXD@users.noreply.github.com> Date: Tue, 10 Mar 2026 21:35:04 +0100 Subject: [PATCH 2/2] fix: test cases for deserialize --- .../my_game/example/ability_generated.rs | 2 +- .../any_ambiguous_aliases_generated.rs | 3 +- .../my_game/example/any_generated.rs | 3 +- .../example/any_unique_aliases_generated.rs | 3 +- .../my_game/example/color_generated.rs | 10 + .../my_game/example/long_enum_generated.rs | 10 + .../my_game/example/monster_generated.rs | 27 ++- .../my_game/example/referrable_generated.rs | 2 +- .../my_game/example/stat_generated.rs | 3 +- .../example/struct_of_structs_generated.rs | 2 +- .../struct_of_structs_of_structs_generated.rs | 2 +- .../my_game/example/test_generated.rs | 2 +- .../test_simple_table_with_enum_generated.rs | 2 +- .../my_game/example/type_aliases_generated.rs | 4 +- .../my_game/example/vec_3_generated.rs | 2 +- .../my_game/example_2/monster_generated.rs | 2 +- .../my_game/in_parent_namespace_generated.rs | 2 +- .../other_name_space/table_b_generated.rs | 3 +- .../other_name_space/unused_generated.rs | 2 +- .../table_a_generated.rs | 3 +- tests/rust_serialize_test/src/main.rs | 216 ++++++++++++++++++ 21 files changed, 287 insertions(+), 18 deletions(-) diff --git a/tests/monster_test_serialize/my_game/example/ability_generated.rs b/tests/monster_test_serialize/my_game/example/ability_generated.rs index aba446c6d05..37f51a8b41b 100644 --- a/tests/monster_test_serialize/my_game/example/ability_generated.rs +++ b/tests/monster_test_serialize/my_game/example/ability_generated.rs @@ -174,7 +174,7 @@ impl<'a> Ability { } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] pub struct AbilityT { pub id: u32, pub distance: u32, diff --git a/tests/monster_test_serialize/my_game/example/any_ambiguous_aliases_generated.rs b/tests/monster_test_serialize/my_game/example/any_ambiguous_aliases_generated.rs index 415961c78a3..c7b47b90dfb 100644 --- a/tests/monster_test_serialize/my_game/example/any_ambiguous_aliases_generated.rs +++ b/tests/monster_test_serialize/my_game/example/any_ambiguous_aliases_generated.rs @@ -140,7 +140,8 @@ pub struct AnyAmbiguousAliasesUnionTableOffset {} #[allow(clippy::upper_case_acronyms)] #[non_exhaustive] -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(tag = "type", content = "value")] pub enum AnyAmbiguousAliasesT { NONE, M1(alloc::boxed::Box), diff --git a/tests/monster_test_serialize/my_game/example/any_generated.rs b/tests/monster_test_serialize/my_game/example/any_generated.rs index 5f4ab10c045..6f32556bbbd 100644 --- a/tests/monster_test_serialize/my_game/example/any_generated.rs +++ b/tests/monster_test_serialize/my_game/example/any_generated.rs @@ -140,7 +140,8 @@ pub struct AnyUnionTableOffset {} #[allow(clippy::upper_case_acronyms)] #[non_exhaustive] -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(tag = "type", content = "value")] pub enum AnyT { NONE, Monster(alloc::boxed::Box), diff --git a/tests/monster_test_serialize/my_game/example/any_unique_aliases_generated.rs b/tests/monster_test_serialize/my_game/example/any_unique_aliases_generated.rs index 12286c6835c..66d9243f62e 100644 --- a/tests/monster_test_serialize/my_game/example/any_unique_aliases_generated.rs +++ b/tests/monster_test_serialize/my_game/example/any_unique_aliases_generated.rs @@ -140,7 +140,8 @@ pub struct AnyUniqueAliasesUnionTableOffset {} #[allow(clippy::upper_case_acronyms)] #[non_exhaustive] -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(tag = "type", content = "value")] pub enum AnyUniqueAliasesT { NONE, M(alloc::boxed::Box), diff --git a/tests/monster_test_serialize/my_game/example/color_generated.rs b/tests/monster_test_serialize/my_game/example/color_generated.rs index 12ea6d5aa47..3534ada8656 100644 --- a/tests/monster_test_serialize/my_game/example/color_generated.rs +++ b/tests/monster_test_serialize/my_game/example/color_generated.rs @@ -32,6 +32,16 @@ impl Serialize for Color { } } +impl<'de> serde::Deserialize<'de> for Color { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let bits = ::deserialize(deserializer)?; + Ok(Self::from_bits_retain(bits as u8)) + } +} + impl<'a> ::flatbuffers::Follow<'a> for Color { type Inner = Self; diff --git a/tests/monster_test_serialize/my_game/example/long_enum_generated.rs b/tests/monster_test_serialize/my_game/example/long_enum_generated.rs index 90ad8e4f538..5932f5b12f5 100644 --- a/tests/monster_test_serialize/my_game/example/long_enum_generated.rs +++ b/tests/monster_test_serialize/my_game/example/long_enum_generated.rs @@ -28,6 +28,16 @@ impl Serialize for LongEnum { } } +impl<'de> serde::Deserialize<'de> for LongEnum { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let bits = ::deserialize(deserializer)?; + Ok(Self::from_bits_retain(bits as u64)) + } +} + impl<'a> ::flatbuffers::Follow<'a> for LongEnum { type Inner = Self; diff --git a/tests/monster_test_serialize/my_game/example/monster_generated.rs b/tests/monster_test_serialize/my_game/example/monster_generated.rs index 26652e1c953..3a9802c183a 100644 --- a/tests/monster_test_serialize/my_game/example/monster_generated.rs +++ b/tests/monster_test_serialize/my_game/example/monster_generated.rs @@ -1972,20 +1972,28 @@ impl ::core::fmt::Debug for Monster<'_> { } #[non_exhaustive] -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] pub struct MonsterT { + #[serde(default, skip_serializing_if = "Option::is_none")] pub pos: Option, pub mana: i16, pub hp: i16, pub name: alloc::string::String, + #[serde(default, skip_serializing_if = "Option::is_none")] pub inventory: Option>, pub color: Color, pub test: AnyT, + #[serde(default, skip_serializing_if = "Option::is_none")] pub test4: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] pub testarrayofstring: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] pub testarrayoftables: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] pub enemy: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] pub testnestedflatbuffer: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] pub testempty: Option>, pub testbool: bool, pub testhashs32_fnv1: i32, @@ -1996,31 +2004,48 @@ pub struct MonsterT { pub testhashu32_fnv1a: u32, pub testhashs64_fnv1a: i64, pub testhashu64_fnv1a: u64, + #[serde(default, skip_serializing_if = "Option::is_none")] pub testarrayofbools: Option>, pub testf: f32, pub testf2: f32, pub testf3: f32, + #[serde(default, skip_serializing_if = "Option::is_none")] pub testarrayofstring2: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] pub testarrayofsortedstruct: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] pub flex: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] pub test5: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] pub vector_of_longs: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] pub vector_of_doubles: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] pub parent_namespace_test: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] pub vector_of_referrables: Option>, pub single_weak_reference: u64, + #[serde(default, skip_serializing_if = "Option::is_none")] pub vector_of_weak_references: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] pub vector_of_strong_referrables: Option>, pub co_owning_reference: u64, + #[serde(default, skip_serializing_if = "Option::is_none")] pub vector_of_co_owning_references: Option>, pub non_owning_reference: u64, + #[serde(default, skip_serializing_if = "Option::is_none")] pub vector_of_non_owning_references: Option>, pub any_unique: AnyUniqueAliasesT, pub any_ambiguous: AnyAmbiguousAliasesT, + #[serde(default, skip_serializing_if = "Option::is_none")] pub vector_of_enums: Option>, pub signed_enum: Race, + #[serde(default, skip_serializing_if = "Option::is_none")] pub testrequirednestedflatbuffer: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] pub scalar_key_sorted_tables: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] pub native_inline: Option, pub long_enum_non_enum_default: LongEnum, pub long_enum_normal_default: LongEnum, diff --git a/tests/monster_test_serialize/my_game/example/referrable_generated.rs b/tests/monster_test_serialize/my_game/example/referrable_generated.rs index d7224c45d14..7ee32737778 100644 --- a/tests/monster_test_serialize/my_game/example/referrable_generated.rs +++ b/tests/monster_test_serialize/my_game/example/referrable_generated.rs @@ -142,7 +142,7 @@ impl ::core::fmt::Debug for Referrable<'_> { } #[non_exhaustive] -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] pub struct ReferrableT { pub id: u64, } diff --git a/tests/monster_test_serialize/my_game/example/stat_generated.rs b/tests/monster_test_serialize/my_game/example/stat_generated.rs index eb81b1cdf1d..371f3d7055e 100644 --- a/tests/monster_test_serialize/my_game/example/stat_generated.rs +++ b/tests/monster_test_serialize/my_game/example/stat_generated.rs @@ -192,8 +192,9 @@ impl ::core::fmt::Debug for Stat<'_> { } #[non_exhaustive] -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] pub struct StatT { + #[serde(default, skip_serializing_if = "Option::is_none")] pub id: Option, pub val: i64, pub count: u16, diff --git a/tests/monster_test_serialize/my_game/example/struct_of_structs_generated.rs b/tests/monster_test_serialize/my_game/example/struct_of_structs_generated.rs index 17b8d7cf0e6..cc916078337 100644 --- a/tests/monster_test_serialize/my_game/example/struct_of_structs_generated.rs +++ b/tests/monster_test_serialize/my_game/example/struct_of_structs_generated.rs @@ -146,7 +146,7 @@ impl<'a> StructOfStructs { } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] pub struct StructOfStructsT { pub a: AbilityT, pub b: TestT, diff --git a/tests/monster_test_serialize/my_game/example/struct_of_structs_of_structs_generated.rs b/tests/monster_test_serialize/my_game/example/struct_of_structs_of_structs_generated.rs index 743baba2325..79d4380f4e4 100644 --- a/tests/monster_test_serialize/my_game/example/struct_of_structs_of_structs_generated.rs +++ b/tests/monster_test_serialize/my_game/example/struct_of_structs_of_structs_generated.rs @@ -112,7 +112,7 @@ impl<'a> StructOfStructsOfStructs { } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] pub struct StructOfStructsOfStructsT { pub a: StructOfStructsT, } diff --git a/tests/monster_test_serialize/my_game/example/test_generated.rs b/tests/monster_test_serialize/my_game/example/test_generated.rs index ed1bffa036c..bf0409b5c03 100644 --- a/tests/monster_test_serialize/my_game/example/test_generated.rs +++ b/tests/monster_test_serialize/my_game/example/test_generated.rs @@ -163,7 +163,7 @@ impl<'a> Test { } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] pub struct TestT { pub a: i16, pub b: i8, diff --git a/tests/monster_test_serialize/my_game/example/test_simple_table_with_enum_generated.rs b/tests/monster_test_serialize/my_game/example/test_simple_table_with_enum_generated.rs index 6cd14b1e73d..e3592cb4903 100644 --- a/tests/monster_test_serialize/my_game/example/test_simple_table_with_enum_generated.rs +++ b/tests/monster_test_serialize/my_game/example/test_simple_table_with_enum_generated.rs @@ -131,7 +131,7 @@ impl ::core::fmt::Debug for TestSimpleTableWithEnum<'_> { } #[non_exhaustive] -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] pub struct TestSimpleTableWithEnumT { pub color: Color, } diff --git a/tests/monster_test_serialize/my_game/example/type_aliases_generated.rs b/tests/monster_test_serialize/my_game/example/type_aliases_generated.rs index 698b5f729b6..f497275e0cb 100644 --- a/tests/monster_test_serialize/my_game/example/type_aliases_generated.rs +++ b/tests/monster_test_serialize/my_game/example/type_aliases_generated.rs @@ -385,7 +385,7 @@ impl ::core::fmt::Debug for TypeAliases<'_> { } #[non_exhaustive] -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] pub struct TypeAliasesT { pub i8_: i8, pub u8_: u8, @@ -397,7 +397,9 @@ pub struct TypeAliasesT { pub u64_: u64, pub f32_: f32, pub f64_: f64, + #[serde(default, skip_serializing_if = "Option::is_none")] pub v8: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] pub vf64: Option>, } diff --git a/tests/monster_test_serialize/my_game/example/vec_3_generated.rs b/tests/monster_test_serialize/my_game/example/vec_3_generated.rs index 3d2d6119924..dbb9b2da508 100644 --- a/tests/monster_test_serialize/my_game/example/vec_3_generated.rs +++ b/tests/monster_test_serialize/my_game/example/vec_3_generated.rs @@ -282,7 +282,7 @@ impl<'a> Vec3 { } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] pub struct Vec3T { pub x: f32, pub y: f32, diff --git a/tests/monster_test_serialize/my_game/example_2/monster_generated.rs b/tests/monster_test_serialize/my_game/example_2/monster_generated.rs index 7c5856e6cbf..117287fe649 100644 --- a/tests/monster_test_serialize/my_game/example_2/monster_generated.rs +++ b/tests/monster_test_serialize/my_game/example_2/monster_generated.rs @@ -108,7 +108,7 @@ impl ::core::fmt::Debug for Monster<'_> { } #[non_exhaustive] -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] pub struct MonsterT { } diff --git a/tests/monster_test_serialize/my_game/in_parent_namespace_generated.rs b/tests/monster_test_serialize/my_game/in_parent_namespace_generated.rs index 2b4bcc16988..3a43ae050d8 100644 --- a/tests/monster_test_serialize/my_game/in_parent_namespace_generated.rs +++ b/tests/monster_test_serialize/my_game/in_parent_namespace_generated.rs @@ -108,7 +108,7 @@ impl ::core::fmt::Debug for InParentNamespace<'_> { } #[non_exhaustive] -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] pub struct InParentNamespaceT { } diff --git a/tests/monster_test_serialize/my_game/other_name_space/table_b_generated.rs b/tests/monster_test_serialize/my_game/other_name_space/table_b_generated.rs index 177cfeb48e4..c7ac8e3b119 100644 --- a/tests/monster_test_serialize/my_game/other_name_space/table_b_generated.rs +++ b/tests/monster_test_serialize/my_game/other_name_space/table_b_generated.rs @@ -137,8 +137,9 @@ impl ::core::fmt::Debug for TableB<'_> { } #[non_exhaustive] -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] pub struct TableBT { + #[serde(default, skip_serializing_if = "Option::is_none")] pub a: Option>, } diff --git a/tests/monster_test_serialize/my_game/other_name_space/unused_generated.rs b/tests/monster_test_serialize/my_game/other_name_space/unused_generated.rs index 99540dba58b..e555be2f61c 100644 --- a/tests/monster_test_serialize/my_game/other_name_space/unused_generated.rs +++ b/tests/monster_test_serialize/my_game/other_name_space/unused_generated.rs @@ -129,7 +129,7 @@ impl<'a> Unused { } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] pub struct UnusedT { pub a: i32, } diff --git a/tests/monster_test_serialize/table_a_generated.rs b/tests/monster_test_serialize/table_a_generated.rs index d079d5f9b50..7b451448355 100644 --- a/tests/monster_test_serialize/table_a_generated.rs +++ b/tests/monster_test_serialize/table_a_generated.rs @@ -137,8 +137,9 @@ impl ::core::fmt::Debug for TableA<'_> { } #[non_exhaustive] -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] pub struct TableAT { + #[serde(default, skip_serializing_if = "Option::is_none")] pub b: Option>, } diff --git a/tests/rust_serialize_test/src/main.rs b/tests/rust_serialize_test/src/main.rs index 0978da04911..e900ab86bdb 100644 --- a/tests/rust_serialize_test/src/main.rs +++ b/tests/rust_serialize_test/src/main.rs @@ -4,6 +4,7 @@ mod monster_test_serialize_generated; pub use monster_test_serialize_generated::my_game; use crate::my_game::example::AnyAmbiguousAliases; +use crate::my_game::example::{Color, MonsterT, TestT, Vec3T, AnyT}; use std::collections::HashMap; fn create_serialized_example_with_generated_code(builder: &mut flatbuffers::FlatBufferBuilder) { @@ -93,4 +94,219 @@ fn main() { let s = r#"{"val":"M1"}"#; let des = serde_json::from_str::>(s).unwrap(); assert_eq!(*des.get("val").unwrap(), AnyAmbiguousAliases::M1); + + // Test Object API deserialization (JSON -> MonsterT) + test_object_api_json_roundtrip(); + test_object_api_full_pipeline(); + test_object_api_deserialize_from_json_string(); + test_bitflags_enum_deserialize(); + test_union_deserialize(); +} + +/// Test JSON round-trip: MonsterT -> JSON -> MonsterT +fn test_object_api_json_roundtrip() { + let monster = MonsterT { + name: "Orc".to_string(), + hp: 300, + mana: 150, + pos: Some(Vec3T { + x: 1.0, + y: 2.0, + z: 3.0, + test1: 0.0, + test2: Color::Red, + test3: TestT { a: 10, b: 20 }, + }), + inventory: Some(vec![1, 2, 3, 4, 5]), + color: Color::Blue, + test4: Some(vec![TestT { a: 10, b: 20 }]), + testarrayofstring: Some(vec!["hello".to_string(), "world".to_string()]), + testbool: true, + // Override NaN/Inf defaults to normal values for JSON round-trip + // (JSON format cannot represent NaN/Infinity) + nan_default: 0.0, + inf_default: 0.0, + positive_inf_default: 0.0, + infinity_default: 0.0, + positive_infinity_default: 0.0, + negative_inf_default: 0.0, + negative_infinity_default: 0.0, + double_inf_default: 0.0, + ..Default::default() + }; + + let json = serde_json::to_string(&monster).unwrap(); + let deserialized: MonsterT = serde_json::from_str(&json).unwrap(); + assert_eq!(monster, deserialized, "Object API JSON round-trip failed"); + + // Verify specific fields survived the round-trip + assert_eq!(deserialized.name, "Orc"); + assert_eq!(deserialized.hp, 300); + assert_eq!(deserialized.mana, 150); + assert_eq!(deserialized.color, Color::Blue); + assert_eq!(deserialized.testbool, true); + assert!(deserialized.pos.is_some()); + let pos = deserialized.pos.unwrap(); + assert_eq!(pos.x, 1.0); + assert_eq!(pos.y, 2.0); + assert_eq!(pos.z, 3.0); + assert_eq!(deserialized.inventory, Some(vec![1, 2, 3, 4, 5])); + assert_eq!(deserialized.testarrayofstring, Some(vec!["hello".to_string(), "world".to_string()])); + + eprintln!("OK: Object API JSON round-trip passed"); +} + +/// Test full pipeline: MonsterT -> FlatBuffer -> MonsterT -> JSON -> MonsterT -> FlatBuffer +fn test_object_api_full_pipeline() { + // Create a MonsterT with known values (avoiding NaN/Inf for JSON compat) + let original = MonsterT { + name: "PipelineMonster".to_string(), + hp: 200, + mana: 100, + pos: Some(Vec3T { + x: 5.0, + y: 6.0, + z: 7.0, + test1: 1.0, + test2: Color::Green, + test3: TestT { a: 1, b: 2 }, + }), + inventory: Some(vec![10, 20, 30]), + testbool: true, + nan_default: 0.0, + inf_default: 0.0, + positive_inf_default: 0.0, + infinity_default: 0.0, + positive_infinity_default: 0.0, + negative_inf_default: 0.0, + negative_infinity_default: 0.0, + double_inf_default: 0.0, + ..Default::default() + }; + + // Pack to FlatBuffer + let mut builder = flatbuffers::FlatBufferBuilder::new(); + let offset = original.pack(&mut builder); + builder.finish(offset, None); + let data = builder.finished_data(); + + // Unpack from FlatBuffer + let fb_monster = my_game::example::root_as_monster(data).unwrap(); + let unpacked = fb_monster.unpack(); + assert_eq!(unpacked.name, "PipelineMonster"); + + // Serialize to JSON + let json = serde_json::to_string(&unpacked).unwrap(); + + // Deserialize back from JSON + let deserialized: MonsterT = serde_json::from_str(&json).unwrap(); + assert_eq!(unpacked, deserialized, "Full pipeline round-trip failed"); + + // Repack to FlatBuffer + let mut builder2 = flatbuffers::FlatBufferBuilder::new(); + let offset2 = deserialized.pack(&mut builder2); + builder2.finish(offset2, None); + let data2 = builder2.finished_data(); + + // Verify the repacked FlatBuffer is readable and correct + let fb_monster2 = my_game::example::root_as_monster(data2).unwrap(); + assert_eq!(fb_monster2.name(), "PipelineMonster"); + assert_eq!(fb_monster2.hp(), 200); + assert_eq!(fb_monster2.mana(), 100); + + eprintln!("OK: Object API full pipeline (FlatBuffer -> JSON -> FlatBuffer) passed"); +} + +/// Test deserializing a hand-written JSON string into MonsterT +fn test_object_api_deserialize_from_json_string() { + let json = r#"{ + "name": "Goblin", + "hp": 50, + "mana": 100, + "color": 8, + "test": { "type": "NONE" }, + "any_unique": { "type": "NONE" }, + "any_ambiguous": { "type": "NONE" }, + "testbool": false, + "testhashs32_fnv1": 0, + "testhashu32_fnv1": 0, + "testhashs64_fnv1": 0, + "testhashu64_fnv1": 0, + "testhashs32_fnv1a": 0, + "testhashu32_fnv1a": 0, + "testhashs64_fnv1a": 0, + "testhashu64_fnv1a": 0, + "testf": 3.14159, + "testf2": 3.0, + "testf3": 0.0, + "single_weak_reference": 0, + "co_owning_reference": 0, + "non_owning_reference": 0, + "signed_enum": "None", + "long_enum_non_enum_default": 0, + "long_enum_normal_default": 2, + "nan_default": 0.0, + "inf_default": 0.0, + "positive_inf_default": 0.0, + "infinity_default": 0.0, + "positive_infinity_default": 0.0, + "negative_inf_default": 0.0, + "negative_infinity_default": 0.0, + "double_inf_default": 0.0 + }"#; + + let monster: MonsterT = serde_json::from_str(json).unwrap(); + assert_eq!(monster.name, "Goblin"); + assert_eq!(monster.hp, 50); + assert_eq!(monster.mana, 100); + assert_eq!(monster.color, Color::Blue); + assert!(monster.pos.is_none()); + assert!(monster.inventory.is_none()); + + eprintln!("OK: Object API deserialize from JSON string passed"); +} + +/// Test bitflags enum deserialization +fn test_bitflags_enum_deserialize() { + // Bitflags serialize/deserialize as numeric values + let json = serde_json::to_string(&Color::Green).unwrap(); + let deserialized: Color = serde_json::from_str(&json).unwrap(); + assert_eq!(deserialized, Color::Green); + + let json = serde_json::to_string(&Color::Blue).unwrap(); + let deserialized: Color = serde_json::from_str(&json).unwrap(); + assert_eq!(deserialized, Color::Blue); + + eprintln!("OK: Bitflags enum deserialization passed"); +} + +/// Test union type deserialization +fn test_union_deserialize() { + // Test NONE union variant + let none_union = AnyT::NONE; + let json = serde_json::to_string(&none_union).unwrap(); + let deserialized: AnyT = serde_json::from_str(&json).unwrap(); + assert_eq!(deserialized, AnyT::NONE); + + // Test union with Monster variant via full round-trip + let inner = MonsterT { + name: "Inner".to_string(), + hp: 42, + // Override NaN/Inf defaults for JSON + nan_default: 0.0, + inf_default: 0.0, + positive_inf_default: 0.0, + infinity_default: 0.0, + positive_infinity_default: 0.0, + negative_inf_default: 0.0, + negative_infinity_default: 0.0, + double_inf_default: 0.0, + ..Default::default() + }; + let union_val = AnyT::Monster(Box::new(inner)); + let json = serde_json::to_string(&union_val).unwrap(); + let deserialized: AnyT = serde_json::from_str(&json).unwrap(); + assert_eq!(deserialized, union_val); + + eprintln!("OK: Union type deserialization passed"); }