Go方法中初始化之后的map返回竟然为nil

Posted by 大攀 on Tuesday, September 7, 2021

TOC

问题描述

  当外部方法调用以下方法时,返回的结果map[int]int, error竟然同时为nil。

// GetEvent 查询事件记录
func GetEvent(ctx context.Context, flag string) (map[int]int, error) {
	var event = make(map[int]int)
	reply, err := redisClient.HGet(ctx, flag, "event")
	if err != nil {
		return nil, fmt.Errorf("redisClient.HGet flag: %s, err: %w", flag, err)
	}
	if len(reply) > 0 {
		if err = json.Unmarshal([]byte(reply), &event); err != nil {
			return nil, fmt.Errorf("json.Unmarshal reply: %s, err: %w", reply, err)
		}
	}
	return event, nil
}

// EventControl 事件控制
func EventControl(ctx context.Context) error {
	var flag = "test"
	event, err := GetEvent(ctx, flag)
	if err != nil {
		return fmt.Errorf("getEvent flag: %s, err: %w", flag, err)
	}
	if event == nil {
		// 程序会走到这里吗?
	}
	return nil
}

问题定位

  在 GetEvent 中,return 之前判断了一下event是否为nil,如果在内层就已经变成nil的话,就把内层的所有数据机器人记录了一下,大致如下:

// GetEvent 查询事件记录
func GetEvent(ctx context.Context, flag string) (map[int]int, error) {
	var event = make(map[int]int)
	reply, err := redisClient.HGet(ctx, flag, "event")
	if err != nil {
		return nil, fmt.Errorf("redisClient.HGet flag: %s, err: %w", flag, err)
	}
	if len(reply) > 0 {
		if err = json.Unmarshal([]byte(reply), &event); err != nil {
			return nil, fmt.Errorf("json.Unmarshal reply: %s, err: %w", reply, err)
		}
	}
    // todo 临时记录
	if event == nil {
		fmt.Printf("[GetEvent] event is nil, reply: %s, event: %T", reply, event)
	}
	return event, nil
}

问题解决

  通过打印记录发现,的确在GetEvent方法内部event就变成了nil,此时的reply为:”null”。

看下json反序列化的过程发现,”null”反序列化之后会变成对应类型的nil值。

// literalStore decodes a literal stored in item into v.
//
// fromQuoted indicates whether this literal came from unwrapping a
// string from the ",string" struct tag option. this is used only to
// produce more helpful error messages.
func (d *decodeState) literalStore(item []byte, v reflect.Value, fromQuoted bool) error {
	......
    switch c := item[0]; c {
	case 'n': // null
		// The main parser checks that only true and false can reach here,
		// but if this was a quoted string input, it could be anything.
		if fromQuoted && string(item) != "null" {
			d.saveError(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type()))
			break
		}
		switch v.Kind() {
		case reflect.Interface, reflect.Ptr, reflect.Map, reflect.Slice:
			v.Set(reflect.Zero(v.Type()))
			// otherwise, ignore null for primitives/string
		}
	case 't', 'f': // true, false
		value := item[0] == 't'
		// The main parser checks that only true and false can reach here,
		// but if this was a quoted string input, it could be anything.
		if fromQuoted && string(item) != "true" && string(item) != "false" {
			d.saveError(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type()))
			break
		}
		switch v.Kind() {
		default:
			if fromQuoted {
				d.saveError(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type()))
			} else {
				d.saveError(&UnmarshalTypeError{Value: "bool", Type: v.Type(), Offset: int64(d.readIndex())})
			}
		case reflect.Bool:
			v.SetBool(value)
		case reflect.Interface:
			if v.NumMethod() == 0 {
				v.Set(reflect.ValueOf(value))
			} else {
				d.saveError(&UnmarshalTypeError{Value: "bool", Type: v.Type(), Offset: int64(d.readIndex())})
			}
		}
    ......
}

问题总结

  1. redis等第三方库,当获取不存在的记录时,可能不会报错并且返回的结果也不是空字符串”“,而是”null”。

  2. json反序列化”null”,结果将是对应类型的nil值,并且不报错。

    To unmarshal JSON into an interface value,

    Unmarshal stores one of these in the interface value:

    • bool, for JSON booleans
    • float64, for JSON numbers
    • string, for JSON strings
    • []interface{}, for JSON arrays
    • map[string]interface{}, for JSON objects
    • nil for JSON null

comments powered by Disqus