2014-01-21 4 views
6

Я ищу итерацию по строковым полям структуры, поэтому я могу выполнить некоторую очистку/проверку (с помощью strings.TrimSpace, strings.Trim и т. Д.).Iterate Over String Fields in Struct

Прямо сейчас у меня есть беспорядочный коммутационный футляр, который на самом деле не масштабируемый, и поскольку это не в горячей точке моего приложения (веб-формы), кажется, что использование reflect - хороший выбор здесь.

Я немного разбираюсь в том, как это реализовать, но документы с отражением немного запутывают меня (я копал некоторые другие пакеты проверки, но они слишком тяжелые + Я использую горилла/схемы для немаршалинг части уже):

  • итерации по структурам
  • для каждого поля типа строки, применить все, что нужно от strings пакета т.е. field = strings.TrimSpace(field)
  • Если существует поле. Tag.Get («max»), мы будем использовать это значение (strconv.Atoi, затем unicode.RuneCoun tInString)
  • Обеспечить срез ошибки, также совместим с типом интерфейса ошибки

    type FormError []string   
    
    type Listing struct { 
         Title string `max:"50"` 
         Location string `max:"100"` 
         Description string `max:"10000"` 
         ExpiryDate time.Time 
         RenderedDesc template.HTML 
         Contact string `max:"255"` 
        } 
    
        // Iterate over our struct, fix whitespace/formatting where possible 
        // and return errors encountered 
        func (l *Listing) Validate() error { 
    
         typ := l.Elem().Type() 
    
         var invalid FormError 
         for i = 0; i < typ.NumField(); i++ { 
          // Iterate over fields 
          // For StructFields of type string, field = strings.TrimSpace(field) 
          // if field.Tag.Get("max") != "" { 
          //  check max length/convert to int/utf8.RuneCountInString 
            if max length exceeded, invalid = append(invalid, "errormsg") 
         } 
    
         if len(invalid) > 0 { 
          return invalid 
         } 
    
         return nil 
        } 
    
    
        func (f FormError) Error() string { 
         var fullError string 
         for _, v := range f { 
          fullError =+ v + "\n" 
         } 
         return "Errors were encountered during form processing: " + fullError 
        } 
    

Спасибо заранее.

ответ

9

Что вы хотите, это прежде всего методы отражения. Значения называются NumFields() int и Field(int). Единственное, чего вам не хватает, это проверка строки и метод SetString.

package main 

import "fmt" 
import "reflect" 
import "strings" 

type MyStruct struct { 
    A,B,C string 
    I int 
    D string 
    J int 
} 

func main() { 
    ms := MyStruct{"Green ", " Eggs", " and ", 2, " Ham  ", 15} 
    // Print it out now so we can see the difference 
    fmt.Printf("%s%s%s%d%s%d\n", ms.A, ms.B, ms.C, ms.I, ms.D, ms.J) 

    // We need a pointer so that we can set the value via reflection 
    msValuePtr := reflect.ValueOf(&ms) 
    msValue := msValuePtr.Elem() 

    for i := 0; i < msValue.NumField(); i++ { 
     field := msValue.Field(i) 

     // Ignore fields that don't have the same type as a string 
     if field.Type() != reflect.TypeOf("") { 
      continue 
     } 

     str := field.Interface().(string) 
     str = strings.TrimSpace(str) 
     field.SetString(str) 
    } 
    fmt.Printf("%s%s%s%d%s%d\n", ms.A, ms.B, ms.C, ms.I, ms.D, ms.J) 
} 

(Playground link)

Есть два предостережения здесь:

  1. Вам нужен указатель на то, что вы собираетесь изменить. Если у вас есть значение, вам нужно будет вернуть измененный результат.

  2. Попытки изменить невыполненные поля, как правило, вызовут панику. Если вы планируете модифицировать невыполненные поля, обязательно сделайте этот трюк внутри пакета.

Этот код является довольно гибким, вы можете использовать операторы переключателей или переключателей типа (на значение, возвращенное field.Interface()), если вам нужно отличаясь поведение в зависимости от типа.

Редактировать: Что касается поведения тегов, вы, похоже, уже это выяснили. Когда у вас есть поле и проверили, что это строка, вы можете просто использовать field.Tag.Get("max") и проанализировать его оттуда.

