golangでコマンドラインツールを作る #6 interface{}をstructにマッピングする
その5の続き
PBXFileReference
isaが特定できるようになったので、どれか一つのisaのjsonをパースしてみる。 例えば、なんのファイルがあるかのisaはPBXFileReferenceでこれは必ずこういう形をしてる。(※進めていくうちに気づいたけどしてなかった。)
{ "path" : "AppDelegate.swift", "isa" : "PBXFileReference", "lastKnownFileType" : "sourcecode.swift", "sourceTree" : "<group>" }
これなら型にできそう。
golangの型
golangにはクラスという概念がないっぽい。その代わりに構造体を使って型を作れるって感じで合ってるのかな? よく分からないけどそんな雰囲気と仮置きしておいてとりあえず先に進む。
構造体を定義する。
type FileReference struct { path string lastKnownFileType string sourceTree string }
構造体を初期化するやり方は色々あるようだ。これが参考になる。[Go] 構造体の初期化方法まとめ - Qiita
で、初期化しようと思ってるんだけど、ループで回してるバリューがinterface{}
なのでキャスト、どうすればいいかわからない。
ので、調べてみる。
interface{}のキャスト
interface{}
をある型として扱うっていうやつなんかうまくいってたから、適当にv.(string)
とかやってたけど
これがまさにそうだったらしい。ここにやり方まとまってる。Golang: interface{}, type assertions and type switches
全部で3パターンやり方がある。
俺が今までやってたのは危険なキャストの仕方を使ってたようだ。間違えると、panicを起こす。
panic: interface conversion: interface is string, not map[string]interface {}
安全なキャストは、第2返却値をもらうようにする。キャストがうまくいかなかったかが分かる。
もう一つはswitchで考えうる限りの型チェックもできる。TPOによって使えばよさそう。
var a interface{} = "string" s := a.(string) // キャストできたかチェックできない s, ok := a.(string) // okにtrue、falseが帰ってきてキャストできなければチェックできる switch v := a.(type) { case string: fmt.Println(v) case int32, int64: fmt.Println(v) default: fmt.Println("unknown") }
swiftのas?に似てるかもしれない。
let a: AnyObject = "string" if let s = a as? String { println(s) } else if let i = a as? Int { println(i) }
で、いざやってみたらやっぱりPBXFileReferenceは決まった型じゃないことが分かった。 mapのkeyがないとpanicになるようだ。 これも、さっきのキャストみたいに、第2返り値で安全にチェックできるようだ。 ということで新しい型を定義
type FileReference struct { name string path string lastKnownFileType string includeInIndex string explicitFileType string sourceTree string }
そして、mapでとるときに安全にとる。安全にとりつつなければデフォルト値とかってmapでできるのかな? なんかちゃっと調べたけどなさそう。なので、これも自分で書く。 ジェネリクスみたいのできないのかな。
func lookupStr(m map[string]interface{}, k string) string { if v, found := m[k]; found { if s, ok := v.(string); ok { return s } } return "" }
そして、ようやくそれっぽいのができた。
コマンドラインのフラグで分ける
で、今のままだとセクションを指定してその情報を見るみたいなことができない。 こうやりたい。
$ xgodeproj show project.pbxproj --section PBXFileReference
どうやら、これはcodegangsta/cliの仕組みなのかgolangの仕組みか分からないけど、 Flagというやつでできるっぽい。 やり方は簡単。commands.goのFlagsに以下のように追加する。
var Commands = []cli.Command{ { Name: "show", Usage: "Prints section names or each section information", Action: command.CmdShow, Flags: []cli.Flag{ cli.StringFlag{ Name: "section", Value: "", Usage: "section name for pbxproj", }, }, }, }
使う側では、こんな感じで取得できる。
section := c.String("section")
この辺りを組み合わせて結果的にこうなった。それっぽくなってきたぞ。
$ xgodeproj show project.pbxproj --section PBXFileReference SampleTests.xctest Base.lproj/LaunchScreen.xib Images.xcassets Base.lproj/Main.storyboard AppDelegate.swift Sample.app Info.plist Info.plist SampleTests.swift ViewController.swift
ここまでの感想
- interfaceに対して実装する、継承できないっていうのswiftのPOPに似てるかもと思った。
- キャストのやり方分かった。
- 安全にやるやり方は、たいてい、
xxx, ok := process()
みたいな感じってことが分かってきた - switchが便利だ。if-elseの代わりになる
- structをnewするやり方がいっぱいあるのは逆にやめてほしい