mlua icon indicating copy to clipboard operation
mlua copied to clipboard

Error deserializing untagged enums

Open noib3 opened this issue 3 years ago • 3 comments

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.

noib3 avatar Apr 28 '22 12:04 noib3

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

khvzak avatar Apr 28 '22 20:04 khvzak

I can add a new option to from_value_with to interpret {} as an array instead of map. If it helps.

khvzak avatar Apr 28 '22 20:04 khvzak

@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.

noib3 avatar Apr 30 '22 00:04 noib3