Пользовательская структура Golang для отображения

Go

Как в Голанге преобразовать структуру в карту? В этой статье описаны два метода. Первый заключается в использованииjsonКодирование декодирования синтаксического анализа пакетов. Во-вторых, использовать отражение, эффективность использования отражения относительно высока,код здесь. Если вы найдете код полезным, вы можете дать мне репозиторий кода звезду.

Предположим, у вас есть следующая структура

func newUser() User {
	name := "user"
	MyGithub := GithubPage{
		URL:  "https://github.com/liangyaopei",
		Star: 1,
	}
	NoDive := StructNoDive{NoDive: 1}
	dateStr := "2020-07-21 12:00:00"
	date, _ := time.Parse(timeLayout, dateStr)
	profile := Profile{
		Experience: "my experience",
		Date:       date,
	}
	return User{
		Name:      name,
		Github:    MyGithub,
		NoDive:    NoDive,
		MyProfile: profile,
	}
}

type User struct {
	Name      string       `map:"name,omitempty"`        // string
	Github    GithubPage   `map:"github,dive,omitempty"` // struct dive
	NoDive    StructNoDive `map:"no_dive,omitempty"`     // no dive struct
	MyProfile Profile      `map:"my_profile,omitempty"`  // struct implements its own method
}

type GithubPage struct {
	URL  string `map:"url"`
	Star int    `map:"star"`
}

type StructNoDive struct {
	NoDive int
}

type Profile struct {
	Experience string    `map:"experience"`
	Date       time.Time `map:"time"`
}

// its own toMap method
func (p Profile) StructToMap() (key string, value interface{}) {
	return "time", p.Date.Format(timeLayout)
}

маршал, демаршал пакета json

Сначала сериализуйте структуру в[]byteмассив, затем из[]byteМассивы сериализуются в структуры.

data, _ := json.Marshal(&user)
m := make(map[string]interface{})
json.Unmarshal(data, &m)

Преимущество

  • Простой в использовании недостаток
  • Эффективность ниже
  • Он не может поддерживать некоторые пользовательские ключи, а также некоторые пользовательские методы, такие как расширение поля структуры и т. д.

использовать отражение

В этой статье реализовано использование отражения для преобразования структуры вmapМетоды. Используя теги и отражение, приведенный выше примерnewUser()Возвращенный результат преобразуется в один из следующихmap. Разверните домен, содержащий структуру, метод структуры настройки.

map[string]interface{}{
	"name":    "user",
	"no_dive": StructNoDive{NoDive: 1},
    // dive struct field
	"url":     "https://github.com/liangyaopei",
	"star":    1,
    // customized method
	"time":    "2020-07-21 12:00:00",
}

Исходная решимость реализации идей и

1. Идентификация этикетки.

использоватьreadTagМетод считывает метку поля и, если метки нет, использует имя поля. Затем прочитайте параметры в теге. На данный момент поддерживаются 3 варианта

  • '-': игнорировать текущее поле
  • omitempty: игнорировать это поле, если значение этого поля пусто.
  • «Погружение»: рекурсивно пройтись по его структуре, все поля в качестве ключевых

Если выбрана опция, бинарная позиция, соответствующая этому полю, называется 1..

const (
	OptIgnore    = "-"
	OptOmitempty = "omitempty"
	OptDive      = "dive"
)

const (
	flagIgnore = 1 << iota
	flagOmiEmpty
	flagDive
)

func readTag(f reflect.StructField, tag string) (string, int) {
	val, ok := f.Tag.Lookup(tag)
	fieldTag := ""
	flag := 0

	// no tag, use field name
	if !ok {
		return f.Name, flag
	}
	opts := strings.Split(val, ",")

	fieldTag = opts[0]
	for i := 1; i < len(opts); i++ {
		switch opts[i] {
		case OptIgnore:
			flag |= flagIgnore
		case OptOmitempty:
			flag |= flagOmiEmpty
		case OptDive:
			flag |= flagDive
		}
	}
	return fieldTag, flag
}

