Implementing Deserialize for custom struct

I have a struct with fieldname name:

#[derive(Debug, Serialize, Deserialize)]
struct MyStruct {
    name: String,
}

I can deserialize it with:

let complete_str = r#"{ "name": "my_str" }"#;
serde_json::from_str::<MyStruct>(complete_str)

But how can I implement Deserialize for MyStruct, so I can deserialize it with lacking fieldname name?

let no_field_name_str  = "my_str";
serde_json::from_str::<MyStruct>(no_field_name_str)

Do you need support for both the { "name": "my_str" } and the "my_str" JSON types or is the latter enough?

1 Like

Surporting later is enough.

I tried the following, but it doesn't work:

#[derive(Debug, Serialize)]
pub struct InputCodes {
    pub name: String,
}


use std::fmt;
use serde::de::{self, Visitor};

struct InputCodesVistor;

impl<'de> Visitor<'de> for InputCodesVistor {
    type Value = InputCodes;

    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_str("what the fuck is going on?")
    }

    fn visit_str<E>(self, v: &str) -> std::result::Result<Self::Value, E>
    where
        E: de::Error, 
    {
        Ok(InputCodes { name: v.to_owned() })    
    }

    fn visit_string<E>(self, v: String) -> std::result::Result<Self::Value, E>
        where
            E: de::Error,
    {
        Ok(InputCodes { name: v })
    }
}

impl<'de> Deserialize<'de> for InputCodes {
    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
        where
            D: serde::Deserializer<'de>
    {
        deserializer.deserialize_str(InputCodesVistor)
    }
}

As yesterday you mentioned, I am trying define a function in javascript to handle post form data, it looks like follow:

        async function GetData(event) {
            // event.target is reference to the form element
            const data = new FormData(event.target); // Get all input data

            fetch("http://127.0.0.1:8081/result", {
                    method: "POST",
                    headers: new Headers({'content-type': 'application/x-www-form-urlencoded'}),
                    body: data, // Send body
                })
                .then((response) => {
                    var upperCase = response.json();
                    console.log("____", event.target);
                    console.log("aaaa", data);
                    console.log("dddd", upperCase);
                    // var upperCase = upperCase.toUpperCase();
                    // document.getElementById("outputText").innerHTML = upperCase;
                })
                .then((response) => {
                    console.log("gggg");
                    for (const product of data.products) {
                        console.log("ddd", product);
                    }
                    setStatusMessage(response.status);
                })
                .catch(() => {
                    console.log("zzz");
                    setStatusMessage("ERROR");
                })
            }

        const form = document.getElementById("getData")

        form.addEventListener("submit", (event)=>{
            event.preventDefault()
            GetData(event)
        })

But somehow, the function just return "my input text" rather than "{ "name": "my input text"}". So I have to implement custom Deserialize for my struct.

Does #[serde(transparent)] fits your need? It modifies the serializing behavior too. If you don't want that, you can manually expand the macro.

impl<'de> serde::de::Deserialize<'de> for MyStruct {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        String::deserialize(deserializer).map(|name| Self { name })
    }
}
1 Like

Just a quick sanity check: are you extracting the data in your endpoint with the Form extractor? Because you are trying to send x-www-form-urlencoded encoded data, not JSON encoded data to your endpoint.

Yes, thsi is the extractor function:

#[post("/result")]
async fn show_result(
    data: web::Data<Mutex<Repl>>,
    params: web::Form<InputCodes>
) -> impl Responder {
    // let code_str = params.name.replace('\r', "").parse_all();
    println!("{:?}", params);
    let code_str = params.name.to_owned();
    let res = data.lock().unwrap().eval(&code_str).unwrap();
    let res = format!("the code input: {:?} \n, the res: {:?}", params.name, res);
    println!("{res}");
    HttpResponse::Ok()
        .content_type("text/plain; charset=utf-8")
        .body(res)
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            // .wrap(Logger::default())
            .service(say_hello)
            .service(Files::new("/codemirror", "./templates/codemirror").show_files_listing())
            .service(show_html)
            .app_data({
                let repl = get_repl();
                let mutex_repl = Mutex::new(repl);
                web::Data::new(mutex_repl)
            })
            .service(show_result)
    })
    .bind(("127.0.0.1", 8081))?
    .run()
    .await
}