Редактировать 2: Я сделал небольшую ошибку в теге. Теги являются частью отражения. Тип структуры, поэтому, чтобы получить их, вы можете использовать (это немного затянуто). msValue.Type().Field(i).Tag.Get("max")

(Playground version кода, который вы отправили в комментариях с рабочим тегом get).

+0

Отлично, вы очень помогли. Все мои поля экспортируются (структура также отражает мою схему БД), но Validate находится в том же пакете, что и в листинге, поэтому должно быть хорошо. Единственная проблема, с которой я все еще сталкиваюсь, заключается в использовании 'field.Tag.Get (« max »)' - если не поле, что я должен назвать методом «Tag»? http://play.golang.org/p/yMRLFCW4vt – elithrar

+1

Я только что редактировал. Теги являются частью отражения. Тип самой структуры, поэтому вам нужно повторно получить поле из 'msValue.Type()', а затем получить тег из соответствующего поля. – LinearZoetrope

+0

Отлично - это (http://play.golang.org/p/Uks300ZsS3) теперь хорошо работает. Я объявил 'listType: = reflect.TypeOf (* l)' в соответствии с ответом Тайсона, чтобы предоставить ярлык для доступа к полю 'Tag'. В очередной раз благодарим за помощь! – elithrar

4

я получил удар на удар, но так как я пошел на работу, вот решение:

type FormError []*string 

type Listing struct { 
    Title  string `max:"50"` 
    Location  string `max:"100"` 
    Description string `max:"10000"` 
    ExpiryDate time.Time 
    RenderedDesc template.HTML 
    Contact  string `max:"255"` 
} 

// Iterate over our struct, fix whitespace/formatting where possible 
// and return errors encountered 
func (l *Listing) Validate() error { 
    listingType := reflect.TypeOf(*l) 
    listingValue := reflect.ValueOf(l) 
    listingElem := listingValue.Elem() 

    var invalid FormError = []*string{} 
    // Iterate over fields 
    for i := 0; i < listingElem.NumField(); i++ { 
     fieldValue := listingElem.Field(i) 
     // For StructFields of type string, field = strings.TrimSpace(field) 
     if fieldValue.Type().Name() == "string" { 
      newFieldValue := strings.TrimSpace(fieldValue.Interface().(string)) 
      fieldValue.SetString(newFieldValue) 

      fieldType := listingType.Field(i) 
      maxLengthStr := fieldType.Tag.Get("max") 
      if maxLengthStr != "" { 
       maxLength, err := strconv.Atoi(maxLengthStr) 
       if err != nil { 
        panic("Field 'max' must be an integer") 
       } 
       //  check max length/convert to int/utf8.RuneCountInString 
       if utf8.RuneCountInString(newFieldValue) > maxLength { 
        //  if max length exceeded, invalid = append(invalid, "errormsg") 
        invalidMessage := `"`+fieldType.Name+`" is too long (max allowed: `+maxLengthStr+`)` 
        invalid = append(invalid, &invalidMessage) 
       } 
      } 
     } 
    } 

    if len(invalid) > 0 { 
     return invalid 
    } 

    return nil 
} 

func (f FormError) Error() string { 
    var fullError string 
    for _, v := range f { 
     fullError = *v + "\n" 
    } 
    return "Errors were encountered during form processing: " + fullError 
} 

Я вижу, что вы спросили о том, как сделать метки. Отражение имеет два компонента: тип и значение. Тег связан с типом, поэтому вы должны получить его отдельно, чем поле: listingType := reflect.TypeOf(*l). Затем вы можете получить проиндексированное поле и тег.

+0

Спасибо за код + уточнение по типу против значения для отражения. Мне любопытно, почему мы будем использовать указатель на срез ('* [] string') в качестве основы для настраиваемого типа ошибки? Я не думаю, что копирование является серьезной проблемой здесь. Я также предполагаю, что 'reflect.TypeOf (* l)' гарантирует, что мы получим базовый тип 'l'? – elithrar

+1

'[] * string' не является указателем на фрагмент, это фрагмент указателей на строку. Но нет, у меня нет веских оснований для использования указателей - это просто моя привычка делать это. И да, вы разыскиваете 'l', чтобы получить тип структуры. Если вы этого не сделаете, вы получите тип указателя. :-) – Tyson