strangejsonで生成されるUnmarshalJSONのコードを短くした
生成されるUnmarshalJSONのコードを短くした。具体的にやったことはnewtype的に定義したstructのunmarsalをcastで済ませるようにした。
生成されるUnmarshalJSON
例えば以下のようなstruct定義で生成されるUnmarshalJSONは以下のようなもの。
// Item : type Item struct { Name string } // UnmarshalJSON : (generated from github.com/podhmo/strangejson/examples/manytypes03.Item) func (x *Item) UnmarshalJSON(b []byte) error { type internal struct { Name *string } var p internal if err := json.Unmarshal(b, &p); err != nil { return err } if p.Name == nil { return errors.New("Name is required") } x.Name = *p.Name return nil }
何が異なるかと言うと。requiredのチェックが入っている点。nameが空の場合にはエラーになる。 (defaultがrequired。unrequiredにするにはフィールドにタグを追加する)
newtype的に定義した場合
以下のようにnewtype的に定義した場合に重複したコードを生成するのを止めた。
// Item2 : newtype type Item2 Item func (x *Item2) UnmarshalJSON(b []byte) error { return (*Item)(x).UnmarshalJSON(b) }
短い(良い)。
(aliasの場合)
これは何も生成されない。aliasなので。
// Item3 : alias type Item3 = Item
構造が同じもの
構造が同じものの場合も新しめのgoならキャスト可能なのだけれど。それには対応していない。
// Item4 : duplicated type Item4 struct { Name string }
go/typesの関数群を使えばたぶんサポートすることは可能なのだけれど。対応していない理由の1つにタグ部分だけ異なるものへの対応も考えなければ行けない点がある。
// Item5 : difference only required/unrequired type Item5 struct { Name string `required:"false"` }
こちらの定義ではnameはunrequiredになって欲しい。実際生成されたコードではnameのrequiredチェックが外れている。
func (x *Item5) UnmarshalJSON(b []byte) error { type internal struct { Name *string `required:"false"` } var p internal if err := json.Unmarshal(b, &p); err != nil { return err } if p.Name != nil { x.Name = *p.Name } return nil }
パッケージをまたがった場合
複数のパッケージに跨ったものでもキャストで済ませられる。例えば以下の様な構造になっていて。
$ tree . ├── item │ ├── model_gen.go │ └── model.go ├── item2 │ ├── model_gen.go │ └── model.go
item/model.goの定義をitem2/model.goで使っていた場合にも省略してくれるようにした。
package item2 import "github.com/podhmo/strangejson/examples/manypackages04/item" // UnmarshalJSON : (generated from github.com/podhmo/strangejson/examples/manypackages04/item2.Item2) func (x *Item2) UnmarshalJSON(b []byte) error { return (*item.Item)(x).UnmarshalJSON(b) }
この時のstructの定義はそれぞれ以下のようなもの。
item/model.go
package item // Item : type Item struct { Name string }
item2/model.go
package item2 import ( "github.com/podhmo/strangejson/examples/manypackages04/item" ) // Item2 : newtype type Item2 item.Item
コマンド
ちなみに生成する時のコマンドは以下の様な感じ(変わりうる)
$ strangejson --pkg github.com/podhmo/strangejson/examples/manypackages04/* 2018/02/23 04:07:24 for github.com/podhmo/strangejson/examples/manypackages04/item.Item.Item.UnmarshalJSON 2018/02/23 04:07:24 write $GOPATH/src/github.com/podhmo/strangejson/examples/manypackages04/item/model_gen.go 2018/02/23 04:07:24 for github.com/podhmo/strangejson/examples/manypackages04/item2.Item2.Item2.UnmarshalJSON 2018/02/23 04:07:24 write $GOPATH/src/github.com/podhmo/strangejson/examples/manypackages04/item2/model_gen.go
packageからfilepathを推測する方法あるいはその逆
packageからfilepathを推測する方法などをまとめておきたかった。
(ついでに個人的な信仰からホームディレクトリは~
になっている)
packageからfilepath
package main import ( "go/build" "os" "os/user" "path/filepath" "strings" "github.com/pkg/errors" ) func guessPath(pkgname string) (string, error) { ctxt := build.Default for _, srcdir := range ctxt.SrcDirs() { path := filepath.Join(srcdir, pkgname) if info, err := os.Stat(path); err == nil && info.IsDir() { u, err := user.Current() if err != nil { return "", err } return strings.Replace(path, u.HomeDir, "~", 1), nil } } return "", errors.Errorf("%q's physical address is not found", pkgname) }
filepathからpackage
package main import ( "go/build" "os/user" "path/filepath" "strings" "github.com/pkg/errors" ) func guessPkg(path string) (string, error) { if strings.HasPrefix(path, "~") { u, err := user.Current() if err != nil { return "", err } path = filepath.Join(u.HomeDir, path[1:]) } ctxt := build.Default path, err := filepath.Abs(path) if err != nil { return "", err } for _, srcdir := range ctxt.SrcDirs() { if strings.HasPrefix(path, srcdir) { pkgname := strings.TrimLeft(strings.Replace(path, srcdir, "", 1), "/") return pkgname, nil } } return "", errors.Errorf("%q is not subdir of srcdirs(%q)", path, build.Default.SrcDirs()) }
使った結果
GOPATHの方もGOROOTの方もOK
func main() { { pkg := "golang.org/x/tools/go/loader" fmt.Println(guessPath(pkg)) path, _ := guessPath(pkg) fmt.Println(guessPkg(path)) } fmt.Println("----------------------------------------") { pkg := "encoding/json" fmt.Println(guessPath(pkg)) path, _ := guessPath(pkg) fmt.Println(guessPkg(path)) } }
~/go/src/golang.org/x/tools/go/loader <nil> golang.org/x/tools/go/loader <nil> ---------------------------------------- /usr/lib/go/src/encoding/json <nil> encoding/json <nil>