golang json反序列化科学计数法的坑

问题背景

func CheckSign(c *gin.Context, signKey string, singExpire int) (string, error) {
	r := c.Request
	var formParams map[string]interface{}
	if c.Request.Body != nil {
		bodyBytes, _ := io.ReadAll(c.Request.Body)
		defer c.Request.Body.Close()
		if len(bodyBytes) > 0 {
            //原始有问题的写法
            //err := json.Unmarshal(bodyBytes, &formParams)
			// 直接解析,会导致数字类型解析出来的是科学计数法
			d := json.NewDecoder(bytes.NewReader([]byte(bodyBytes)))
			d.UseNumber()
			err := d.Decode(&formParams)
			if err != nil {
				return "", err
			}
		}
		// 创建新的reader,使用bytes.NewReader
		// 恢复r.Body,以便可以多次读取
		r.Body = io.NopCloser(bytes.NewReader(bodyBytes))
	}

	sign := c.GetHeader("api-sign")
	timestamp := c.GetHeader("api-timestamp")

	if sign == "" || timestamp == "" {
		return "", errors.New("api-sign 或 api-timestamp为空")
	}

	//验证时间戳格式
	timestampValue, err := strconv.ParseInt(timestamp, 10, 64)
	if err != nil {
		return "", errors.New("timetamp 格式错误")
	}

	//验证时间戳
	nowstamp := time.Now().UnixNano() / int64(time.Millisecond)
	ago := timestampValue + int64(singExpire)
	if nowstamp > ago {
		return "", errors.New("签名时间已过期")
	}

	//验证签名

	// 按照字段名正序排序
	keys := make([]string, 0, len(formParams))
	for k := range formParams {
		keys = append(keys, k)
	}
	sort.Strings(keys)

	targetArr := make([]string, 0, len(formParams))
	// TODO 暂不支持嵌套json格式
	for _, k := range keys {
		val := reflect.ValueOf(formParams[k])
		switch val.Kind() {
		case reflect.Slice:
			strSlice := make([]string, 0, val.Len())
			for i := 0; i < val.Len(); i++ {
				v := val.Index(i)
				strSlice = append(strSlice, fmt.Sprintf("%v", v))
			}
			targetArr = append(targetArr, fmt.Sprintf("%s=%v", k, strings.Join(strSlice, ",")))
		default:
			targetArr = append(targetArr, fmt.Sprintf("%s=%v", k, formParams[k]))
		}
	}

	str := strings.Join(targetArr, "&") + timestamp + signKey
	hash := md5.Sum([]byte(str))
	md5Str := hex.EncodeToString(hash[:])

	if sign != md5Str {
		return md5Str, errors.New("签名错误~")
	}
	if gin.Mode() == "prod" {
		md5Str = ""
	}
	return md5Str, nil
}

前端传参:

{"id":33,"old_warranty_end_time":1720713600,"new_warranty_end_time":1720800000}

前端生成验签加密之前的字符串如下:

33&new_warranty_end_time=1720800000&old_warranty_end_time=17207136001720755503589{{加密盐值}}

服务端验签加密之前的字符串如下:

id=33&new_warranty_end_time=1.7208e+09&old_warranty_end_time=1.7207136e+091720755503589{{加密盐值}}

显而易见加密之前拼接的字符串不一样。服务端拼接的字符串变成了科学计数法的格式。

问题原因定位

经过查询发现,问题出现在json.Unmarshal(bodyBytes, &jsonBody)这个地方,反序列化之后,出来的就是科学计数法的类型。

我们可以看一下反序列化之后的数据类型和值,解析为了float类型。

为什么float类型出来的是科学计数法的表示样式呢?这个问题我们到源码中寻找答案,我们先看这个问题的解决方案。

解决方案

d := json.NewDecoder(bytes.NewReader([]byte(bodyBytes)))
d.UseNumber()
d.Decode(&jsonBody)

可以通过这种方式解决这个问题,这个问题很容易解决。但是有一点值得注意,通过这种方式反序列化数字类型会被反射为Number类型。

而这个json.Number的类型本质是个string类型。

问题原因

我们通过json.Unmarshal这个方法进到源码去看一下其执行逻辑。


func Unmarshal(data []byte, v any) error {
	// Check for well-formedness.
	// Avoids filling out half a data structure
	// before discovering a JSON syntax error.
	var d decodeState
	err := checkValid(data, &d.scan)
	if err != nil {
		return err
	}

	d.init(data)
	return d.unmarshal(v)
}

 chekValid这个方法是校验json格式是否合法,貌似是通过逐字节进行处理的。这个部分跟我们的问题不太相关,我们暂且略过。