The params is still a type of Form.

This is the whole html file:

<!doctype html>
<html>

<head>
    <title>Online Strategy</title>
    <meta charset="utf-8" />
    <link rel="stylesheet" href="codemirror/lib/codemirror.css">
    <script src="codemirror/lib/codemirror.js"></script>
    <script src="codemirror/addon/mode/simple.js"></script>
    <script src="codemirror/mode/rust/rust.js"></script>
    <link rel="stylesheet" href="codemirror/theme/dracula.css">
</head>

<body>
    
    <form id="getData">
        <textarea id="editor" name="name"></textarea>
        <button type="submit">Run</button>
    </form>
    <script>
        var editor = CodeMirror.fromTextArea(document.getElementById('editor'), {
            lineNumbers: true,
            lineWrapping: true,
            indentUnit: 2,
            tabsize: 2,
            indentWithTabs: 1,
            mode: "rust",
            theme: "dracula",
        });
        editor.setSize(700, 700);
        editor.scrollTo(700, 700);

        async function GetData(event) {
            // event.target is reference to the form element
            const data = new FormData(event.target); // Get all input data

            fetch("http://127.0.0.1:8081/result", {
                    method: "POST",
                    headers: new Headers({'content-type': 'application/x-www-form-urlencoded'}),
                    body: data, // Send body
                })
                .then((response) => {
                    var upperCase = response.json();
                    console.log("____", event.target);
                    console.log("aaaa", data);
                    console.log("dddd", upperCase);
                    // var upperCase = upperCase.toUpperCase();
                    // document.getElementById("outputText").innerHTML = upperCase;
                })
                .then((response) => {
                    console.log("gggg");
                    for (const product of data.products) {
                        console.log("ddd", product);
                    }
                    setStatusMessage(response.status);
                })
                .catch(() => {
                    console.log("zzz");
                    setStatusMessage("ERROR");
                })
            }

        const form = document.getElementById("getData")

        form.addEventListener("submit", (event)=>{
            event.preventDefault()
            GetData(event)
        })
    </script>

    <h3>INPUT</h3>
    <textarea id="inputText" placeholder="input" cols="70" rows="10" wrap="on"></textarea>
    <button onclick="MakeUpper()">Enter</button>
    <h3>OUTPUT</h3>
    <textarea id="outputText" placeholder="output" cols="70" rows="10" wrap="on" readonly></textarea>
    <script>
        function MakeUpper() {
            var upperCase = document.getElementById("inputText").value;
            var upperCase = upperCase.toUpperCase();
            document.getElementById("outputText").innerHTML = upperCase;
        }
    </script>

</body>

</html>

I tried using content-type as 'application/json', but it has the following error:
图片
So, I have to change it to 'application/x-www-form-urlencoded'.

Could you try and replace this call with:

const data = new URLSearchParams(new FormData(formElement));

(found in this SO answer)

I think your encoding is not working properly and hopefully this will fix it. Once your form data is correctly encoded as name=my_str, serde should be able to deserialize it properly with the default deserializer.

1 Like

The latter method you mentioned doesn't work, I made the playground for it.

After chaning to `const data = new URLSearchParams(new FormData(formElement)), it works very well. Thank your so much.

1 Like

Those two methods are identical regarding deserializing. You forgot to quote you string.

fn main() {
    let _complete_str = r#"{ "name": "my_str" }"#;
-    let no_field_name_str  = "my_str";
+    let no_field_name_str  = r#""my_str""#;
    let _ = serde_json::from_str::<MyStruct>(no_field_name_str).unwrap();
}

A lone unquoted string is not valid json.

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.