monoculum / formam

a package for decode form's values into struct in Go
Apache License 2.0
190 stars 19 forks source link

Call UnmarshalText for every value if the name ends with [] #38

Closed arp242 closed 3 years ago

arp242 commented 3 years ago

I have the following problem: one of my forms has a setting with a bitmask, this all works grand, but right now there is, as far as I can find, no way to tell Formam to decode that since it always uses the first value.

So with my form:

<input type="checkbox" name="settings.collect[]" value="1"> One</label>
<input type="checkbox" name="settings.collect[]" value="2"> Two</label>
<input type="checkbox" name="settings.collect[]" value="4"> Two</label>

Formam will just set the first value that is selected.

With this change, if the name ends with "[]" it will call UnmarshalText() on all values. I think it might make sense to just always do this, but that wouldn't be backwards-compatible so this is probably better.

emilgpa commented 3 years ago

Oops... Sorry, Just now saw this... I don't know if I understand it correctly. With that form, when Formam decode it, it only get the first value in a field that it's a Array?

arp242 commented 3 years ago

Yeah, no worries. There's no hurry.

With that form, when Formam decode it, it only get the first value in a field that it's a Array?

Previously it would only get the first value, but with this it gets all the values. So in the above example formam will first call UnmarhalText for 1, and then with 2, and then with 4, all on the same variable.

emilgpa commented 3 years ago

Do you say something like this?

func TestArray(t *testing.T) {
    s := struct {
        Name []string
    }{
        Name: []string{},
    }
    vals := url.Values{
        "Name[]": []string{"hola", "hello", "salut"},
    }
    dec := formam.NewDecoder(&formam.DecoderOptions{})
    err := dec.Decode(vals, &s)
    if err != nil {
        t.Error(err)
    }
}
arp242 commented 3 years ago

Arrays never come in to play here; it's when you have a single value (string, int); also see the test I added: https://github.com/monoculum/formam/pull/38/files#diff-d4634447a9ee45efb7bb29ccf7305f2987a55d9d4658d9c431f065285ecaad9bR1015

Normally, if you send two values (banana and chocolate), formam will just call UnmarshalText on the first value, and chocolate is ignored, but if you send it with [] at the end, it will call UnmarshalText on all values in a loop, and the end result is banana-chocolate.

This is a bit of an artificial example (it was just easy to write a test like this); in my actual application I have a bitmask like so:

screenshot_2021-01-13-21-17-38

All those checkboxes are stored as a single number; I actually wrote a bit about this last month: Bitmasks for nicer APIs

The problem is, how do I store this in formam? I want to end up with a single number (i.e. 32) and not a slice.

As far as I could tell, there isn't really any good way to solve this without this change; I'd have to go back and look at what I tried, but in a lot of cases formam will just use the first value. I could use an array and then convert that to a number, but it's a bit convoluted as I'd have to create two struct fields: one for formam to scan in, and then set the actual value I want from there.

I hope that clarifies things 😅

emilgpa commented 3 years ago

Sorry again for delay... @arp242.

Ok, I understand now, you can merge it when you want!

arp242 commented 3 years ago

Sorry again for delay

No worries, it's all spare time work, no need to feel guilty and I can wait :-)