func (d *decodeState) unmarshal(v any) error {
	rv := reflect.ValueOf(v)
	if rv.Kind() != reflect.Pointer || rv.IsNil() {
		return &InvalidUnmarshalError{reflect.TypeOf(v)}
	}

	d.scan.reset()
	d.scanWhile(scanSkipSpace)
	// We decode rv not rv.Elem because the Unmarshaler interface
	// test must be applied at the top level of the value.
	err := d.value(rv)
	if err != nil {
		return d.addErrorContext(err)
	}
	return d.savedError
}

我们重点关注d.vale中的逻辑。

func (d *decodeState) value(v reflect.Value) error {
	switch d.opcode {
	default:
		panic(phasePanicMsg)

	case scanBeginArray:
		if v.IsValid() {
			if err := d.array(v); err != nil {
				return err
			}
		} else {
			d.skip()
		}
		d.scanNext()

	case scanBeginObject:
		if v.IsValid() {
			if err := d.object(v); err != nil {
				return err
			}
		} else {
			d.skip()
		}
		d.scanNext()

	case scanBeginLiteral:
		// All bytes inside literal return scanContinue op code.
		start := d.readIndex()
		d.rescanLiteral()

		if v.IsValid() {
			if err := d.literalStore(d.data[start:d.readIndex()], v, false); err != nil {
				return err
			}
		}
	}
	return nil
}
func stateBeginValue(s *scanner, c byte) int {
	if isSpace(c) {
		return scanSkipSpace
	}
	switch c {
	case '{':
		s.step = stateBeginStringOrEmpty
		return s.pushParseState(c, parseObjectKey, scanBeginObject)
	case '[':
		s.step = stateBeginValueOrEmpty
		return s.pushParseState(c, parseArrayValue, scanBeginArray)
	case '"':
		s.step = stateInString
		return scanBeginLiteral
	case '-':
		s.step = stateNeg
		return scanBeginLiteral
	case '0': // beginning of 0.123
		s.step = state0
		return scanBeginLiteral
	case 't': // beginning of true
		s.step = stateT
		return scanBeginLiteral
	case 'f': // beginning of false
		s.step = stateF
		return scanBeginLiteral
	case 'n': // beginning of null
		s.step = stateN
		return scanBeginLiteral
	}
    //以数字开头的都是字面量
	if '1' <= c && c <= '9' { // beginning of 1234.5
		s.step = state1
		return scanBeginLiteral
	}
	return s.error(c, "looking for beginning of value")
}

以数字开头的都归属于字面量类型。所以,我们看一下d.vale中的scanBeginLiteral这个分支。

相关推荐

  1. RabbitMQ传递序列/序列自定义对象时踩

    2024-07-13 09:56:06       33 阅读
  2. 在php中序列序列

    2024-07-13 09:56:06       32 阅读
  3. 【C语言中科学计数

    2024-07-13 09:56:06       25 阅读
  4. tomcat序列

    2024-07-13 09:56:06       51 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-13 09:56:06       70 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-13 09:56:06       74 阅读
  3. 在Django里面运行非项目文件

    2024-07-13 09:56:06       62 阅读
  4. Python语言-面向对象

    2024-07-13 09:56:06       72 阅读

热门阅读

  1. parquet-go的CSVWriter

    2024-07-13 09:56:06       30 阅读
  2. 玩转鸿蒙NXET之组件导航与路由跳转二

    2024-07-13 09:56:06       25 阅读
  3. Go语言入门之数组切片

    2024-07-13 09:56:06       31 阅读
  4. P6. 对局列表和排行榜功能

    2024-07-13 09:56:06       24 阅读
  5. 使用Nginx实现高效负载均衡

    2024-07-13 09:56:06       23 阅读
  6. CRC32简述

    2024-07-13 09:56:06       27 阅读
  7. 赛博灯泡3.0,未完善,无bug

    2024-07-13 09:56:06       23 阅读
  8. C#——二进制流序列化和反序列化

    2024-07-13 09:56:06       31 阅读
  9. Redis原子计数器incr,防止并发请求

    2024-07-13 09:56:06       27 阅读
  10. 求某个矩阵的鞍点的个数

    2024-07-13 09:56:06       23 阅读