go自定义数据的序列化流程

        对于网络传输数据而言,在go中只要是[]byte的格式就能进行传输,对于最简单的client->server->mysql,然后数据再传回来的简单流程来分析(其实对于一个进程来说,没有client和server之分,一个进程即可能是客户端,也可能是服务端,mysql本身就是一个server,只是功能不同,笼统来说,没有本质上的区别),本篇以decimal的数据来分析,它的github地址 "github.com/shopspring/decimal"

1.简述decimal的存储格式

type Decimal struct {
	value *big.Int

	// NOTE(vadim): this must be an int32, because we cast it to float64 during
	// calculations. If exp is 64 bit, we might lose precision.
	// If we cared about being able to represent every possible decimal, we
	// could make exp a *big.Int but it would hurt performance and numbers
	// like that are unrealistic.
	exp int32
}

type Int struct {
	neg bool // sign
	abs nat  // absolute value of the integer
}

type nat []Word

        其中decimal的包括一个big.Int的结构体来存储数据,big.Int的结构体主要是来存储大数的,比如uint64,用nat来存储,就是[2]uint,长度为2的uint数组,一个存32位,两个存64,如果是uint32,那就只用1个uint,就能存储,依次类推,越大,数组越大,neg是符号位,表示正负的,那decimal是怎么表示的了

data, _ := decimal.NewFromString("123.456")
fmt.Println(data.String())

//打印 123.456

func NewFromString(value string) (Decimal, error) 
func (d Decimal) string(trimTrailingZeros bool) string 

        对于上面的两个源码函数,简要的分析了一下,就是找到其中符号.的索引,转成负数,然后将其中的123.456的字符串转成123456的数,并存入big.Int的nat的数组中,并返回exp=-3的索引,然后String()显示的时候,将big.Int也调用String(),其实就是将nat数组的数,又转成123456,然后根据下面的exp的索引得出整数部分和小数部分,然后+个.,拼接起来

intPart = str[:len(str)+dExpInt]
fractionalPart = str[len(str)+dExpInt:]

        上面就是decimal的简要的存储格式,很清晰,简单,其他的部分读者们可以自己去看看

2.decimal的client-server的传输

        对于client和server的data传输,一般是json,probuf,还有自定义的格式。这里我们以json格式为例。

        对于js客户端,我们对于精度要求特别高的数据,用string类型进行传输,然后server通过decimal.NewFromString 函数进行获取(当然json也能反序列化将string转成decimal类型,获取可能有其他格式的传参form类的),然后进行decimal的数据的运算,最后通过json的序列化传回给client

st := &struct {
		A int
		B decimal.Decimal
	}{
		1,
		data,
	}

	j, err := json.Marshal(st)
	if err != nil {
		t.Error(err)
	}
	fmt.Println(string(j))

//打印
{"A":1,"B":"123.456"}

        json进行序列化的时候,将decimal的格式转换成了string的格式,我们解释一下怎么分析

json的Marshal-》由于我们这里是struct,对于每个字段都会调用newTypeEncoder{
    if t.Kind() != reflect.Pointer && allowAddr && reflect.PointerTo(t).Implements(marshalerType) {
		return newCondAddrEncoder(addrMarshalerEncoder, newTypeEncoder(t, false))
	}
	if t.Implements(marshalerType) {
		return marshalerEncoder
	}
	if t.Kind() != reflect.Pointer && allowAddr && reflect.PointerTo(t).Implements(textMarshalerType) {
		return newCondAddrEncoder(addrTextMarshalerEncoder, newTypeEncoder(t, false))
	}
	if t.Implements(textMarshalerType) {
		return textMarshalerEncoder
	}
    .
    .
    .
   
}

其中marshalerType     = reflect.TypeOf((*Marshaler)(nil)).Elem(),是接口
type Marshaler interface {
    MarshalJSON() ([]byte, error)
}

在decimal中,我们能找到这个接口
func (d Decimal) MarshalJSON() ([]byte, error) {
	var str string
	if MarshalJSONWithoutQuotes {
		str = d.String()
	} else {
		str = "\"" + d.String() + "\""
	}
	return []byte(str), nil
}

        上面解析出了原因。

3.decimal的server-mysql的传输

        由于mysql有自己的数据传输格式和数据定义,所以先看获取mysql的数据


RealAmount decimal.Decimal
mydb.QueryRow("SELECT realmount FROM repli_table.new_table1 WHERE id=?", 6).Scan(&RealAmount)

        下面进行分析

mysql的Scan()-》调用func convertAssignRows(dest, src any, rows *Rows) error {
    .
    .
    .
    if scanner, ok := dest.(Scanner); ok {
		return scanner.Scan(src)
	}
    .
    .
    .

}

其中的Scanner是type Scanner interface {接口
    Scan(src any) error
}

