В прошлой части мы договорились про тег
match. Теперь посмотрим на реализацию. Она занимает около 130 строк и состоит из обычной рефлексии.Сначала разворачиваем указатели и проверяем, что перед нами действительно структуры.
Функция принимает
any с обеих сторон, поэтому работает с любым типом:func (m *Mapper) CompareAndMap(dto any, db any) (map[string]any, error) {
dtoVal := reflect.ValueOf(dto)
dbVal := reflect.ValueOf(db)
if dtoVal.Kind() == reflect.Ptr {
dtoVal = dtoVal.Elem()
}
if dbVal.Kind() == reflect.Ptr {
dbVal = dbVal.Elem()
}
if dtoVal.Kind() != reflect.Struct || dbVal.Kind() != reflect.Struct {
return nil, fmt.Errorf("both dto and db must be structs or pointers to structs")
}
// ...
}Дальше один раз проходим по модели базы и строим карту вида имя колонки в значение поля. Так матчинг работает за O(1) на поле, а порядок полей в структурах может не совпадать:
func (m *Mapper) buildColumnMap(dbVal reflect.Value) map[string]reflect.Value {
columnMap := make(map[string]reflect.Value)
dbType := dbVal.Type()
for i := 0; i < dbType.NumField(); i++ {
if column := m.parseGormColumn(dbType.Field(i).Tag.Get(dbGormTag)); column != "" {
columnMap[column] = dbVal.Field(i)
}
}
return columnMap
}Имя колонки достаётся из тега GORM, который бывает грязным, например
type:smallint;column:is_suspended. Режем по точке с запятой и берём кусок после column:func (m *Mapper) parseGormColumn(gormTag string) string {
for _, part := range strings.Split(gormTag, ";") {
if col, ok := strings.CutPrefix(part, dbColumnKey+":"); ok {
return col
}
}
return ""
}Потом идём по полям DTO. Поле без тега
match пропускаем, так DTO может иметь поля, которые мы намеренно не синхронизируем.Поле, чей тег указывает на несуществующую колонку, тоже пропускаем без паники:
for i := 0; i < dtoVal.NumField(); i++ {
dtoField := dtoVal.Field(i)
dtoFieldType := dtoType.Field(i)
dbGormColumnName := dtoFieldType.Tag.Get(dtoMatchTag)
if dbGormColumnName == "" {
continue // нет тега match, это не наша забота
}
dbField, ok := columnMap[dbGormColumnName]
if !ok {
continue // DTO ссылается на колонку, которой нет в модели базы
}
// ... сравниваем dtoField и dbField
}Каркас готов. Осталось самое скользкое, сравнение значений с указателями и nil. Об этом в следующей части.
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека Go-разработчика
#GoDeep