2. Обход поля структуры.

Пройдитесь по каждому полю (field) структуры и определите тип поля (kind). еслиstring,intБазовый тип и т. д. принимают значение напрямую и используют значение в теге в качестве ключа.

for i := 0; i < t.NumField(); i++ {
        ...
        switch fieldValue.Kind() {
		case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int, reflect.Int64:
			res[tagVal] = fieldValue.Int()
		case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint, reflect.Uint64:
			res[tagVal] = fieldValue.Uint()
		case reflect.Float32, reflect.Float64:
			res[tagVal] = fieldValue.Float()
		case reflect.String:
			res[tagVal] = fieldValue.String()
		case reflect.Bool:
			res[tagVal] = fieldValue.Bool()
		default:
		}
    }
}

3. Преобразование встроенных структур

Если это структура, сначала проверьте, есть ли метод, реализующий входящий параметр, и если он реализован, вызовите этот метод. Если не реализовано, вызывается рекурсивноStructToMapметод, а затем в зависимости от того, расширять или нет (dive), чтобы записать возвращенный результат в карту res.

for i := 0; i < t.NumField(); i++ {
		fieldType := t.Field(i)

		// ignore unexported field
		if fieldType.PkgPath != "" {
			continue
		}
		// read tag
		tagVal, flag := readTag(fieldType, tag)

		if flag&flagIgnore != 0 {
			continue
		}

		fieldValue := v.Field(i)
		if flag&flagOmiEmpty != 0 && fieldValue.IsZero() {
			continue
		}

		// ignore nil pointer in field
		if fieldValue.Kind() == reflect.Ptr && fieldValue.IsNil() {
			continue
		}
		if fieldValue.Kind() == reflect.Ptr {
			fieldValue = fieldValue.Elem()
		}

		// get kind
		switch fieldValue.Kind() {
		case reflect.Struct:
			_, ok := fieldValue.Type().MethodByName(methodName)
			if ok {
				key, value, err := callFunc(fieldValue, methodName)
				if err != nil {
					return nil, err
				}
				res[key] = value
				continue
			}
			// recursive
			deepRes, deepErr := StructToMap(fieldValue.Interface(), tag, methodName)
			if deepErr != nil {
				return nil, deepErr
			}
			if flag&flagDive != 0 {
				for k, v := range deepRes {
					res[k] = v
				}
			} else {
				res[tagVal] = deepRes
			}
		default:
		}
    }
    ...
}

// call function
func callFunc(fv reflect.Value, methodName string) (string, interface{}, error) {
	methodRes := fv.MethodByName(methodName).Call([]reflect.Value{})
	if len(methodRes) != methodResNum {
		return "", nil, fmt.Errorf("wrong method %s, should have 2 output: (string,interface{})", methodName)
	}
	if methodRes[0].Kind() != reflect.String {
		return "", nil, fmt.Errorf("wrong method %s, first output should be string", methodName)
	}
	key := methodRes[0].String()
	return key, methodRes[1], nil
}

4. Преобразование типа массива, среза

еслиarray,sliceType аналогично проверяет, есть ли метод, реализующий входящий параметр, и если есть, то вызывает этот метод. Если это не реализовано, используйте тег поля в качестве ключа и значение поля в качестве значения.

switch fieldValue.Kind() {
		case reflect.Slice, reflect.Array:
			_, ok := fieldValue.Type().MethodByName(methodName)
			if ok {
				key, value, err := callFunc(fieldValue, methodName)
				if err != nil {
					return nil, err
				}
				res[key] = value
				continue
			}
            res[tagVal] = fieldValue
            ....
}

5. Другие типы

Для других типов, таких как встроенныйmapНапрямую верните значение результата.

switch fieldValue.Kind() {
		...
		case reflect.Map:
			res[tagVal] = fieldValue
		case reflect.Chan:
			res[tagVal] = fieldValue
		case reflect.Interface:
			res[tagVal] = fieldValue.Interface()
		default:
		}