README.md
1[![GoDoc](https://godoc.org/github.com/xeipuuv/gojsonschema?status.svg)](https://godoc.org/github.com/xeipuuv/gojsonschema)
2[![Build Status](https://travis-ci.org/xeipuuv/gojsonschema.svg)](https://travis-ci.org/xeipuuv/gojsonschema)
3[![Go Report Card](https://goreportcard.com/badge/github.com/xeipuuv/gojsonschema)](https://goreportcard.com/report/github.com/xeipuuv/gojsonschema)
4
5# gojsonschema
6
7## Description
8
9An implementation of JSON Schema for the Go programming language. Supports draft-04, draft-06 and draft-07.
10
11References :
12
13* http://json-schema.org
14* http://json-schema.org/latest/json-schema-core.html
15* http://json-schema.org/latest/json-schema-validation.html
16
17## Installation
18
19```
20go get github.com/xeipuuv/gojsonschema
21```
22
23Dependencies :
24* [github.com/xeipuuv/gojsonpointer](https://github.com/xeipuuv/gojsonpointer)
25* [github.com/xeipuuv/gojsonreference](https://github.com/xeipuuv/gojsonreference)
26* [github.com/stretchr/testify/assert](https://github.com/stretchr/testify#assert-package)
27
28## Usage
29
30### Example
31
32```go
33
34package main
35
36import (
37 "fmt"
38 "github.com/xeipuuv/gojsonschema"
39)
40
41func main() {
42
43 schemaLoader := gojsonschema.NewReferenceLoader("file:///home/me/schema.json")
44 documentLoader := gojsonschema.NewReferenceLoader("file:///home/me/document.json")
45
46 result, err := gojsonschema.Validate(schemaLoader, documentLoader)
47 if err != nil {
48 panic(err.Error())
49 }
50
51 if result.Valid() {
52 fmt.Printf("The document is valid\n")
53 } else {
54 fmt.Printf("The document is not valid. see errors :\n")
55 for _, desc := range result.Errors() {
56 fmt.Printf("- %s\n", desc)
57 }
58 }
59}
60
61
62```
63
64#### Loaders
65
66There are various ways to load your JSON data.
67In order to load your schemas and documents,
68first declare an appropriate loader :
69
70* Web / HTTP, using a reference :
71
72```go
73loader := gojsonschema.NewReferenceLoader("http://www.some_host.com/schema.json")
74```
75
76* Local file, using a reference :
77
78```go
79loader := gojsonschema.NewReferenceLoader("file:///home/me/schema.json")
80```
81
82References use the URI scheme, the prefix (file://) and a full path to the file are required.
83
84* JSON strings :
85
86```go
87loader := gojsonschema.NewStringLoader(`{"type": "string"}`)
88```
89
90* Custom Go types :
91
92```go
93m := map[string]interface{}{"type": "string"}
94loader := gojsonschema.NewGoLoader(m)
95```
96
97And
98
99```go
100type Root struct {
101 Users []User `json:"users"`
102}
103
104type User struct {
105 Name string `json:"name"`
106}
107
108...
109
110data := Root{}
111data.Users = append(data.Users, User{"John"})
112data.Users = append(data.Users, User{"Sophia"})
113data.Users = append(data.Users, User{"Bill"})
114
115loader := gojsonschema.NewGoLoader(data)
116```
117
118#### Validation
119
120Once the loaders are set, validation is easy :
121
122```go
123result, err := gojsonschema.Validate(schemaLoader, documentLoader)
124```
125
126Alternatively, you might want to load a schema only once and process to multiple validations :
127
128```go
129schema, err := gojsonschema.NewSchema(schemaLoader)
130...
131result1, err := schema.Validate(documentLoader1)
132...
133result2, err := schema.Validate(documentLoader2)
134...
135// etc ...
136```
137
138To check the result :
139
140```go
141 if result.Valid() {
142 fmt.Printf("The document is valid\n")
143 } else {
144 fmt.Printf("The document is not valid. see errors :\n")
145 for _, err := range result.Errors() {
146 // Err implements the ResultError interface
147 fmt.Printf("- %s\n", err)
148 }
149 }
150```
151
152
153## Loading local schemas
154
155By default `file` and `http(s)` references to external schemas are loaded automatically via the file system or via http(s). An external schema can also be loaded using a `SchemaLoader`.
156
157```go
158 sl := gojsonschema.NewSchemaLoader()
159 loader1 := gojsonschema.NewStringLoader(`{ "type" : "string" }`)
160 err := sl.AddSchema("http://some_host.com/string.json", loader1)
161```
162
163Alternatively if your schema already has an `$id` you can use the `AddSchemas` function
164```go
165 loader2 := gojsonschema.NewStringLoader(`{
166 "$id" : "http://some_host.com/maxlength.json",
167 "maxLength" : 5
168 }`)
169 err = sl.AddSchemas(loader2)
170```
171
172The main schema should be passed to the `Compile` function. This main schema can then directly reference the added schemas without needing to download them.
173```go
174 loader3 := gojsonschema.NewStringLoader(`{
175 "$id" : "http://some_host.com/main.json",
176 "allOf" : [
177 { "$ref" : "http://some_host.com/string.json" },
178 { "$ref" : "http://some_host.com/maxlength.json" }
179 ]
180 }`)
181
182 schema, err := sl.Compile(loader3)
183
184 documentLoader := gojsonschema.NewStringLoader(`"hello world"`)
185
186 result, err := schema.Validate(documentLoader)
187```
188
189It's also possible to pass a `ReferenceLoader` to the `Compile` function that references a loaded schema.
190
191```go
192err = sl.AddSchemas(loader3)
193schema, err := sl.Compile(gojsonschema.NewReferenceLoader("http://some_host.com/main.json"))
194```
195
196Schemas added by `AddSchema` and `AddSchemas` are only validated when the entire schema is compiled, unless meta-schema validation is used.
197
198## Using a specific draft
199By default `gojsonschema` will try to detect the draft of a schema by using the `$schema` keyword and parse it in a strict draft-04, draft-06 or draft-07 mode. If `$schema` is missing, or the draft version is not explicitely set, a hybrid mode is used which merges together functionality of all drafts into one mode.
200
201Autodectection can be turned off with the `AutoDetect` property. Specific draft versions can be specified with the `Draft` property.
202
203```go
204sl := gojsonschema.NewSchemaLoader()
205sl.Draft = gojsonschema.Draft7
206sl.AutoDetect = false
207```
208
209If autodetection is on (default), a draft-07 schema can savely reference draft-04 schemas and vice-versa, as long as `$schema` is specified in all schemas.
210
211## Meta-schema validation
212Schemas that are added using the `AddSchema`, `AddSchemas` and `Compile` can be validated against their meta-schema by setting the `Validate` property.
213
214The following example will produce an error as `multipleOf` must be a number. If `Validate` is off (default), this error is only returned at the `Compile` step.
215
216```go
217sl := gojsonschema.NewSchemaLoader()
218sl.Validate = true
219err := sl.AddSchemas(gojsonschema.NewStringLoader(`{
220 $id" : "http://some_host.com/invalid.json",
221 "$schema": "http://json-schema.org/draft-07/schema#",
222 "multipleOf" : true
223}`))
224 ```
225```
226 ```
227
228Errors returned by meta-schema validation are more readable and contain more information, which helps significantly if you are developing a schema.
229
230Meta-schema validation also works with a custom `$schema`. In case `$schema` is missing, or `AutoDetect` is set to `false`, the meta-schema of the used draft is used.
231
232
233## Working with Errors
234
235The library handles string error codes which you can customize by creating your own gojsonschema.locale and setting it
236```go
237gojsonschema.Locale = YourCustomLocale{}
238```
239
240However, each error contains additional contextual information.
241
242Newer versions of `gojsonschema` may have new additional errors, so code that uses a custom locale will need to be updated when this happens.
243
244**err.Type()**: *string* Returns the "type" of error that occurred. Note you can also type check. See below
245
246Note: An error of RequiredType has an err.Type() return value of "required"
247
248 "required": RequiredError
249 "invalid_type": InvalidTypeError
250 "number_any_of": NumberAnyOfError
251 "number_one_of": NumberOneOfError
252 "number_all_of": NumberAllOfError
253 "number_not": NumberNotError
254 "missing_dependency": MissingDependencyError
255 "internal": InternalError
256 "const": ConstEror
257 "enum": EnumError
258 "array_no_additional_items": ArrayNoAdditionalItemsError
259 "array_min_items": ArrayMinItemsError
260 "array_max_items": ArrayMaxItemsError
261 "unique": ItemsMustBeUniqueError
262 "contains" : ArrayContainsError
263 "array_min_properties": ArrayMinPropertiesError
264 "array_max_properties": ArrayMaxPropertiesError
265 "additional_property_not_allowed": AdditionalPropertyNotAllowedError
266 "invalid_property_pattern": InvalidPropertyPatternError
267 "invalid_property_name": InvalidPropertyNameError
268 "string_gte": StringLengthGTEError
269 "string_lte": StringLengthLTEError
270 "pattern": DoesNotMatchPatternError
271 "multiple_of": MultipleOfError
272 "number_gte": NumberGTEError
273 "number_gt": NumberGTError
274 "number_lte": NumberLTEError
275 "number_lt": NumberLTError
276 "condition_then" : ConditionThenError
277 "condition_else" : ConditionElseError
278
279**err.Value()**: *interface{}* Returns the value given
280
281**err.Context()**: *gojsonschema.JsonContext* Returns the context. This has a String() method that will print something like this: (root).firstName
282
283**err.Field()**: *string* Returns the fieldname in the format firstName, or for embedded properties, person.firstName. This returns the same as the String() method on *err.Context()* but removes the (root). prefix.
284
285**err.Description()**: *string* The error description. This is based on the locale you are using. See the beginning of this section for overwriting the locale with a custom implementation.
286
287**err.DescriptionFormat()**: *string* The error description format. This is relevant if you are adding custom validation errors afterwards to the result.
288
289**err.Details()**: *gojsonschema.ErrorDetails* Returns a map[string]interface{} of additional error details specific to the error. For example, GTE errors will have a "min" value, LTE will have a "max" value. See errors.go for a full description of all the error details. Every error always contains a "field" key that holds the value of *err.Field()*
290
291Note in most cases, the err.Details() will be used to generate replacement strings in your locales, and not used directly. These strings follow the text/template format i.e.
292```
293{{.field}} must be greater than or equal to {{.min}}
294```
295
296The library allows you to specify custom template functions, should you require more complex error message handling.
297```go
298gojsonschema.ErrorTemplateFuncs = map[string]interface{}{
299 "allcaps": func(s string) string {
300 return strings.ToUpper(s)
301 },
302}
303```
304
305Given the above definition, you can use the custom function `"allcaps"` in your localization templates:
306```
307{{allcaps .field}} must be greater than or equal to {{.min}}
308```
309
310The above error message would then be rendered with the `field` value in capital letters. For example:
311```
312"PASSWORD must be greater than or equal to 8"
313```
314
315Learn more about what types of template functions you can use in `ErrorTemplateFuncs` by referring to Go's [text/template FuncMap](https://golang.org/pkg/text/template/#FuncMap) type.
316
317## Formats
318JSON Schema allows for optional "format" property to validate instances against well-known formats. gojsonschema ships with all of the formats defined in the spec that you can use like this:
319
320````json
321{"type": "string", "format": "email"}
322````
323
324Not all formats defined in draft-07 are available. Implemented formats are:
325
326* `date`
327* `time`
328* `date-time`
329* `hostname`. Subdomains that start with a number are also supported, but this means that it doesn't strictly follow [RFC1034](http://tools.ietf.org/html/rfc1034#section-3.5) and has the implication that ipv4 addresses are also recognized as valid hostnames.
330* `email`. Go's email parser deviates slightly from [RFC5322](https://tools.ietf.org/html/rfc5322). Includes unicode support.
331* `idn-email`. Same caveat as `email`.
332* `ipv4`
333* `ipv6`
334* `uri`. Includes unicode support.
335* `uri-reference`. Includes unicode support.
336* `iri`
337* `iri-reference`
338* `uri-template`
339* `uuid`
340* `regex`. Go uses the [RE2](https://github.com/google/re2/wiki/Syntax) engine and is not [ECMA262](http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf) compatible.
341* `json-pointer`
342* `relative-json-pointer`
343
344`email`, `uri` and `uri-reference` use the same validation code as their unicode counterparts `idn-email`, `iri` and `iri-reference`. If you rely on unicode support you should use the specific
345unicode enabled formats for the sake of interoperability as other implementations might not support unicode in the regular formats.
346
347The validation code for `uri`, `idn-email` and their relatives use mostly standard library code.
348
349For repetitive or more complex formats, you can create custom format checkers and add them to gojsonschema like this:
350
351```go
352// Define the format checker
353type RoleFormatChecker struct {}
354
355// Ensure it meets the gojsonschema.FormatChecker interface
356func (f RoleFormatChecker) IsFormat(input interface{}) bool {
357
358 asString, ok := input.(string)
359 if ok == false {
360 return false
361 }
362
363 return strings.HasPrefix("ROLE_", asString)
364}
365
366// Add it to the library
367gojsonschema.FormatCheckers.Add("role", RoleFormatChecker{})
368````
369
370Now to use in your json schema:
371````json
372{"type": "string", "format": "role"}
373````
374
375Another example would be to check if the provided integer matches an id on database:
376
377JSON schema:
378```json
379{"type": "integer", "format": "ValidUserId"}
380```
381
382```go
383// Define the format checker
384type ValidUserIdFormatChecker struct {}
385
386// Ensure it meets the gojsonschema.FormatChecker interface
387func (f ValidUserIdFormatChecker) IsFormat(input interface{}) bool {
388
389 asFloat64, ok := input.(float64) // Numbers are always float64 here
390 if ok == false {
391 return false
392 }
393
394 // XXX
395 // do the magic on the database looking for the int(asFloat64)
396
397 return true
398}
399
400// Add it to the library
401gojsonschema.FormatCheckers.Add("ValidUserId", ValidUserIdFormatChecker{})
402````
403
404Formats can also be removed, for example if you want to override one of the formats that is defined by default.
405
406```go
407gojsonschema.FormatCheckers.Remove("hostname")
408```
409
410
411## Additional custom validation
412After the validation has run and you have the results, you may add additional
413errors using `Result.AddError`. This is useful to maintain the same format within the resultset instead
414of having to add special exceptions for your own errors. Below is an example.
415
416```go
417type AnswerInvalidError struct {
418 gojsonschema.ResultErrorFields
419}
420
421func newAnswerInvalidError(context *gojsonschema.JsonContext, value interface{}, details gojsonschema.ErrorDetails) *AnswerInvalidError {
422 err := AnswerInvalidError{}
423 err.SetContext(context)
424 err.SetType("custom_invalid_error")
425 // it is important to use SetDescriptionFormat() as this is used to call SetDescription() after it has been parsed
426 // using the description of err will be overridden by this.
427 err.SetDescriptionFormat("Answer to the Ultimate Question of Life, the Universe, and Everything is {{.answer}}")
428 err.SetValue(value)
429 err.SetDetails(details)
430
431 return &err
432}
433
434func main() {
435 // ...
436 schema, err := gojsonschema.NewSchema(schemaLoader)
437 result, err := gojsonschema.Validate(schemaLoader, documentLoader)
438
439 if true { // some validation
440 jsonContext := gojsonschema.NewJsonContext("question", nil)
441 errDetail := gojsonschema.ErrorDetails{
442 "answer": 42,
443 }
444 result.AddError(
445 newAnswerInvalidError(
446 gojsonschema.NewJsonContext("answer", jsonContext),
447 52,
448 errDetail,
449 ),
450 errDetail,
451 )
452 }
453
454 return result, err
455
456}
457```
458
459This is especially useful if you want to add validation beyond what the
460json schema drafts can provide such business specific logic.
461
462## Uses
463
464gojsonschema uses the following test suite :
465
466https://github.com/json-schema/JSON-Schema-Test-Suite
467