TGViewer
Библиотека Go-разработчика | Golang Библиотека Go-разработчика | Golang @goproglib · 24.1K subscribers
Post #7343 2.76K
🧑‍💻 Сравнение двух Go-структур без хардкода полей. Часть 3, как это устроено внутри

В прошлой части мы договорились про тег 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
  • 👍 3
  • ❤ 1
More from @goproglib
  1. Sep 25, 2026🤡🤡 📍 Навигация: Вакансии • Задачи • Собесы 🐸 Библиотека Go-разработчика #GoGiggle
  2. Sep 25, 2026💡 Код работает. А data race уже есть В Go можно записать значение в одной горутине, прочи…
  3. Sep 23, 2026💥 TCP/IP: что происходит с данными в сети Когда Go-приложение отправляет данные по сети,…
  4. Sep 22, 2026🔴 Встроенные функции len, make, panic — встроенные функции Go, которые можно использовать…
  5. Sep 21, 2026🤩 Go с нуля: серия базовых шпаргалок Собрали путь от Hello, World! до goroutines, channel…
  6. Sep 20, 2026🤔 Вопрос с собеседования по Go Что выведет программа? ❤️ — [1 2 3 4 5] / [1 2 10] 🔥 — [1…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →