xeipuuv/gojsonschema
GitHub: xeipuuv/gojsonschema
Go 语言实现的 JSON Schema 验证库,支持 draft v4/v6/v7,用于对 JSON 文档进行结构和格式校验。
Stars: 2737 | Forks: 373
[](https://godoc.org/github.com/xeipuuv/gojsonschema)
[](https://travis-ci.org/xeipuuv/gojsonschema)
[](https://goreportcard.com/report/github.com/xeipuuv/gojsonschema)
# gojsonschema
## 描述
JSON Schema 的 Go 语言实现。支持 draft-04、draft-06 和 draft-07。
参考:
* http://json-schema.org
* http://json-schema.org/latest/json-schema-core.html
* http://json-schema.org/latest/json-schema-validation.html
## 安装
```
go get github.com/xeipuuv/gojsonschema
```
依赖:
* [github.com/xeipuuv/gojsonpointer](https://github.com/xeipuuv/gojsonpointer)
* [github.com/xeipuuv/gojsonreference](https://github.com/xeipuuv/gojsonreference)
* [github.com/stretchr/testify/assert](https://github.com/stretchr/testify#assert-package)
## 用法
### 示例
```
package main
import (
"fmt"
"github.com/xeipuuv/gojsonschema"
)
func main() {
schemaLoader := gojsonschema.NewReferenceLoader("file:///home/me/schema.json")
documentLoader := gojsonschema.NewReferenceLoader("file:///home/me/document.json")
result, err := gojsonschema.Validate(schemaLoader, documentLoader)
if err != nil {
panic(err.Error())
}
if result.Valid() {
fmt.Printf("The document is valid\n")
} else {
fmt.Printf("The document is not valid. see errors :\n")
for _, desc := range result.Errors() {
fmt.Printf("- %s\n", desc)
}
}
}
```
#### 加载器
有多种方式可以加载你的 JSON 数据。
为了加载你的 schema 和文档,
首先声明一个合适的加载器:
* Web / HTTP,使用引用:
```
loader := gojsonschema.NewReferenceLoader("http://www.some_host.com/schema.json")
```
* 本地文件,使用引用:
```
loader := gojsonschema.NewReferenceLoader("file:///home/me/schema.json")
```
引用使用 URI scheme,需要前缀 (file://) 和文件的完整路径。
* JSON 字符串:
```
loader := gojsonschema.NewStringLoader(`{"type": "string"}`)
```
* 自定义 Go 类型:
```
m := map[string]interface{}{"type": "string"}
loader := gojsonschema.NewGoLoader(m)
```
以及
```
type Root struct {
Users []User `json:"users"`
}
type User struct {
Name string `json:"name"`
}
...
data := Root{}
data.Users = append(data.Users, User{"John"})
data.Users = append(data.Users, User{"Sophia"})
data.Users = append(data.Users, User{"Bill"})
loader := gojsonschema.NewGoLoader(data)
```
#### 验证
一旦设置了加载器,验证就很简单:
```
result, err := gojsonschema.Validate(schemaLoader, documentLoader)
```
或者,你可能希望只加载一次 schema 并进行多次验证:
```
schema, err := gojsonschema.NewSchema(schemaLoader)
...
result1, err := schema.Validate(documentLoader1)
...
result2, err := schema.Validate(documentLoader2)
...
// etc ...
```
检查结果:
```
if result.Valid() {
fmt.Printf("The document is valid\n")
} else {
fmt.Printf("The document is not valid. see errors :\n")
for _, err := range result.Errors() {
// Err implements the ResultError interface
fmt.Printf("- %s\n", err)
}
}
```
## 加载本地 schema
默认情况下,指向外部 schema 的 `file` 和 `http(s)` 引用会自动通过文件系统或通过 http(s) 加载。也可以使用 `SchemaLoader` 加载外部 schema。
```
sl := gojsonschema.NewSchemaLoader()
loader1 := gojsonschema.NewStringLoader(`{ "type" : "string" }`)
err := sl.AddSchema("http://some_host.com/string.json", loader1)
```
或者,如果你的 schema 已经有 `$id`,你可以使用 `AddSchemas` 函数。
```
loader2 := gojsonschema.NewStringLoader(`{
"$id" : "http://some_host.com/maxlength.json",
"maxLength" : 5
}`)
err = sl.AddSchemas(loader2)
```
主 schema 应该传递给 `Compile` 函数。然后,该主 schema 可以直接引用已添加的 schema,而无需下载它们。
```
loader3 := gojsonschema.NewStringLoader(`{
"$id" : "http://some_host.com/main.json",
"allOf" : [
{ "$ref" : "http://some_host.com/string.json" },
{ "$ref" : "http://some_host.com/maxlength.json" }
]
}`)
schema, err := sl.Compile(loader3)
documentLoader := gojsonschema.NewStringLoader(`"hello world"`)
result, err := schema.Validate(documentLoader)
```
也可以将引用已加载 schema 的 `ReferenceLoader` 传递给 `Compile` 函数。
```
err = sl.AddSchemas(loader3)
schema, err := sl.Compile(gojsonschema.NewReferenceLoader("http://some_host.com/main.json"))
```
除非使用元 schema 验证,否则通过 `AddSchema` 和 `AddSchemas` 添加的 schema 仅在整个 schema 被编译时才会进行验证。
## 使用特定的 draft
默认情况下,`gojsonschema` 会尝试使用 `$schema` 关键字检测 schema 的 draft,并以严格的 draft-04、draft-06 或 draft-07 模式进行解析。如果缺少 `$schema`,或者未显式设置 draft 版本,则会使用混合模式,该模式将所有 draft 的功能合并到一个模式中。
可以使用 `AutoDetect` 属性关闭自动检测。可以使用 `Draft` 属性指定特定的 draft 版本。
```
sl := gojsonschema.NewSchemaLoader()
sl.Draft = gojsonschema.Draft7
sl.AutoDetect = false
```
如果开启自动检测(默认),只要在所有 schema 中指定了 `$schema`,draft-07 schema 就可以安全地引用 draft-04 schema,反之亦然。
## 元 schema 验证
通过设置 `Validate` 属性,可以对使用 `AddSchema`、`AddSchemas` 和 `Compile` 添加的 schema 针对其元 schema 进行验证。
以下示例将产生错误,因为 `multipleOf` 必须是一个数字。如果 `Validate` 关闭(默认),则此错误只会在 `Compile` 步骤返回。
```
sl := gojsonschema.NewSchemaLoader()
sl.Validate = true
err := sl.AddSchemas(gojsonschema.NewStringLoader(`{
"$id" : "http://some_host.com/invalid.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"multipleOf" : true
}`))
```
元 schema 验证返回的错误可读性更强,并包含更多信息,这对于开发 schema 非常有帮助。
元 schema 验证也适用于自定义 `$schema`。如果缺少 `$schema`,或者 `AutoDetect` 设置为 `false`,则会使用所使用的 draft 的元 schema。
## 处理错误
该库处理字符串错误代码,你可以通过创建自己的 gojsonschema.locale 并设置它来进行自定义。
```
gojsonschema.Locale = YourCustomLocale{}
```
但是,每个错误都包含额外的上下文信息。
较新版本的 `gojsonschema` 可能会有新的额外错误,因此当发生这种情况时,需要更新使用自定义 locale 的代码。
**err.Type()**:*string* 返回发生的错误的“类型”。注意,你也可以进行类型检查。见下文
注意:RequiredType 类型的错误返回的 err.Type() 值为 "required"
```
"required": RequiredError
"invalid_type": InvalidTypeError
"number_any_of": NumberAnyOfError
"number_one_of": NumberOneOfError
"number_all_of": NumberAllOfError
"number_not": NumberNotError
"missing_dependency": MissingDependencyError
"internal": InternalError
"const": ConstEror
"enum": EnumError
"array_no_additional_items": ArrayNoAdditionalItemsError
"array_min_items": ArrayMinItemsError
"array_max_items": ArrayMaxItemsError
"unique": ItemsMustBeUniqueError
"contains" : ArrayContainsError
"array_min_properties": ArrayMinPropertiesError
"array_max_properties": ArrayMaxPropertiesError
"additional_property_not_allowed": AdditionalPropertyNotAllowedError
"invalid_property_pattern": InvalidPropertyPatternError
"invalid_property_name": InvalidPropertyNameError
"string_gte": StringLengthGTEError
"string_lte": StringLengthLTEError
"pattern": DoesNotMatchPatternError
"multiple_of": MultipleOfError
"number_gte": NumberGTEError
"number_gt": NumberGTError
"number_lte": NumberLTEError
"number_lt": NumberLTError
"condition_then" : ConditionThenError
"condition_else" : ConditionElseError
```
**err.Value()**:*interface{}* 返回给定的值
**err.Context()**:*gojsonschema.JsonContext* 返回上下文。它有一个 String() 方法,会打印出类似这样的内容:(root).firstName
**err.Field()**:*string* 返回格式为 firstName 的字段名,对于嵌入的属性,则为 person.firstName。这返回的内容与 *err.Context()* 上的 String() 方法相同,但去除了 (root). 前缀。
**err.Description()**:*string* 错误描述。这基于你正在使用的 locale。有关使用自定义实现覆盖 locale 的内容,请参见本节的开头。
**err.DescriptionFormat()**:*string* 错误描述格式。如果你之后要向结果添加自定义验证错误,这一点很重要。
**err.Details()**:*gojsonschema.ErrorDetails* 返回特定于该错误的额外错误详细信息的 map[string]interface{}。例如,GTE 错误会有一个 "min" 值,LTE 会有一个 "max" 值。有关所有错误详细信息的完整描述,请参见 errors.go。每个错误都始终包含一个 "field" 键,其值为 *err.Field()*
请注意,在大多数情况下,err.Details() 将用于在你的 locale 中生成替换字符串,而不是直接使用。这些字符串遵循 text/template 格式,即:
```
{{.field}} must be greater than or equal to {{.min}}
```
如果你需要更复杂的错误消息处理,该库允许你指定自定义模板函数。
```
gojsonschema.ErrorTemplateFuncs = map[string]interface{}{
"allcaps": func(s string) string {
return strings.ToUpper(s)
},
}
```
有了上述定义,你就可以在本地化模板中使用自定义函数 `"allcaps"` 了:
```
{{allcaps .field}} must be greater than or equal to {{.min}}
```
然后,上述错误消息在渲染时就会将 `field` 的值显示为大写字母。例如:
```
"PASSWORD must be greater than or equal to 8"
```
通过参考 Go 的 [text/template FuncMap](https://golang.org/pkg/text/template/#FuncMap) 类型,了解有关你可以在 `ErrorTemplateFuncs` 中使用哪些类型的模板函数的更多信息。
## Formats
JSON Schema 允许使用可选的 "format" 属性来针对已知格式验证实例。gojsonschema 内置了规范中定义的所有格式,你可以像这样使用它们:
```
{"type": "string", "format": "email"}
```
并非 draft-07 中定义的所有格式都可用。已实现的格式有:
* `date`
* `time`
* `date-time`
* `hostname`。也支持以数字开头的子域名,但这意味着它并不严格遵循 [RFC1034](http://tools.ietf.org/html/rfc1034#section-3.5),并且意味着 ipv4 地址也会被识别为有效的 hostname。
* `email`。Go 的 email 解析器与 [RFC5322](https://tools.ietf.org/html/rfc5322) 略有偏差。包含 unicode 支持。
* `idn-email`。与 `email` 有相同的注意事项。
* `ipv4`
* `ipv6`
* `uri`。包含 unicode 支持。
* `uri-reference`。包含 unicode 支持。
* `iri`
* `iri-reference`
* `uri-template`
* `uuid`
* `regex`。Go 使用 [RE2](https://github.com/google/re2/wiki/Syntax) 引擎,且与 [ECMA262](http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf) 不兼容。
* `json-pointer`
* `relative-json-pointer`
`email`、`uri` 和 `uri-reference` 使用与其对应的 unicode 格式 `idn-email`、`iri` 和 `iri-reference` 相同的验证代码。如果你依赖 unicode 支持,出于互操作性的考虑,你应该使用特定的支持 unicode 的格式,因为其他实现可能不支持常规格式中的 unicode。
`uri`、`idn-email` 及其相关项的验证代码主要使用标准库代码。
对于重复或更复杂的格式,你可以创建自定义格式检查器,并像这样将它们添加到 gojsonschema 中:
```
// Define the format checker
type RoleFormatChecker struct {}
// Ensure it meets the gojsonschema.FormatChecker interface
func (f RoleFormatChecker) IsFormat(input interface{}) bool {
asString, ok := input.(string)
if ok == false {
return false
}
return strings.HasPrefix("ROLE_", asString)
}
// Add it to the library
gojsonschema.FormatCheckers.Add("role", RoleFormatChecker{})
```
现在可以在你的 json schema 中使用:
```
{"type": "string", "format": "role"}
```
另一个示例是检查提供的整数是否与数据库中的 id 匹配:
JSON schema:
```
{"type": "integer", "format": "ValidUserId"}
```
```
// Define the format checker
type ValidUserIdFormatChecker struct {}
// Ensure it meets the gojsonschema.FormatChecker interface
func (f ValidUserIdFormatChecker) IsFormat(input interface{}) bool {
asFloat64, ok := input.(float64) // Numbers are always float64 here
if ok == false {
return false
}
// XXX
// do the magic on the database looking for the int(asFloat64)
return true
}
// Add it to the library
gojsonschema.FormatCheckers.Add("ValidUserId", ValidUserIdFormatChecker{})
```
格式也可以被移除,例如,如果你想覆盖默认定义的某个格式。
```
gojsonschema.FormatCheckers.Remove("hostname")
```
## 额外的自定义验证
在验证运行并得到结果后,你可以使用 `Result.AddError` 添加额外的错误。这很有用,可以保持结果集内的格式一致,而不必为你自己的错误添加特殊异常。下面是一个示例。
```
type AnswerInvalidError struct {
gojsonschema.ResultErrorFields
}
func newAnswerInvalidError(context *gojsonschema.JsonContext, value interface{}, details gojsonschema.ErrorDetails) *AnswerInvalidError {
err := AnswerInvalidError{}
err.SetContext(context)
err.SetType("custom_invalid_error")
// it is important to use SetDescriptionFormat() as this is used to call SetDescription() after it has been parsed
// using the description of err will be overridden by this.
err.SetDescriptionFormat("Answer to the Ultimate Question of Life, the Universe, and Everything is {{.answer}}")
err.SetValue(value)
err.SetDetails(details)
return &err
}
func main() {
// ...
schema, err := gojsonschema.NewSchema(schemaLoader)
result, err := gojsonschema.Validate(schemaLoader, documentLoader)
if true { // some validation
jsonContext := gojsonschema.NewJsonContext("question", nil)
errDetail := gojsonschema.ErrorDetails{
"answer": 42,
}
result.AddError(
newAnswerInvalidError(
gojsonschema.NewJsonContext("answer", jsonContext),
52,
errDetail,
),
errDetail,
)
}
return result, err
}
```
如果你想添加超出 json schema drafts 所能提供的业务特定逻辑验证,这尤其有用。
## 用途
gojsonschema 使用以下测试套件:
https://github.com/json-schema/JSON-Schema-Test-Suite
标签:EVTX分析, Go, JSON Schema, Ruby工具, 开发组件库, 数据校验, 数据格式, 日志审计