2015-09-06 1 views
0

J'ai du code avec lequel j'ai été exporté et je suis actuellement déconcentré - j'ai déjà travaillé avec RPC et le côté JSON, mais je n'arrive pas à le faire fonctionner sur RPC quand ça fonctionne bien localement.Unmarshal à un type d'interface

package main 

import (
    "log" 
    "net" 
    "net/rpc" 
    "net/rpc/jsonrpc" 
    "reflect" 
) 

type Foo interface { 
    SayHello() error 
} 

type fakeFoo struct { 
    internalValue string 
} 

func NewFakeFoo() *fakeFoo { 
    f := &fakeFoo{} 
    f.internalValue = "123456789" 
    return f 
} 

func (m *fakeFoo) SayHello() error { 
    return nil 
} 

type FooManager struct { 
    availableFoos []Foo 
} 

func NewFooManager() *FooManager { 
    p := new(FooManager) 
    p.availableFoos = make([]Foo, 0) 
    return p 
} 

func AddFoo(mm *FooManager, m Foo) { 
    mm.availableFoos = append(mm.availableFoos, m) 
    log.Println("Added type ", reflect.TypeOf(m)) 
} 

func (mm *FooManager) GetAvailableFoos(in []Foo, out *[]Foo) error { 

    log.Println("availableFoos:", reflect.TypeOf(mm.availableFoos)) 
    log.Println("*out is", reflect.TypeOf(*out)) 

    *out = append(in, mm.availableFoos...) 

    log.Println("Out is:", reflect.TypeOf(*out)) 

    return nil 
} 

func startServer(mm *FooManager) { 
    server := rpc.NewServer() 
    server.Register(mm) 

    l, e := net.Listen("tcp", ":8222") 
    if e != nil { 
     log.Fatal("listen error:", e) 
    } 

    for { 
     conn, err := l.Accept() 
     log.Println("Incoming!") 
     if err != nil { 
      log.Fatal(err) 
     } 

     go server.ServeCodec(jsonrpc.NewServerCodec(conn)) 
    } 
} 

func main() { 
    fake1 := NewFakeFoo() 

    fooHolder := NewFooManager() 
    AddFoo(fooHolder, fake1) 
    go startServer(fooHolder) 

    log.Println("Using standard function call") 
    var foos []Foo 
    fooHolder.GetAvailableFoos(foos, &foos) 
    log.Println(foos) 

    log.Println("Using RPC call") 
    conn, err := net.Dial("tcp", "localhost:8222") 
    if err != nil { 
     log.Fatalln(err) 
    } 
    defer conn.Close() 
    c := jsonrpc.NewClient(conn) 

    err = c.Call("FooManager.GetAvailableFoos", foos, &foos) 
    if err != nil { 
     log.Println(foos) 
     log.Fatal("GetAvailableFoos error:", err) 
    } 
    log.Println("Success: ", foos) 
} 

(! Aussi ici, mais pas tcp disponible urgh http://play.golang.org/p/HmK-K09D2J)

La sortie est assez surprenant que cela indique quelque chose de mal avec le rassemblement plutôt que les données réelles - Exécution en Wireshark je peux voir les données étant envoyées sous la forme correcte (j'ai eu du succès en utilisant une technique similaire dans une autre question) mais je ne peux pas, pour la vie de moi, arrêter de lancer des bogues de triage.

La sortie de l'exécution est la suivante:

2015/09/07 10:04:35 Added type *main.fakeFoo 
2015/09/07 10:04:35 Using standard function call 
2015/09/07 10:04:35 availableFoos: []main.Foo 
2015/09/07 10:04:35 *out is []main.Foo 
2015/09/07 10:04:35 Out is: []main.Foo 
2015/09/07 10:04:35 [0x1870a540] 
2015/09/07 10:04:35 Using RPC call 
2015/09/07 10:04:35 Incoming! 
2015/09/07 10:04:35 [0x1870a540] 
2015/09/07 10:04:35 GetAvailableFoos error:json: cannot unmarshal object into Go value of type main.Foo 
exit status 1 

Est-ce que je manque un truc d'interface/type ou est-ce un bug dans le rassemblement de Go?

+0

Je ne sais pas pourquoi cela fonctionnerait différemment à appeler mais se demander si elle est due au fait que fakeFoo a une internalValue qui arrête le travail de triage? – 17Twenty

Répondre

4

Tous les marshaling/unmarshaling ont ce problème.

Vous pouvez marshaler à partir d'une variable de type interface, car l'objet existe localement, donc le réflecteur connaît le type sous-jacent.

Vous ne pouvez pas démaraître un type d'interface, car le réflecteur ne sait pas quel type de béton donner à une nouvelle instance pour recevoir les données marshalées.

Dans certains cadres marshal/unmarshal, nous avons besoin d'informations supplémentaires pour aider le réflecteur. Par exemple, dans Java Json (jackson), nous utilisons l'annotation JsonTypeInfo pour spécifier le type de classe, voir this.

Pour golang, vous pouvez implémenter vous-même l'interface Unmarshaler pour votre propre type. Reportez-vous à How do I Unmarshal JSON?

// RawString is a raw encoded JSON object. 
// It implements Marshaler and Unmarshaler and can 
// be used to delay JSON decoding or precompute a JSON encoding. 
type RawString string 

// MarshalJSON returns *m as the JSON encoding of m. 
func (m *RawString) MarshalJSON() ([]byte, error) { 
    return []byte(*m), nil 
} 

// UnmarshalJSON sets *m to a copy of data. 
func (m *RawString) UnmarshalJSON(data []byte) error { 
    if m == nil { 
     return errors.New("RawString: UnmarshalJSON on nil pointer") 
    } 
    *m += RawString(data) 
    return nil 
} 

const data = `{"i":3, "S":{"phone": {"sales": "2223334444"}}}` 

type A struct { 
    I int64 
    S RawString `sql:"type:json"` 
} 

func main() { 
    a := A{} 
    err := json.Unmarshal([]byte(data), &a) 
    if err != nil { 
     log.Fatal("Unmarshal failed", err) 
    } 
    fmt.Println("Done", a) 
}