xeipuuv/gojsonschema

GitHub: xeipuuv/gojsonschema

Go 语言实现的 JSON Schema 验证库,支持 draft v4/v6/v7,用于对 JSON 文档进行结构和格式校验。

Stars: 2737 | Forks: 373

[![GoDoc](https://godoc.org/github.com/xeipuuv/gojsonschema?status.svg)](https://godoc.org/github.com/xeipuuv/gojsonschema) [![Build Status](https://travis-ci.org/xeipuuv/gojsonschema.svg)](https://travis-ci.org/xeipuuv/gojsonschema) [![Go Report Card](https://goreportcard.com/badge/github.com/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工具, 开发组件库, 数据校验, 数据格式, 日志审计