はじめに
構造体のstringフィールドにJSONを格納するときにJSON Schemaで手軽にvalidateする方法を紹介します。
gopkg.in/go-playground/validator.v9
validatorにはgopkg.in/go-playground/validator.v9を使います。
構造体の各フィールドに正しい値が入っているかどうかをお手軽にチェックするライブラリです。型だけではチェックできないような性質もチェックできるようになります。
他にも似たようなライブラリはありますがこれが一番validationの種類が多くシンプルでした。
構造体にvalidate
タグでvalidationの種類を指定するだけでチェックできるようになります。
例えば次のような構造体User
を定義できます。
type User struct {
Username string `validate:"required,printascii,max=16,"`
Name string
Email string `validate:"required,email"`
Age int `validate:"min=0"`
}
これ以外にも豊富なvalidationが用意されています。
https://godoc.org/gopkg.in/go-playground/validator.v9
また、後述しますが自分でカスタムvalidationを定義することもできます。
validationを定義したら次のようにして構造体をチェックします。。
package main
import (
"fmt"
"gopkg.in/go-playground/validator.v9"
)
type User struct {
Username string `validate:"required,printascii,max=16"`
Name string
Email string `validate:"required,email"`
Age int `validate:"min=0"`
}
func main() {
v := validator.New()
fmt.Println("---- valid values")
u := User{
Username: "valid user",
Email: "[email protected]",
Age: 20,
}
if err := v.Struct(u); err == nil {
fmt.Println("Valid!")
} else {
fmt.Println("Invalid!", err)
}
fmt.Println("---- invalid values")
u = User{
Username: "invalid user",
Email: "invalid",
Age: 20,
}
if err := v.Struct(u); err == nil {
fmt.Println("Valid!")
} else {
fmt.Println("Invalid!", err)
}
}
実行結果は次のようになります。
---- valid values
Valid!
---- invalid values
Invalid! Key: 'User.Email' Error:Field validation for 'Email' failed on the 'email' tag
github.com/xeipuuv/gojsonschema
JSON Schemaにはgithub.com/xeipuuv/gojsonschemaを使います。
次のようにしてJSONをチェックできます。
package main
import (
"fmt"
"github.com/xeipuuv/gojsonschema"
)
func main() {
schemaLoader := gojsonschema.NewStringLoader(`{"type":"string"}`)
fmt.Println("---- Valid JSON")
jsonLoader := gojsonschema.NewStringLoader(`"string"`)
result, _ := gojsonschema.Validate(schemaLoader, jsonLoader)
if result.Valid() {
fmt.Println("Valid!")
} else {
fmt.Println("Invalid!")
}
fmt.Println("---- Invalid JSON")
jsonLoader = gojsonschema.NewStringLoader(`{}`)
result, _ = gojsonschema.Validate(schemaLoader, jsonLoader)
if result.Valid() {
fmt.Println("Valid!")
} else {
fmt.Println("Invalid!")
}
}
実行結果は次のようになります。
---- Valid JSON
Valid!
---- Invalid JSON
Invalid!
このサンプルでは文字列でJSON SchemaやJSONを与えていますが、ファイルから直接読み込むことも可能です。
組み合わせる
この2つを組み合わせて次のようにして構造体のフィールドのJSONをJSON Schemaでチェックすることを目指します。
type User struct {
Username string `validate:"required,printascii,max=16"`
Profile string `validate:"required,jsonschema=profile"`
}
gopkg.in/go-playground/validator.v9には独自のカスタムvalidationを追加する機能があり、そのカスタムvalidationでJSON Schemaによるチェックを実装します。
JSON Schemaによるカスタムvalidationは次のように実装できます。
var schema = map[string]string = {
// JSON Schema
}
func isValidJSON(fl validator.FieldLevel) bool {
json := fl.Field().String()
field := fl.Param()
schemaLoader := gojsonschema.NewStringLoader(schema[field])
jsonLoader := gojsonschema.NewStringLoader(json)
result, _ := gojsonschema.Validate(schemaLoader, jsonLoader)
return result.Valid()
}
fl.Field().String()
でフィールドの値、つまりJSONがとれます。
また、fl.Param()
でvalidationのパラメータがとれます。今回の場合は"profile"
になります。
このサンプルではパラメータによってschema
というmapからJSON Schemaを取り出すようになっています。
このカスタムvalidationをvalidate:"jsonschema=xxx"
で使えるようにするには次のようにします。
v := validator.New()
v.RegisterValidation("jsonschema", isValidJSON)
最終的にこんな感じになります。
package main
import (
"fmt"
"github.com/go-playground/validator"
"github.com/xeipuuv/gojsonschema"
)
type User struct {
Username string `validate:"required,printascii,max=16"`
Profile string `validate:"required,jsonschema=profile"`
}
const profileSchema = `
{
"type":"object",
"properties":{
"age": {
"type":"integer",
"minimum":0
}
}
}
`
var schema = map[string]string{"profile": profileSchema}
func main() {
v := validator.New()
v.RegisterValidation("jsonschema", isValidJSON)
fmt.Println("---- valid values")
u := User{
Username: "valid user",
Profile: `{"age":20}`,
}
if err := v.Struct(u); err == nil {
fmt.Println("Valid!")
} else {
fmt.Println("Invalid!", err)
}
fmt.Println("---- invalid values")
u = User{
Username: "invalid user",
Profile: `{"age":-666}`,
}
if err := v.Struct(u); err == nil {
fmt.Println("Valid!")
} else {
fmt.Println("Invalid!", err)
}
}
func isValidJSON(fl validator.FieldLevel) bool {
json := fl.Field().String()
field := fl.Param()
schemaLoader := gojsonschema.NewStringLoader(schema[field])
jsonLoader := gojsonschema.NewStringLoader(json)
result, _ := gojsonschema.Validate(schemaLoader, jsonLoader)
return result.Valid()
}
実行結果は次のようになります。
---- valid values
Valid!
---- invalid values
Invalid! Key: 'User.Profile' Error:Field validation for 'Profile' failed on the 'jsonschema' tag
おわりに
今回のソースコードはこちらに置いてます。
https://github.com/nownabe/examples/tree/master/jsonschemavalidation
データベースに挿入するときにvalidatorを使うこともあるかと思いますが、この方法を使うことでJSON型のスキーマも同じようにチェックできるようになります。