Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Unable to handle nil pointers when implementing Encoder #46

Closed
meblum opened this issue Feb 7, 2021 · 6 comments
Closed

Unable to handle nil pointers when implementing Encoder #46

meblum opened this issue Feb 7, 2021 · 6 comments

Comments

@meblum
Copy link

meblum commented Feb 7, 2021

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.

@willnorris
Copy link
Collaborator

willnorris commented Feb 23, 2021

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?

@willnorris
Copy link
Collaborator

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.

@meblum
Copy link
Author

meblum commented Feb 24, 2021

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
}

@willnorris
Copy link
Collaborator

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 :)

@willnorris
Copy link
Collaborator

willnorris commented Feb 24, 2021

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 added a commit that referenced this issue Feb 24, 2021
many of these values are not actually what we want, but allows us to see
the change in behavior in a future change.

Related to #46
@Integralist
Copy link

Integralist commented Nov 3, 2021

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

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants