-2

Because I often unmarshal http.Response.Body, I thought I could write a function which handles all the hassle of reading, closing and unmarshaling into various different structs. That's why I introduced a function func unmarhalInterface(closer *io.ReadCloser, v *interface{}) error and can then assert the return value with t:=i.(T).

According to this answer I already wrapped it into a value of type *interface{}, but because the overlying type is interface{}and not myStruct, the json package implementation chooses map[string]interface{}. After that a type assertion fails (of course). Is there anything i am missing or requires this implementation a type assertion "by hand", that means look for all fields in the map and assign those that I want into my struct.

Code below has minimal example with notation in comments. If my explanation is not sufficient, please ask away.

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io"
    "io/ioutil"
    "log"
)

type myStruct struct {
    A string `json:"a"`
    B string `json:"b"`
}

func main() {
    jsonBlob := []byte(`{"a":"test","b":"test2"}`)

    var foo = interface{}(myStruct{})
    closer := ioutil.NopCloser(bytes.NewReader(jsonBlob))

    err := unmarshalCloser(&closer, &foo)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(fmt.Sprintf("%v", foo))

    // That´s what i want:
    foo2 := foo.(myStruct)
    fmt.Println(foo2.A)
}

func unmarshalCloser(closer *io.ReadCloser, v *interface{}) error {
    defer func() { _ = (*closer).Close() }()

    data, err := ioutil.ReadAll(*closer)
    if err != nil {
        return err
    }

    err = json.Unmarshal(data, v)
    if err != nil {
        return err
    }
    return nil
}

Golang Playground

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
sHartmann
  • 541
  • 4
  • 12
  • 4
    The linked answer says to _not_ use a `*interface{}`. The only time a pointer to an interface is required is if you want to pass in an empty interface to `Unmarshal` itself. also, why do you also have `*io.ReadCloser` as an argument? There is almost never a reason to pass around pointers to interfaces (the above exception is one of the few if you want to assign to an interface via reflection) – JimB Jun 19 '19 at 17:42
  • 1
    @j I also tried it without pointers, but the same problem persisted. The same interface conversion problem, even though json.unmarshal now gets the correct type as input. NotPointerCode: https://play.golang.org/p/edn5opF50SQ – sHartmann Jun 19 '19 at 17:46
  • 2
    Tried what? if you remove all the interface pointer nonsense, it works as expected: https://play.golang.org/p/GfJFvhVad3w. How does that differ from what you're trying to achieve? – JimB Jun 19 '19 at 17:48

1 Answers1

1

An empty interface isn't an actual type, it's basically something that matches anything. As stated in the comments, a pointer to an empty interface doesn't really make sense as a pointer already matches an empty interface since everything matches an empty interface. To make your code work, you should remove the interface wrapper around your struct, since that's just messing up the json type checking, and the whole point of an empty interface is that you can pass anything to it.

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io"
    "io/ioutil"
    "log"
)

type myStruct struct {
    A string `json:"a"`
    B string `json:"b"`
}

func main() {
    jsonBlob := []byte(`{"a":"test","b":"test2"}`)

    var foo = &myStruct{} // This need to be a pointer so its attributes can be assigned
    closer := ioutil.NopCloser(bytes.NewReader(jsonBlob))

    err := unmarshalCloser(closer, foo)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(fmt.Sprintf("%v", foo))

    // That´s what i want:
    fmt.Println(foo.A)
}

// You don't need to declare either of these arguments as pointers since they're both interfaces
func unmarshalCloser(closer io.ReadCloser, v interface{}) error {
    defer closer.Close()
    // v NEEDS to be a pointer or the json stuff will barf

    // Simplified with the decoder
    return json.NewDecoder(closer).Decode(v)
}
Tim Brown
  • 3,173
  • 1
  • 18
  • 15
  • The empty interface most certainly is a type: https://golang.org/ref/spec#Interface_types. – Peter Jun 20 '19 at 05:21