From d0ea428e23e01c216474ca2a68c4ebc91b3f5e50 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Sat, 26 Apr 2025 17:53:56 +0100 Subject: [PATCH] Add `encode_empty_tables_as_array` serialize option. This will change the behaviour of encoding empty Lua tables into array instead of map. --- src/serde/de.rs | 18 +++++++++++++++++ src/table.rs | 5 ++++- src/value.rs | 9 +++++++++ tests/serde.rs | 51 +++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 82 insertions(+), 1 deletion(-) diff --git a/src/serde/de.rs b/src/serde/de.rs index 0a94117..69d9056 100644 --- a/src/serde/de.rs +++ b/src/serde/de.rs @@ -49,6 +49,11 @@ pub struct Options { /// /// Default: **false** pub sort_keys: bool, + + /// If true, empty Lua tables will be encoded as array, instead of map. + /// + /// Default: **false** + pub encode_empty_tables_as_array: bool, } impl Default for Options { @@ -64,6 +69,7 @@ impl Options { deny_unsupported_types: true, deny_recursive_tables: true, sort_keys: false, + encode_empty_tables_as_array: false, } } @@ -93,6 +99,15 @@ impl Options { self.sort_keys = enabled; self } + + /// Sets [`encode_empty_tables_as_array`] option. + /// + /// [`encode_empty_tables_as_array`]: #structfield.encode_empty_tables_as_array + #[must_use] + pub const fn encode_empty_tables_as_array(mut self, enabled: bool) -> Self { + self.encode_empty_tables_as_array = enabled; + self + } } impl Deserializer { @@ -141,6 +156,9 @@ impl<'de> serde::Deserializer<'de> for Deserializer { Err(_) => visitor.visit_bytes(&s.as_bytes()), }, Value::Table(ref t) if t.raw_len() > 0 || t.is_array() => self.deserialize_seq(visitor), + Value::Table(ref t) if self.options.encode_empty_tables_as_array && t.is_empty() => { + self.deserialize_seq(visitor) + } Value::Table(_) => self.deserialize_map(visitor), Value::LightUserData(ud) if ud.0.is_null() => visitor.visit_none(), Value::UserData(ud) if ud.is_serializable() => { diff --git a/src/table.rs b/src/table.rs index 0b63efd..8d43ff5 100644 --- a/src/table.rs +++ b/src/table.rs @@ -1020,7 +1020,10 @@ impl Serialize for SerializableTable<'_> { // Array let len = self.table.raw_len(); - if len > 0 || self.table.is_array() { + if len > 0 + || self.table.is_array() + || (self.options.encode_empty_tables_as_array && self.table.is_empty()) + { let mut seq = serializer.serialize_seq(Some(len))?; let mut serialize_err = None; let res = self.table.for_each_value::(|value| { diff --git a/src/value.rs b/src/value.rs index 428ce20..119f147 100644 --- a/src/value.rs +++ b/src/value.rs @@ -700,6 +700,15 @@ impl<'a> SerializableValue<'a> { self.options.sort_keys = enabled; self } + + /// If true, empty Lua tables will be encoded as array, instead of map. + /// + /// Default: **false** + #[must_use] + pub const fn encode_empty_tables_as_array(mut self, enabled: bool) -> Self { + self.options.encode_empty_tables_as_array = enabled; + self + } } #[cfg(feature = "serialize")] diff --git a/tests/serde.rs b/tests/serde.rs index 167095f..9e3d598 100644 --- a/tests/serde.rs +++ b/tests/serde.rs @@ -249,6 +249,26 @@ fn test_serialize_same_table_twice() -> LuaResult<()> { Ok(()) } +#[test] +fn test_serialize_empty_table() -> LuaResult<()> { + let lua = Lua::new(); + + let table = Value::Table(lua.create_table()?); + let json = serde_json::to_string(&table.to_serializable()).unwrap(); + assert_eq!(json, "{}"); + + // Set the option to encode empty tables as array + let json = serde_json::to_string(&table.to_serializable().encode_empty_tables_as_array(true)).unwrap(); + assert_eq!(json, "[]"); + + // Check hashmap table with this option + table.as_table().unwrap().set("hello", "world")?; + let json = serde_json::to_string(&table.to_serializable().encode_empty_tables_as_array(true)).unwrap(); + assert_eq!(json, r#"{"hello":"world"}"#); + + Ok(()) +} + #[test] fn test_to_value_struct() -> LuaResult<()> { let lua = Lua::new(); @@ -667,6 +687,37 @@ fn test_from_value_userdata() -> Result<(), Box> { Ok(()) } +#[test] +fn test_from_value_empty_table() -> Result<(), Box> { + let lua = Lua::new(); + + // By default we encode empty tables as objects + let t = lua.create_table()?; + let got = lua.from_value::(Value::Table(t.clone()))?; + assert_eq!(got, serde_json::json!({})); + + // Set the option to encode empty tables as array + let got = lua + .from_value_with::( + Value::Table(t.clone()), + DeserializeOptions::new().encode_empty_tables_as_array(true), + ) + .unwrap(); + assert_eq!(got, serde_json::json!([])); + + // Check hashmap table with this option + t.raw_set("hello", "world")?; + let got = lua + .from_value_with::( + Value::Table(t), + DeserializeOptions::new().encode_empty_tables_as_array(true), + ) + .unwrap(); + assert_eq!(got, serde_json::json!({"hello": "world"})); + + Ok(()) +} + #[test] fn test_from_value_sorted() -> Result<(), Box> { let lua = Lua::new();