google / go-querystring

go-querystring is Go library for encoding structs into URL query strings.

Home Page:https://pkg.go.dev/github.com/google/go-querystring/query

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Unable to handle nil pointers when implementing Encoder

meblum opened this issue · comments

With the default decoding, a nil pointer is encoded as an empty string (unless omitempty was specified) so a nil *int will encode to an empty string. But when implementing a custom encoder, there is no way to check if value was nil as the value will be initialized to the zero value before EncodeValues is called.

Could you not just use a pointer to your custom type to allow for the same kind of nil checking?

For example, the following works as I would expect, and I think as you're describing:

package main

import (
	"fmt"
	"net/url"

	"github.com/google/go-querystring/query"
)

type T int

func (t T) EncodeValues(key string, v *url.Values) error {
	v.Add(key, fmt.Sprintf("_%d_", t))
	return nil
}

type options struct {
	*T `url:"t,omitempty"`
}

func main() {
	v, _ := query.Values(options{})
	fmt.Println(v.Encode()) // will output: ""

	v, _ = query.Values(options{T: new(T)})
	fmt.Println(v.Encode()) // will output: "t=_0_"
}

If that doesn't work for you, could you share a code snippet of what you're trying to do?

oh, the other thing you can do is implement the zeroable interface by adding an IsZero() bool method on your custom type. That way, you can do whatever kind of zero checking makes sense for that type. But again, I'm not sure if that covers your use case or not.

Sorry I wasn't clear enough. Was trying to do something like this https://play.golang.org/p/wnzO0oIJI5i

package main

import (
	"fmt"
	"net/url"

	"github.com/google/go-querystring/query"
)

type T int


// EncodeValues encodes a 0 as false, 1 as true, and nil as unknown
func (t *T) EncodeValues(key string, v *url.Values) error {
	if t == nil {
		v.Add(key, "unknown")
		return nil
	}
	if *t == 0 {
		v.Add(key, "false")
		return nil
	}
	if *t == 1 {
		v.Add(key, "true")
		return nil
	}

	return fmt.Errorf("value not supported")
}

type options struct {
	*T `url:"t"`
}

func main() {
	
	v, _ := query.Values(options{})
	fmt.Println(v.Encode()) // will output: false, expected unknown
}

Got it, that makes sense.

That will definitely take a little bit of work to try and make it possible. I tried a naive approach, and keep running into places throughout the package that chokes on nil values because it assumes it gets handled earlier :)

Actually, I'm not even sure if this is possible at all. Since it's a nil pointer, I don't believe there's any way to know what type it would have pointed to is. I believe nil interfaces carry their type information along, but not nil pointers. I could be wrong on that, and will keep looking into it, just sort of thinking out loud here.

Yeah, scratch that (I think)

@willnorris

I'm actually trying to do the opposite to the examples you added into the tests. I'm trying to convert a bool false to 0 and true to 1.

I'm having an issue with a non-pointer (e.g. T(false)) not triggering EncodeValues() to be called. Which means the false is omitted from the encoded output as if it was never set, when it was.

Here's an example of what I'm trying:
https://goplay.tools/snippet/DMgWjMLCtu1

NOTE: In my example I could remove the omitempty from the Tee field's struct tag but then the first example where I don't set the Tee field would then be set to its zero value and I have no way to know if it was set to false or just omitted.

Not sure if you're able to offer any guidance on how best to achieve this. Thanks.