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

Structure fields original order messed up

achesco opened this issue · comments

Hi
Thanks for your work!

Code sample from README states:

type Options struct {
  Query   string `url:"q"`
  ShowAll bool   `url:"all"`
  Page    int    `url:"page"`
}
opt := Options{ "foo", true, 2 }
v, _ := query.Values(opt)
fmt.Print(v.Encode()) // will output: "q=foo&all=true&page=2"

Question is about this: fmt.Print(v.Encode()) // will output: "q=foo&all=true&page=2"

But the actual output is all=true&page=2&q=foo for me. It could be critical, for example, if you need to generate payload based signature, since json.Marshal(opt) keeps original keys order:

type Options struct {
	Query   string `url:"q" json:"q"`
	ShowAll bool   `url:"all" json:"all"`
	Page    int    `url:"page" json:"page"`
}
opt := Options{ "foo", true, 2 }
v, _ := json.Marshal(opt)
fmt.Print(string(js)) // outputs {"q":"foo","all":true,"page":2}

Thanks for the report. I suspect that this is because url.Values is a map[string][]string, whose key access is by definition unordered. There's not really much go-querystring can do about that. That's a good callout though, and we could certainly add a reminder in the docs somewhere that if stable ordering is needed, then the caller will need to handle that. I haven't looked, but I would imagine encoding/json doesn't have this problem because it's using its own internal data structures rather than maps.

I'm curious though, is this causing problems in practice? You mention signatures, but I would assume that signatures would be generated on the output of encoding the url.Values, so as long as you're generating the signature on the same encoding that is passed in a URL, then the ordering shouldn't matter. And I don't say that to dismiss the concern... only to better understand the severity.

I wouldn't insist on "severity" actually :) My practical case was about the API requires request body payload to be accompanied by a signature header calculated on the string represented by param1=value1&param2=value2 pairs of actual JSON payload. Possible workaround in my case is to use a query string to pass all the params I need. Or to relay on map for both payload forming and signature generation.

Another type of problem I can think about is when values of the params with the same names are overridden by the latter ones under some circumstances... not critical either.

Suppose we can close the issue then