Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

SetResult replaces nested struct with a map inside a map #733

Open
maticmeznar opened this issue Oct 16, 2023 · 2 comments
Open

SetResult replaces nested struct with a map inside a map #733

maticmeznar opened this issue Oct 16, 2023 · 2 comments

Comments

@maticmeznar
Copy link

In the provided sample code, after a successful HTTP request, result["data"] changes type from getIP to map[string]interface{}. I don't know if this is feature or a bug, but it is unexpected and annoying to use. This does not happen if the outer map is replaced with a struct.

package main

import (
	"encoding/json"
	"log"
	"net/http"
	"net/netip"

	"github.com/davecgh/go-spew/spew"
	"github.com/go-resty/resty/v2"
)

type getIP struct {
	Address string `json:"address"`
	Version int    `json:"version"`
}

const serveAddr = ":48916"

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Add("Content-Type", "application/json")
		addr := netip.MustParseAddrPort(r.RemoteAddr).Addr().String()
		result := map[string]any{
			"data": getIP{
				Address: addr,
				Version: 4,
			},
		}

		enc := json.NewEncoder(w)
		if err := enc.Encode(result); err != nil {
			log.Fatalln(err)
		}
	})

	go func() {
		if err := http.ListenAndServe(serveAddr, nil); err != http.ErrServerClosed {
			log.Fatalf("Error starting server: %v\n", err)
		}
	}()

	result := map[string]any{
		"data": getIP{},
	}

	spew.Dump(result)

	r := resty.New().R().SetResult(&result)

	httpResp, err := r.Get("http://localhost" + serveAddr)
	if err != nil {
		log.Fatalf("unable to contact service: %v\n", err)
	}

	if httpResp.IsError() {
		log.Fatalf("error contacting service: %s\n", httpResp.Status())
	}

	spew.Dump(result)
}
@kecci
Copy link

kecci commented Oct 16, 2023

Hi @maticmeznar

I trace the result is depends on the JSONUnmarshaler.

The resty client are set the default of JSONUnmarshaler using "encoding/json".

To unmarshal JSON into an interface value, Unmarshal stores one of these in the interface value:

...
map[string]interface{}, for JSON objects
...

Refer to the official documentation on Unmarshal() for more information.

Reproduce

To make this clarify we tried to reproduce.

  1. Reproduce without the json unmarshal.
...
r := resty.New().SetJSONUnmarshaler(func(data []byte, v interface{}) error {
		return nil
	}).R().SetResult(&result)
...

Dump result without json unmarshal:

(map[string]interface {}) (len=1) {
 (string) (len=4) "data": (main.getIP) {
  Address: (string) "",
  Version: (int) 0
 }
}
(map[string]interface {}) (len=1) {
 (string) (len=4) "data": (main.getIP) {
  Address: (string) "",
  Version: (int) 0
 }
}
  1. Reproduce with the json unmarshal.
...
r := resty.New().SetJSONUnmarshaler(func(data []byte, v interface{}) error {
		return json.Unmarshal(data, &v)
	}).R().SetResult(&result)
...

Dump result with json unmarshal:

(map[string]interface {}) (len=1) {
 (string) (len=4) "data": (main.getIP) {
  Address: (string) "",
  Version: (int) 0
 }
}
(map[string]interface {}) (len=1) {
 (string) (len=4) "data": (map[string]interface {}) (len=2) {
  (string) (len=7) "address": (string) (len=3) "::1",
  (string) (len=7) "version": (float64) 4
 }

Conclusion

The result is depends on JSONUnmarshaler.
For now, you could set to more suitable unmarshaler (if you have).

However, I think your concerns could be input for other unmarshaler options. Both from internal and external packages.

Could we consider this ? @jeevatkm

@jeevatkm
Copy link
Member

jeevatkm commented Mar 2, 2024

@maticmeznar Thanks for reaching out.
I don't know if this expected result could be achieved the way it is. You could try to define a custom type for the outer map.

@kecci Thanks for responding to the comment. Could you explain what to consider? I'm unable to understand.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

No branches or pull requests

3 participants