20
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

構造体のフィールドをJSON Schemaでvalidationする

Posted at

はじめに

構造体の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型のスキーマも同じようにチェックできるようになります。

20
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?