samscott89 / serde_qs

Serde support for querystring-style strings
Apache License 2.0
193 stars 68 forks source link

How to deserialize a checkbox #68

Open ssendev opened 2 years ago

ssendev commented 2 years ago

In Ruby on Rails checkboxes are usually used like

<input type="hidden" name="box" value="false" />
<input type="checkbox" name="box" value="true" />

since that way it's possible to model an Option<bool> but that causes Failed to deserialize query string: duplicate field 'box' Adding #[serde(keep_last)] is apparently not a thing: https://github.com/serde-rs/serde/issues/690. Is there a way to get it to work?

samscott89 commented 2 years ago

Hey @ssendev. That's an interesting case! I haven't seen that before. So the query string that's generated would be post?box=false&box=true if the checkbox is checked? That's kind of surprising.

Can you show me where that comes up? For example, checking out the rails guide, that's not what I'm seeing: https://guides.rubyonrails.org/v5.0/form_helpers.html#checkboxes

samscott89 commented 2 years ago

It wouldn't be possible to handle this in serde_qs currently. It would need to be added as new logic.

If you can change the name fields, you could do:

<input type="hidden" name="box[]" value="false" />
<input type="checkbox" name="box[]" value="true" />

and deserialize to a box: Vec<bool>. Which you could make into a bool with box.iter().any(|checked| checked)

ssendev commented 2 years ago

@samscott89 Yes that's the query string. The hidden field is there when instead of check_box_tag check_box is used.

Yeah I used a similar Vec workaround though mine was more janky. I guess i hoped there would be something similar to the Flatten workaround which would allow to have an Option<bool>.

Usability wise I imagined it similar to serde_with. Something like

#[derive(Deserialize, Serialize, Debug, PartialEq)]
struct Query {
    #[serde_into(into="vec_to_bool", over="Vec<bool>")]
    common: Option<bool>,
}

fn vec_to_bool(vec: Vec<bool>) -> Option<bool> {
  if vec.empty() {
    return None
  } else {
    Some(vec.iter().any(|checked| checked)
  }
}

But i can live with the Vec in the struct it just leaks some implementation details. So just consider this a low prio feature request and maybe someone comes along that knows of a solution.

samscott89 commented 2 years ago

Thank you for the reference, that's exactly what I was looking for.

This line is very telling:

Since the HTML specification says key/value pairs have to be sent in the same order they appear in the form, and parameters extraction gets the last occurrence of any repeated key in the query string, that works for ordinary forms.

Whereas what's implemented here is something stricter: make sure there are no repeated keys (that aren't vector elements as indicated by [] or [i]).

I'll change this behaviour to match the spec, probably with an option somewhere to toggle the previous behaviour. Thank you!