mlua
mlua copied to clipboard
Error deserializing untagged enums
In the following code
use mlua::{Lua, LuaSerdeExt};
use serde::Deserialize;
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CompletionItem {
pub label: String,
pub additional_text_edits: Vec<()>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CompletionList {
pub is_incomplete: bool,
pub items: Vec<CompletionItem>,
}
#[derive(Debug, Deserialize)]
#[serde(untagged)]
pub enum CompletionResponse {
CompletionList(CompletionList),
CompletionItems(Vec<CompletionItem>),
}
fn main() -> mlua::Result<()> {
let lua = Lua::new();
// let lua_list = lua.from_value::<CompletionList>(
let lua_list = lua.from_value::<CompletionResponse>(
lua.load(
r#"
{
isIncomplete = true,
items = {
{
label = "self.count",
additionalTextEdits = {},
},
}
}
"#,
)
.eval()?,
);
let json_list = serde_json::from_str::<CompletionResponse>(
r#"
{
"isIncomplete": true,
"items": [
{
"label": "self.count",
"additionalTextEdits": []
}
]
}
"#,
);
// let lua_vec = lua.from_value::<Vec<CompletionItem>>(
let lua_vec = lua.from_value::<CompletionResponse>(
lua.load(
r#"
{
{
label = "self.count",
additionalTextEdits = {},
},
}
"#,
)
.eval()?,
);
let json_vec = serde_json::from_str::<CompletionResponse>(
r#"
[
{
"label": "self.count",
"additionalTextEdits": []
}
]
"#,
);
assert!(lua_list.is_err());
assert!(lua_vec.is_err());
assert!(json_list.is_ok());
assert!(json_vec.is_ok());
Ok(())
}
I try to deserialize two tables into a CompletionResponse untagged enum. Unfortunately the deserialization fails both times.
However, if I try to deserialize into the CompletionList and Vec<CompletionItem> variants directly, then it works. Also, removing the additional_text_edits field from the CompletionItem struct seems to fix it.
For comparison, serde_json deserializes the equivalent data structures properly.
Unfortunately it seems expected behaviour.
In particular {} for Lua deserializer is a map by default, not an array.
When you deserialize into CompletionResponse (an untagged enum) serde tries to probe all possible variants and sees that {} is a map.
When you deserialize into CompletionList serde expects an array and calls deserialize_seq explicitly.
It works when you change the code to force a map being an array:
let lua_list = lua.from_value::<CompletionResponse>(
lua.load(
r#"
{
isIncomplete = true,
items = {
{
label = "self.count",
additionalTextEdits = setmetatable({}, array_mt),
},
}
}
"#,
)
.eval()?,
);
where array_mt is LuaSerdeExt::array_metatable
I can add a new option to from_value_with to interpret {} as an array instead of map. If it helps.
@khvzak that'd certainly help, but it would interpret every {} as an empty array.
I assume there's no way to specify it on a per-field basis (interpreting {} as an empty map or array when deserializing a struct or a vec, respectively), as that's handled by serde, correct? Maybe I should implement serde::Deserialize manually in this case.