golang 使用接口和类型断言在Go中使用任意键名解析JSON对象
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了golang 使用接口和类型断言在Go中使用任意键名解析JSON对象相关的知识,希望对你有一定的参考价值。
golang类型断言的使用(Type Assertion)
第一部分
首先,转自https://studygolang.com/articles/3314对断言的基本介绍
golang的语言中提供了断言的功能。golang中的所有程序都实现了interface的接口,这意味着,所有的类型如string,int,int64甚至是自定义的struct类型都就此拥有了interface的接口,这种做法和java中的Object类型比较类似。那么在一个数据通过func funcName(interface)的方式传进来的时候,也就意味着这个参数被自动的转为interface的类型。
如以下的代码:
func funcName(a interface) string return string(a)
编译器将会返回:
cannot convert a (type interface) to type string: need type assertion
此时,意味着整个转化的过程需要类型断言。类型断言有以下几种形式:
1)直接断言使用
var a interface
fmt.Println("Where are you,Jonny?", a.(string))
但是如果断言失败一般会导致panic的发生。所以为了防止panic的发生,我们需要在断言前进行一定的判断
value, ok := a.(string)
如果断言失败,那么ok的值将会是false,但是如果断言成功ok的值将会是true,同时value将会得到所期待的正确的值。示例:
value, ok := a.(string) if !ok fmt.Println("It‘s not ok for type string") return fmt.Println("The value is ", value)
另外也可以配合switch语句进行判断:
var t interface t = functionOfSomeType() switch t := t.(type) default: fmt.Printf("unexpected type %T", t) // %T prints whatever type t has
break case bool: fmt.Printf("boolean %t\n", t) // t has type bool
break case int: fmt.Printf("integer %d\n", t) // t has type int
break case *bool: fmt.Printf("pointer to boolean %t\n", *t) // t has type *bool
break case *int: fmt.Printf("pointer to integer %d\n", *t) // t has type *int
break
第二部分
net/jsonrpc增加get_client_ip功能
问题描述:falcon-agent无法监测到宿主机ip的变动,导致初始化安装时写在配置里的IP地址与当前真实IP地址不符,产生错误的上报数据(将数据以旧IP上报,新IP查不到任何监控项)。
解决思路:由于agent已大规模部署在服务器上,更新比较麻烦,考虑修改transfer端,从rpc连接中直接获取到agent的IP。
来自open-falcon/falcon-plus/transfer组件中的rpc相关代码:
初始建立RPC server:open-falcon\falcon-plus\modules\transfer\receiver\rpc\rpc.go
package rpc import ( "github.com/open-falcon/falcon-plus/modules/transfer/g" "log" "net" "net/rpc" "net/rpc/jsonrpc" ) func StartRpc() if !g.Config().Rpc.Enabled return addr := g.Config().Rpc.Listen tcpAddr, err := net.ResolveTCPAddr("tcp", addr) if err != nil log.Fatalf("net.ResolveTCPAddr fail: %s", err) listener, err := net.ListenTCP("tcp", tcpAddr) if err != nil log.Fatalf("listen %s fail: %s", addr, err) else log.Println("rpc listening", addr) server := rpc.NewServer() server.Register(new(Transfer)) for conn, err := listener.Accept() if err != nil log.Println("listener.Accept occur error:", err) continue go server.ServeCodec(jsonrpc.NewServerCodec(conn))
这里使用的是jsonrpc的编码解码器,其中会对conn中的数据使用json.Unmarshal解码。
重要的步骤位于jsonrpc/server.go的下列函数中:
1 type ArgsContext interface 2 Value(key string) interface 3 SetValue(key string, value interface) 4 5 6 func (c *serverCodec) ReadRequestBody(x interface) error 7 if x == nil 8 return nil 9 10 if c.req.Params == nil 11 return errMissingParams 12 13 if args, ok := x.(ArgsContext); ok 14 args.SetValue("conn", c.c) 15 16 // JSON params is array value. 17 // RPC params is struct. 18 // Unmarshal into array containing struct for now. 19 // Should think about making RPC more general. 20 var params [1]interface 21 params[0] = x 22 return json.Unmarshal(*c.req.Params, ¶ms) 23
1-4行和13-15行是新增的一个断言判断,目的是为了给解析出来的args参数增加一些上下文信息,比如最重要的:将conn对象存入其中。
如此,便可以从rpc的callback函数中访问到conn对象,从而拿到client IP【要求args的类型在定义时实现ArgsContext的接口】。
该思路源自https://github.com/club-codoon/rpcx/blob/630e53bff09759ba2a21644f318907504cfdd98a/_examples/context/server.go,应用方式如下:
1 package main 2 3 import ( 4 "fmt" 5 "net" 6 7 "github.com/smallnest/rpcx" 8 ) 9 10 type Args struct 11 A int `msg:"a"` 12 B int `msg:"b"` 13 ctx map[string]interface 14 15 16 type Reply struct 17 C int `msg:"c"` 18 19 20 func (a *Args) Value(key string) interface 21 if a.ctx != nil 22 return a.ctx[key] 23 24 return nil 25 26 27 func (a *Args) SetValue(key string, value interface) 28 if a.ctx == nil 29 a.ctx = make(map[string]interface) 30 31 a.ctx[key] = value 32 33 34 type Arith int 35 36 func (t *Arith) Mul(args *Args, reply *Reply) error 37 reply.C = args.A * args.B 38 conn := args.Value("conn").(net.Conn) 39 fmt.Printf("Client IP: %s \n", conn.RemoteAddr().String()) 40 return nil 41 42 43 func main() 44 server := rpcx.NewServer() 45 server.RegisterName("Arith", new(Arith)) 46 server.Serve("tcp", "127.0.0.1:8972") 47
但是该方法有一个局限,如下的callback场景中,args参数为[]*sometype,是一个slice。
如果是一个struct,则可以方便的按照上述方法添加一个私有的ctx即可存放相关数据,但如果是一个slice,是没办法放一个ctx解决的,那样的话会把slice改为一个struct,从而在json.Unmarshal时失败。
RPC server的callback函数定义:open-falcon\falcon-plus\modules\transfer\receiver\rpc\rpc_transfer.go
import ( "fmt" cmodel "github.com/open-falcon/falcon-plus/common/model" cutils "github.com/open-falcon/falcon-plus/common/utils" "github.com/open-falcon/falcon-plus/modules/transfer/g" "github.com/open-falcon/falcon-plus/modules/transfer/proc" "github.com/open-falcon/falcon-plus/modules/transfer/sender" "strconv" "time" "path/filepath" "crypto/md5" "io" "encoding/hex" ) type Transfer int type TransferResp struct Msg string Total int ErrInvalid int Latency int64 func (t *TransferResp) String() string s := fmt.Sprintf("TransferResp total=%d, err_invalid=%d, latency=%dms", t.Total, t.ErrInvalid, t.Latency) if t.Msg != "" s = fmt.Sprintf("%s, msg=%s", s, t.Msg) return s func (t *Transfer) Update(args []*cmodel.MetricValue, reply *cmodel.TransferResponse) error return RecvMetricValues(args, reply, "rpc")
一个workaround思路是,将jsonrpc单拿出来作为一个私有依赖包,更改其中的逻辑,直接将args断言为slice指针类型,并遍历其数据,将client IP放入Endpoint字段中。
【由于transfer的rpc机制只有这里用到了jsonrpc包,所以该workaround可以不影响其他rpc逻辑】:
1 func (c *serverCodec) ReadRequestBody(x interface) error 2 if x == nil 3 return nil 4 5 if c.req.Params == nil 6 return errMissingParams 7 8 // JSON params is array value. 9 // RPC params is struct. 10 // Unmarshal into array containing struct for now. 11 // Should think about making RPC more general. 12 var params [1]interface 13 params[0] = x 14 if err := json.Unmarshal(*c.req.Params, ¶ms); err != nil 15 return err 16 17 // fmt.Printf("[jsonrpc]x type is %T \n", x) 18 if args, ok := x.(*[]*cmodel.MetricValue); ok 19 remote_addr := strings.Split(c.c.(net.Conn).RemoteAddr().String(), ":")[0] 20 if remote_addr != "" 21 for _, v := range *args 22 v.Endpoint = remote_addr 23 24 25 26 return nil 27
以上是关于golang 使用接口和类型断言在Go中使用任意键名解析JSON对象的主要内容,如果未能解决你的问题,请参考以下文章