,所以decimal能实现这个接口就可以了
func (d *Decimal) Scan(value interface{}) error {
	// first try to see if the data is stored in database as a Numeric datatype
	switch v := value.(type) {

	case float32:
		*d = NewFromFloat(float64(v))
		return nil

	case float64:
		// numeric in sqlite3 sends us float64
		*d = NewFromFloat(v)
		return nil

	case int64:
		// at least in sqlite3 when the value is 0 in db, the data is sent
		// to us as an int64 instead of a float64 ...
		*d = New(v, 0)
		return nil

	default:
		// default is trying to interpret value stored as string
		str, err := unquoteIfQuoted(v)
		if err != nil {
			return err
		}
		*d, err = NewFromString(str)
		return err
	}
}

        那是怎么传入数据的了,就是将数据拼接起来

RealAmount decimal.Decimal
mydb.QueryRow("SELECT *FROM repli_table.new_table1 WHERE realmount >=?", RealAmount)

 我们也进行下面的分析

        

QueryRow-》queryDC-》driverArgsConnLocked-》checker{nvc.CheckNamedValue}-》func (c converter) ConvertValue(v interface{}) (driver.Value, error) {
	if driver.IsValue(v) {
		return v, nil
	}

	if vr, ok := v.(driver.Valuer); ok {
		sv, err := callValuerValue(vr)
		if err != nil {
			return nil, err
		}
		if driver.IsValue(sv) {
			return sv, nil
		}
		// A value returend from the Valuer interface can be "a type handled by
		// a database driver's NamedValueChecker interface" so we should accept
		// uint64 here as well.
		if u, ok := sv.(uint64); ok {
			return u, nil
		}
		return nil, fmt.Errorf("non-Value type %T returned from Value", sv)
	}
	rv := reflect.ValueOf(v)
	switch rv.Kind() {
	case reflect.Ptr:
		// indirect pointers
		if rv.IsNil() {
			return nil, nil
		} else {
			return c.ConvertValue(rv.Elem().Interface())
		}
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		return rv.Int(), nil
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		return rv.Uint(), nil
	case reflect.Float32, reflect.Float64:
		return rv.Float(), nil
	case reflect.Bool:
		return rv.Bool(), nil
	case reflect.Slice:
		switch t := rv.Type(); {
		case t == jsonType:
			return v, nil
		case t.Elem().Kind() == reflect.Uint8:
			return rv.Bytes(), nil
		default:
			return nil, fmt.Errorf("unsupported type %T, a slice of %s", v, t.Elem().Kind())
		}
	case reflect.String:
		return rv.String(), nil
	}
	return nil, fmt.Errorf("unsupported type %T, a %s", v, rv.Kind())
}


上面的执行逻辑最后ConvertValue这个转换成driver.Value值,这个值只能有IsValue函数返回true的类型,才能最后拼接最后的sql,func IsValue(v any) bool {
	if v == nil {
		return true
	}
	switch v.(type) {
	case []byte, bool, float64, int64, string, time.Time:
		return true
	case decimalDecompose:
		return true
	}
	return false
}

因为在queryDC-》driverArgsConnLocked执行完后执行sql的conn的函数
queryquery(query string, args []driver.Value) (*textRows, error) ,然后再执行,
func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (string, error) 
最终将参数覆盖?号的,拼接成sql的string的是interpolateParams函数,这个函数能解析的类型才是最终
的类型,看到这里,是不是明白了所有。

也就是传入的参数如果是整数型ConvertValue会将他转成int64,浮点型转成float64就可以了,也就是转
成IsValue函数是true的类型


对于自定义的接口,我们也要将他转成driver.Value类型,所以在ConvertValue函数里,有这段函数
if vr, ok := v.(driver.Valuer); ok {
		sv, err := callValuerValue(vr)
		if err != nil {
			return nil, err
		}
		if driver.IsValue(sv) {
			return sv, nil
		}

 其中的  driver.Valuer是type Valuer interface {
    Value() (Value, error)
}接口

所以我们只要看decimal是否实现这个接口,然后转成 driver.Value就可以了
// Value implements the driver.Valuer interface for database serialization.
func (d Decimal) Value() (driver.Value, error) {
	return d.String(), nil
}

                

        对此自定义的mysql的获取和传入就解析清楚了,我们可以看到decimal还有很多其他的转换的接口函数,GobEncode的gob的系列化,MarshalBinary的probuf的接口,我们就不一一分析了。

        总结对于写好的序列化库,一般会留个接口给自定义的类型来实现自己的功能,比如上面分析的三个接口,由于上面的三个太重要了,我们就从源码分析并找到了它的接口,以后我们自己实现就轻车熟路,原理很简单,但一定要熟悉。

  • 16
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值