Reputation: 645
A common http response model using generics:
type HttpResp[T any] struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data T `json:"data"`
}
Sometimes the data does not return, it is null or string or number; in any case, I am not too concerned about its value.
If i use struct{}
will get error: json: cannot unmarshal string into Go struct field HttpResp[struct {}].data of type struct {}
resp := HttpResp[struct{}]{}
err := json.Unmarshal([]byte(`{"code":200,"msg":"ok","data": "ok"}`), &resp)
I currently found a clumsy way to solve it.
resp := HttpResp[json.RawMessage]{}
Is there a more elegant way?
Upvotes: 2
Views: 106
Reputation: 4490
if you doesn't know what type of is declared in json
(null or string or number), you can use *any
to mark Data
field as optional and then check resp.Data != nil
and field's type to cast:
func TestName(t *testing.T) {
resp := HttpResp[*any]{}
err := json.Unmarshal([]byte(`{"code":200,"msg":"ok","data": "ok"}`), &resp)
if err != nil {
t.Fatal(err)
}
fmt.Println(resp)
fmt.Println(resp.Data != nil)
fmt.Println(*resp.Data)
fmt.Println((*resp.Data).(string))
fmt.Println(`----`)
resp = HttpResp[*any]{}
err = json.Unmarshal([]byte(`{"code":200,"msg":"ok","data": 1}`), &resp)
if err != nil {
t.Fatal(err)
}
fmt.Println(resp)
fmt.Println(resp.Data != nil)
fmt.Println(*resp.Data)
fmt.Println((*resp.Data).(float64))
fmt.Println(`----`)
resp = HttpResp[*any]{}
err = json.Unmarshal([]byte(`{"code":200,"msg":"ok"}`), &resp)
if err != nil {
t.Fatal(err)
}
fmt.Println(resp)
fmt.Println(resp.Data == nil)
}
{200 ok 0xc000112450}
true
ok
ok
----
{200 ok 0xc0001124a0}
true
1
1
----
{200 ok <nil>}
true
ā ļø But, I think , you does not need to use generic in this case.
Or try to use more "elegant" way to unmarshal struct
šš» create custom implementation of the Unmarshaller with types check after unmarshalling:
type StringFloatStruct struct {
Val any
}
func (b *StringFloatStruct) UnmarshalJSON(data []byte) error {
switch sdata := strings.TrimSpace(string(data)); {
case sdata == "null":
return nil
default: // for example
fdata, err := strconv.ParseFloat(sdata, 64)
if err == nil {
b.Val = fdata
return nil
}
b.Val = strings.Trim(sdata, "\"")
return nil
}
}
func (b *StringFloatStruct) IsNil() bool {
return b.Val == nil
}
func (b *StringFloatStruct) ValString() (string, bool) {
v, ok := b.Val.(string)
return v, ok
}
func (b *StringFloatStruct) ValFloat64() (float64, bool) {
v, ok := b.Val.(float64)
return v, ok
}
func TestV2(t *testing.T) {
resp := HttpResp[StringFloatStruct]{}
err := json.Unmarshal([]byte(`{"code":200,"msg":"ok","data": "ok"}`), &resp)
if err != nil {
t.Fatal(err)
}
fmt.Println(resp)
fmt.Println(resp.Data.IsNil())
fmt.Println(resp.Data.ValString())
fmt.Println(resp.Data.ValFloat64())
fmt.Println(`----`)
resp = HttpResp[StringFloatStruct]{}
err = json.Unmarshal([]byte(`{"code":200,"msg":"ok","data": 1}`), &resp)
if err != nil {
t.Fatal(err)
}
fmt.Println(resp.Data.IsNil())
fmt.Println(resp.Data.ValString())
fmt.Println(resp.Data.ValFloat64())
fmt.Println(`----`)
resp = HttpResp[StringFloatStruct]{}
err = json.Unmarshal([]byte(`{"code":200,"msg":"ok"}`), &resp)
if err != nil {
t.Fatal(err)
}
fmt.Println(resp.Data.IsNil())
fmt.Println(resp.Data.ValString())
fmt.Println(resp.Data.ValFloat64())
}
false
ok true
0 false
----
false
false
1 true
----
true
false
0 false
Upvotes: 0
Reputation: 52081
Your HttpResp[T]
type implies that you know in advance the correct type T
for the "data"
part of your response.
In the example of your question, this means that, before looking at any content for the http response at this location in your code, you somehow know that the response will always contain a string
: HttpResp[string]
.
If your intention is to have code that accepts whatever comes at it when executing that specific json.Unmarshal(...)
, and figure out how to turn that into a go struct later, then json.RawMessage
is one of the standard ways to go -- and perhaps you are not looking to use generics at that specific location yet.
Upvotes: -2