2014-01-21 21 views
6

構造体の文字列フィールドを反復処理して、クリーンアップ/検証(strings.TrimSpacestrings.Trimなど)を行うことができます。構造体の文字列フィールドを反復処理する

今私は、本当にスケーラブルではない厄介なスイッチケースを持っています。これは私のアプリケーション(ウェブフォーム)のホットスポットではないので、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

あなたが欲しいのは、主にreflect.Valueのメソッドで、NumFields() intField(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)

2つの注意点はここにあります

  1. あなたが変更しようとしているものへのポインタを必要としています。値がある場合は、変更した結果を返す必要があります。

  2. 非公開のフィールドを変更しようとすると、一般的にパニックに陥ります。あなたが非公開のフィールドを変更する予定の場合は、パッケージ内でこのトリックを行うようにしてください。

このコードはかなり柔軟性がありますが、種類に応じて異なる動作が必要な場合は、()field.Interface(によって返された値に)switch文や型のスイッチを使用することができます。

編集:タグの動作については、すでにわかっているようです。フィールドを取得して文字列であることを確認したら、field.Tag.Get("max")を使用してそこから解析することができます。

Edit2:タグで小さなエラーが発生しました。タグは構造体のreflect.Typeの一部なので、使用することができます(これは少し長めです)msValue.Type().Field(i).Tag.Get("max")

(作業タグ付きのコメントに投稿したコードのPlayground version

+0

すばらしかったです。私のすべてのフィールドがエクスポートされます(構造体も私のDBスキーマを反映します)が、Validateはリストと同じパッケージに入っています。私がまだ持っている唯一の問題は、 'field.Tag.Get(" max ")'を使用しています - もしそうでなければ、私は 'Tag'メソッドを呼び出すべきですか? http://play.golang.org/p/yMRLFCW4vt – elithrar

+1

私はちょうど編集を行いました。タグは構造体自体のreflect.Typeの一部です。したがって、 'msValue.Type()'からフィールドを再取得し、対応するフィールドからタグを取得する必要があります。 – LinearZoetrope

+0

グレート - これ(http://play.golang.org/p/Uks300ZsS3)はうまくいきました。フィールド 'Tag'にアクセスするためのショートカットを提供するための答えとして、' listType:= reflect.TypeOf(* l) 'を宣言しました。あなたの助けをもう一度ありがとう! – 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 
} 

私はあなたがタグを行う方法について尋ねご覧ください。リフレクションには、タイプと値の2つのコンポーネントがあります。タグはタイプに関連付けられているため、フィールドとは別に取得する必要があります(listingType := reflect.TypeOf(*l))。その後、インデックスフィールドとそのタグを取得できます。

+0

コード+反射のためのタイプと値の説明に感謝します。カスタムエラータイプの基礎としてスライスへのポインタ( '* [] string')を使用する理由について興味がありますか?私はコピーがここでは大きな問題ではないと思います。私は 'reflect.TypeOf(* l)'が 'l'の根底にある型を確実に取得すると仮定します。 – elithrar

+1

'[] * string'はスライスへのポインタではなく、文字列ポインタのスライスです。しかし、いいえ、私は実際にポインタを使用する正当な理由はありません - そうすることは私の習慣です。そして、はい、構造体の型を取得するために 'l'を参照しないでください。そうしなければ、ポインタの型が得られます。 :-) – Tyson