Code Monkey home page Code Monkey logo

gojsonschema's Introduction

GoDoc Build Status Go Report Card

gojsonschema

Description

An implementation of JSON Schema for the Go programming language. Supports draft-04, draft-06 and draft-07.

References :

Installation

go get github.com/xeipuuv/gojsonschema

Dependencies :

Usage

Example

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)
        }
    }
}

Loaders

There are various ways to load your JSON data. In order to load your schemas and documents, first declare an appropriate loader :

  • Web / HTTP, using a reference :
loader := gojsonschema.NewReferenceLoader("http://www.some_host.com/schema.json")
  • Local file, using a reference :
loader := gojsonschema.NewReferenceLoader("file:///home/me/schema.json")

References use the URI scheme, the prefix (file://) and a full path to the file are required.

  • JSON strings :
loader := gojsonschema.NewStringLoader(`{"type": "string"}`)
  • Custom Go types :
m := map[string]interface{}{"type": "string"}
loader := gojsonschema.NewGoLoader(m)

And

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)

Validation

Once the loaders are set, validation is easy :

result, err := gojsonschema.Validate(schemaLoader, documentLoader)

Alternatively, you might want to load a schema only once and process to multiple validations :

schema, err := gojsonschema.NewSchema(schemaLoader)
...
result1, err := schema.Validate(documentLoader1)
...
result2, err := schema.Validate(documentLoader2)
...
// etc ...

To check the result :

    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)
        }
    }

Loading local schemas

By 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.

	sl := gojsonschema.NewSchemaLoader()
	loader1 := gojsonschema.NewStringLoader(`{ "type" : "string" }`)
	err := sl.AddSchema("http://some_host.com/string.json", loader1)

Alternatively if your schema already has an $id you can use the AddSchemas function

	loader2 := gojsonschema.NewStringLoader(`{
			"$id" : "http://some_host.com/maxlength.json",
			"maxLength" : 5
		}`)
	err = sl.AddSchemas(loader2)

The main schema should be passed to the Compile function. This main schema can then directly reference the added schemas without needing to download them.

	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)

It's also possible to pass a ReferenceLoader to the Compile function that references a loaded schema.

err = sl.AddSchemas(loader3)
schema, err := sl.Compile(gojsonschema.NewReferenceLoader("http://some_host.com/main.json"))

Schemas added by AddSchema and AddSchemas are only validated when the entire schema is compiled, unless meta-schema validation is used.

Using a specific draft

By 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.

Autodectection can be turned off with the AutoDetect property. Specific draft versions can be specified with the Draft property.

sl := gojsonschema.NewSchemaLoader()
sl.Draft = gojsonschema.Draft7
sl.AutoDetect = false

If 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.

Meta-schema validation

Schemas that are added using the AddSchema, AddSchemas and Compile can be validated against their meta-schema by setting the Validate property.

The 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.

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
}`))

Errors returned by meta-schema validation are more readable and contain more information, which helps significantly if you are developing a schema.

Meta-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.

Working with Errors

The library handles string error codes which you can customize by creating your own gojsonschema.locale and setting it

gojsonschema.Locale = YourCustomLocale{}

However, each error contains additional contextual information.

Newer versions of gojsonschema may have new additional errors, so code that uses a custom locale will need to be updated when this happens.

err.Type(): string Returns the "type" of error that occurred. Note you can also type check. See below

Note: An error of RequiredType has an err.Type() return value of "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{} Returns the value given

err.Context(): gojsonschema.JsonContext Returns the context. This has a String() method that will print something like this: (root).firstName

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.

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.

err.DescriptionFormat(): string The error description format. This is relevant if you are adding custom validation errors afterwards to the result.

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()

Note 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.

{{.field}} must be greater than or equal to {{.min}}

The library allows you to specify custom template functions, should you require more complex error message handling.

gojsonschema.ErrorTemplateFuncs = map[string]interface{}{
	"allcaps": func(s string) string {
		return strings.ToUpper(s)
	},
}

Given the above definition, you can use the custom function "allcaps" in your localization templates:

{{allcaps .field}} must be greater than or equal to {{.min}}

The above error message would then be rendered with the field value in capital letters. For example:

"PASSWORD must be greater than or equal to 8"

Learn more about what types of template functions you can use in ErrorTemplateFuncs by referring to Go's text/template FuncMap type.

Formats

JSON 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:

{"type": "string", "format": "email"}

Not all formats defined in draft-07 are available. Implemented formats are:

  • date
  • time
  • date-time
  • hostname. Subdomains that start with a number are also supported, but this means that it doesn't strictly follow RFC1034 and has the implication that ipv4 addresses are also recognized as valid hostnames.
  • email. Go's email parser deviates slightly from RFC5322. Includes unicode support.
  • idn-email. Same caveat as email.
  • ipv4
  • ipv6
  • uri. Includes unicode support.
  • uri-reference. Includes unicode support.
  • iri
  • iri-reference
  • uri-template
  • uuid
  • regex. Go uses the RE2 engine and is not ECMA262 compatible.
  • json-pointer
  • relative-json-pointer

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 unicode enabled formats for the sake of interoperability as other implementations might not support unicode in the regular formats.

The validation code for uri, idn-email and their relatives use mostly standard library code.

For repetitive or more complex formats, you can create custom format checkers and add them to gojsonschema like this:

// 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{})

Now to use in your json schema:

{"type": "string", "format": "role"}

Another example would be to check if the provided integer matches an id on database:

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{})

Formats can also be removed, for example if you want to override one of the formats that is defined by default.

gojsonschema.FormatCheckers.Remove("hostname")

Additional custom validation

After the validation has run and you have the results, you may add additional errors using Result.AddError. This is useful to maintain the same format within the resultset instead of having to add special exceptions for your own errors. Below is an example.

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

}

This is especially useful if you want to add validation beyond what the json schema drafts can provide such business specific logic.

Uses

gojsonschema uses the following test suite :

https://github.com/json-schema/JSON-Schema-Test-Suite

gojsonschema's People

Contributors

ashb avatar attilaolah avatar austinov avatar binary132 avatar brandur avatar chrisdostert avatar chriskaly avatar cristiangraz avatar dbadura avatar dgolub avatar dsanader avatar freakingawesome avatar genesor avatar jabley avatar jbirch-atlassian avatar jboelter avatar johandorland avatar kajf avatar lylex avatar mtiller avatar packrat386 avatar pytlesk4 avatar ricardomaraschini avatar sigu-399 avatar stephensearles avatar technoweenie avatar thajeztah avatar vovanec avatar xeipuuv avatar yuokada avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

gojsonschema's Issues

Add JsonPointer method to resulterror

This would be convenient instead of having to use the dot-separated value returned by the Field() method of ResultErrorFields.

example:

res, err := gojsonschema.Validate(schemaLoader, jsonLoader)
for _, resErr := range res.Errors() {
    resErr.Field() // -> search.name
    resErr.JSONPointer() // -> /search/name
}

If this is a good idea, I would be happy to implement the feature.

Validation failed.

We have valid json which valid against schema.
http://www.jsonschemavalidator.net/ shows that validation OK, but "gojsonschema" failed.

Json:

{"id":8021929379916255508,"version":14,"ppid":"001N","event_type":108,"event_name":"GAME1","time":1438387197195,"date":"2015.07.31 23:59:57:195","uid":2876123,"sid":217821637863217869,"seq":3828,"conn":"38ge62g6g26gd6g23eg676d2","accounts":{"1":{"balance":567748580,"available":521648580,"children":{"111005166":{"balance":36600000,"amount":-1000000},"111012365":{"balance":9500000}}},"2":{"balance":59}},"server":111,"server_state":"ALIVE","buildno":"2.9.16","params":{"login":"login","nick":"nick","desk":{"id":111005166,"game":"GAME1","hand":16094218,"gip":false,"dealer_place":0,"places":[{"place":1,"reserved":false,"player":46966470,"amount":36600000,"stake":1000000,"fold":false},{"place":3,"reserved":false,"player":17898625,"amount":9000000,"stake":0,"fold":false},{"place":5,"reserved":false,"player":58429925,"amount":0,"stake":0,"fold":false},{"place":7,"reserved":false,"player":23778346,"amount":9000000,"stake":0,"fold":false},{"place":9,"reserved":false,"player":54497914,"amount":44000000,"stake":500000,"fold":false}],"category":500000,"zone":"C0","type":"STANDARD","level":0,"limit":"NL","speed":"FAST","placesCount":5,"vip":false,"pot":0},"type":"BIG_BLIND","amount":1000000}}

Schema:
https://gist.github.com/wizard580/ec6e7c859296b53a9df3

Invalid errors for dependencies

Using this schema

{
  "type": "object",
  "properties": {
    "a": {"type": "string"},
    "b": {"type": "string"},
    "c": {"type": "string"}
  },
  "dependencies": {
    "b": ["a"],
    "c": ["a"]
  }
}

and this document

{
  "b": "b",
  "c": "c"
}

we get the error "(root): Has a dependency on a" twice. It seems like "(root)" should be replaced with "b" and "c" in these errors, respectively.

The error details don't make any reference to "b" or "c", so you can't tell the errors apart by looking at details either.

Message key constants associated with error messages

Hi,

Great work on this library.

Right now validation messages are tied to a particular English representation ready for printf in locales.go- e.g. https://github.com/xeipuuv/gojsonschema/blob/master/locales.go#L43 .

We are building an app that would like to use your library but we need messages that can be handcrafted and, eventually, internationalised.

We'd like to be able to associate constant key names with a validation message (as in i18n). I think the easiest way to do this would be to add a MessageKey to the ErrorResult object, which would map to a constant associated with the error. This would make it easy for people to build their own validation messages if they wanted, mapping them to the MessageKey.

I'll fork and add this and make a PR. I just wanted to check first in case if you'd already thought about this and had another solution.

Cheers,
Nicholas

Performance

Were there any performance tests?
I see slow validations, so I try to look inside and figure out what happens.

May be devs knows where to look first?

Invalid error when using "patternProperties"

When using patternProperties with a nested object, an error in the child object will cause an invalid error in the parent.

For example, with schema

{
  "type": "object",

  "patternProperties": {
    "^[a-z]+$": {
      "type": "object",

      "properties": {
        "b": {
          "oneOf": [
            { "type": "object" },
            { "type": "array" }
          ]
        }
      }
    }
  }
}

and document

{
  "a": {
    "b": "b"
  }
}

the following errors are generated

- a.b: Must validate one and only one schema (oneOf)
- a.b: Invalid type. Expected: object, given: string
- a: Property "a" does not match pattern "^[a-z]+$"

"a" is a valid key for the parent, so the third error is invalid. If you make property "b" into an object or array, all errors, including the third, disappear.

If you add '"additionalProperties": false' to the root object, an incorrect error remains but it turns into

- a: Additional property a is not allowed

schema can cause infinite recursion

Parsing a recursive schema not from a URL can cause infinite recursion.

Here is a repro example. Watch out, it will tank your computer until it runs out of stack space. Putting a print inside parseSchema will cause it to be more friendly.

package main

import (
    "encoding/json"
    "log"

    "github.com/binary132/gojsonschema"
)

func main() {
    var schema map[string]interface{}
    err := json.Unmarshal(data, &schema)
    if err != nil {
        log.Fatalf("cannot unmarshal schema definition: %v", err)
    }

    _, err = gojsonschema.NewJsonSchemaDocument(schema)
    if err != nil {
        log.Fatalf("cannot parse schema definition: %v", err)
    }
}

var data = []byte(`
{
    "definitions": {
        "schemaArray": {
            "items": {
                "$ref": "#"
            },
            "type": "array"
        }
    },
    "properties": {
        "items": {
            "$ref": "#/definitions/schemaArray"
        },
        "not": {
            "$ref": "#"
        }
    },
    "type": "object"
}
`)

`$ref` with a relative path `$ref: ./bson_object_id` returns `References are not compatible`

I'm not sure if I'm using $ref correctly, but I have two schemas under the same directory /data/json/schemas and I'm trying to referencing one bson_object_id.json from another account.json.

/data/json/schemas/account.json

{
  "type": "object",
  "required": ["user_id"],
  "additionalProperties": false,
  "properties": {
    "user_id": {
      "$ref": "./bson_object_id.json#properties/_id"
    }
  }
}

/data/json/schemas/bson_object_id.json

{
  "type": "object",
  "properties": {
    "_id": {
      "type": [ { "type": "string" },
                { "type": "object",
                  "additionalProperties" : false,
                  "properties": {
                    "$oid": {
                      "type": "string"
                    }
                  },
                  "required": [ "$oid" ]
                }
              ]
    }
  }
}

When I execute the validation schema.Validate I get References are not compatible. Is it the proper for referencing schemas?

Thanks

Accessible schema properties

Nearly all fields in gojsonschema structs are invisible outside the package. This works pretty well as long as its mainly used to call Validate, but it might be useful to be able to re-use gojsonschema to explore a document outside of this context. For example, if I've built a JSON Hyper Schema and would like to enumerate every link that it provides.

Have you put any thought into opening up these interfaces for re-use?

Generating via Instances

I've been using your package at work for a tool we're considering open sourcing. I've made some significant additions that allow you to generate JSON Schema documents from instances and to augment existing schema from more instances.

Before we just released a fork, I wanted to ping you to ask if you're interested in accepting these features as a pull request.

PS - I couldn't find a better place to post this message, so sorry if this is weird.

Custom keywords

What do you think about allowing for the definition of new keywords with custom behavior ?

In other words, allowing for projects utilizing gojsonschema to define their own schema properties on top of those standard to the json-schema v4 spec.

It seems like a useful feature which would allow for some nice extendability.

There is a javascript json-schema library called tv4 which supports similar functionality.

Public types

Do you plan on making types and interfaces like jsonLoader public (like it was with for example JsonSchemaDocument before)?
That would simplify definition of types/structs that have loaders as members or function paramerters.

If not could you suggest an alternative to achive the same without losing type safety to interface{}?

Array items as object

Hi there, i have a schema

{
  "type": "object",
  "properties": {
    "ARRAY": {
      "type": ["array", "null"],
      "additionalItems": false,
      "uniqueItems": true,
      "items": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "NUM": {
            "type": "string",
            "enum": ["1", "2"]
          }
        }
      }
    }
  }
}

and a document

{
 "ARRAY":[
   {"NUM":"1", "additional_property":1}
 ]
}

Cloned repo says that this document is valid. But it should not.

Consider moving imports from sigu-399 to xeipuuv

It looks like gojson{pointer,reference,schema} where previously repositories from sigu-399 but are now owned by xeipuuv. Could you consider reflecting this in their respective import clauses ?

Thanks.

Enum validator doesn't work for Go struct

When I use NewReferenceLoader with incorrect string enum value, then I get: "...must match one of the enum values ...", but NewGoLoader doesn't throw en error for the same case

maximum validation error is incorrect

When maximum validation is applied I am getting the wrong error 'desc: Must be less than %max%'
Seems like the 'max' should be used instead of 'min' in validation.go:
https://github.com/xeipuuv/gojsonschema/blob/master/validation.go#L783
https://github.com/xeipuuv/gojsonschema/blob/master/validation.go#L794
Looking at the locales - the %max% (not %min%) is used there:
https://github.com/xeipuuv/gojsonschema/blob/master/locales.go#L186

The same is for min (should be 'min' instead of 'max'):
https://github.com/xeipuuv/gojsonschema/blob/master/validation.go#L810
https://github.com/xeipuuv/gojsonschema/blob/master/validation.go#L821

Generate Go Structs From Schema

I want to be able to verify and then *access the data from a JSON document, based on its schema.

It seems like this library solves the first problem, but do you know how to achieve the second? I could manually create a bunch of Go structs for the schema, but is there a better way? Like a way to generate Go types based on the schema?

Simplify gojsonschema.FormatCheckers.Add() by not using structs

Why does gojsonschema.FormatCheckers.Add() pass structs instead of functions? It'd be a lot easier to have this type instead:

func (c *FormatCheckerChain) Add(name string, f func(string) bool *FormatCheckerChain

Why the extra typing? Is there an advantage of the current method?

Setting default values

I notice that the "default" key is not tested. Can gojsonschema generate a default object for a schema such as this? If not, would you be open to a PR to generate a default map[string]interface{} from a gojsonschema document?

{
  "properties": {
    "outfile": {
      "type": "string",
      "default": "out.bz2"
    }
  },
  "type": "object"
}

should produce

map[string]interface{}{
    "outfile": "out.bz2",
}

as a default object.

Strings passing as integers using map

My schema.json:

{
    "title": "Example Schema",
    "type": "object",
    "properties": {
        "firstName": {
            "type": "string"
        },
        "lastName": {
            "type": "string"
        }
    }
}

This shouldn't be valid:

package main

import (
    "fmt"

    "github.com/xeipuuv/gojsonschema"
)

func main() {
    schemaDocument, _ := gojsonschema.NewJsonSchemaDocument("file:///schema.json")

    // Creating the map with integer values validates (INCORRECT)
    jsonDocument := map[string]interface{}{
        "firstName": 1, // this passes and shouldn't
        "lastName":  1.337, // this fails
    }

    // ...
}

Unmarshalling json into interface works:

package main

import (
    "fmt"

    "github.com/xeipuuv/gojsonschema"
)

func main() {
    schemaDocument, _ := gojsonschema.NewJsonSchemaDocument("file:///schema.json")

    // The below shows that both fields are invalid (CORRECT)
    var jsonDocument interface{}
    _ = json.Unmarshal([]byte(`{"firstName": 1, "lastName": 1.337}`), &jsonDocument)

    // ...
}

I noticed this the first few minutes of playing with this library. I haven't dug into the code to contribute a fix, so hopefully someone who is more versed can let me know if I'm way off on this.

Sometimes schema incorrectly interprets integers as numbers

I have this json object starting like this

{"lead":{"id":1074313,

and a schema definition like this

    "lead": {
      "id": "/lead",
      "type": "object",
      "properties": {
        "id": {
          "id": "/lead/id",
          "type": "integer"
        },

but gojsonschema seems to regularly return this error

Type: invalid_type,
Description: Invalid type. Expected: integer, given: number
Details: map[expected:integer given:number field:lead.id]

Note Still working on making a reproducible example

"minimum" and "maximum" criteria fail to parse for integer values.

Since currentSchema.minimum and .maximum are typed as a float64, and the value in the JSON-Schema is required to be a float64, valid JSON-Schemas with integer keys will never parse as JsonSchemaDocuments. To use the canonical example from http://json-schema.org/examples.html,

{
    "title": "Example Schema",
    "type": "object",
    "properties": {
        "firstName": {
            "type": "string"
        },
        "lastName": {
            "type": "string"
        },
        "age": {
            "description": "Age in years",
            "type": "integer",
            "minimum": 0
        }
    },
    "required": ["firstName", "lastName"]
}

This will always fail to parse with the error "minimum must be a number".

    if existsMapKey(m, KEY_MINIMUM) {
        if isKind(m[KEY_MINIMUM], reflect.Float64) {
            minimumValue := m[KEY_MINIMUM].(float64)
            currentSchema.minimum = &minimumValue
        } else {
            return errors.New("minimum must be a number")
        }
    }

https://github.com/xeipuuv/gojsonschema/blob/master/schemaDocument.go#L355

Can I generate a Schema?

I see that this library can load a schema (seemingly from different sources, strings, Go types?, etc).

But once that schema is loaded, is there a way to output it back out as a JSON Schema? I'm curious because it would be nice to see what the library is actually using internally (after any normalization it might perform).

If I understand the API, it seems as though the library is able to build a schema from an existing Go type. In that case, it would be even more interesting to be able to output the resulting schema.

Or am I missing something?

Integer validation issues

When validating against type integer, validation fails, despite proper data being passed.

Schema excerpt:

{
    "gpid": {
        "type": "integer",
        "description": "GPID"
    }
}

Data excerpt:

{
    "gpid": 5235234
}

Error:

- (root).gpid : must be of type number, given 5235234

File URL not handled correctly on Windows

The library doesn't properly handle loading a JSON schema from a properly formed file URL when running on Windows. There, a file URL will have an extra slash after the file:// and the path with have slashes converted to backslashes and spaces escaped. Extra logic is needed to handle these conversions at jsonLoader.go:78-82.

Loaders

That's how I validate schema.

schema_loader := gojsonschema.NewGoLoader(schema)
_, err = gojsonschema.NewSchema(schema_loader)

Here is the schema:

{
    "$schema": "http://127.0.0.1:8000/custom_schema#",
    "title": "Example Schema",
    "type": "object",
    "properties": {
        "avatar": {
            "type": "file"
        }
    }
}

This is a "remote" schema

{
            "id": "http://127.0.0.1:8000/custom_schema#",
            "$schema": "http://json-schema.org/draft-04/schema#",
            "type": "object",
            "definitions": {
                "customTypes": {
                    "enum": [ "file" ]
                }
            },
            "properties": {
                "type": {
                    "anyOf": [
                        { "$ref": "#schema/definitions/simpleTypes" },
                        {
                            "type": "array",
                            "items": { "$ref": "#schema/definitions/simpleTypes" },
                            "minItems": 1,
                            "uniqueItems": true
                        },
                        { "$ref": "#/definitions/customTypes" },
                        {
                            "type": "array",
                            "items": { "$ref": "#/definitions/customTypes" },
                            "minItems": 1,
                            "uniqueItems": true
                        }
                    ]
                }
        }
}

It fails with an error file is not a valid type.

Am I doing something wrong ?:D

Course $schema is being ignored.
And If I use ReferenceLoader - nested are being ignored too.

Incorrect validation using patternProperties and additionalProperties

Using this schema

{
    "$schema": "http://json-schema.org/draft-04/schema#",
    "type": "object",
    "patternProperties": {
        "^[a-zA-Z0-9]{1,10}$": { "type": "string" }
    },
    "additionalProperties": false
}

This should be invalid

{
    "a": "hello",
    "aaaaaaaaaaaaaaaaaaa": null
} 

However it validates. If you remove the "a" then it does produce an error as expected

{
    "aaaaaaaaaaaaaaaaaaa": null
} 

produces

ROOT : No additional property ( aaaaaaaaaaaaaaaaaaa ) is allowed on (root)

I've checked the above with https://github.com/Julian/jsonschema and it agrees with me that this is a bug

Add API to create JsonSchemaDocument from JSON

The only public API to create a JsonSchemaDocument takes a URL. In my use case I already have the (parsed) contents of the schema, since the schema is contained as a sub-object in an app configuration file. So I would like to be able to create a document by passing in a map[string]interface{}.

It looks like this would be a pretty simple variation on the NewJsonSchemaDocument function.

Extra error when validation for a "oneOf" rule fails

Whenever validation for a "oneOf" rule fails because of an incorrect type, an "incorrect type" error is also generated.

For example, with schema

{
  "type": "object",
  "properties": {
    "a": {
      "oneOf": [
        { "type": "array" },
        { "type": "object" }
      ]
    }
  }
}

and document

{
  "a": "a"
}

the following errors are generated

- a: Must validate one and only one schema (oneOf)
- a: Invalid type. Expected: array, given: string

It seems like a bug for there to be more than one error here. If it is expected behavior, the expected type for the second error only lists the first type in the "oneOf" rule.

Configurable delimiter

It would be good if the delimiter was configurable for "jsonContext" instead of assumed being ".".

Like:

c.String("/")

Consider making names shorter

Names in Go are usually short.

Shorter type and package names are more idiomatic and less to type. For example:

gojsonschema -> jsonschema
GetFileJson -> GetFile
GetHttpJson -> GetHTTP (initialism)
JsonSchemaDocument -> Document
ValidationResult -> Result

gojsonschema does not handle duplicate fields

Hello.
gojsonschema does not handle duplicate fields and only keeps the last occurence.
Can you throw an error when processing duplicate fields?
Thank you.

Here is a sample ("firstName" field occurs twice ( "Petr" and "Ivan") ):

package main

import (
"fmt"
"github.com/xeipuuv/gojsonschema"
)

func main() {
src := { "firstName" : "Petr", "firstName" : "Ivan", "lastName" : "Petrov" }

schema := `{
    "title": "test",
    "type": "object",
    "properties": {
        "firstName": {
            "type": "string"
        },
        "lastName": {
            "type": "string"
        }
    },
    "required": ["firstName", "lastName"],
    "additionalProperties": false
}`

documentLoader := gojsonschema.NewStringLoader(src)
schemaLoader := gojsonschema.NewStringLoader(schema)
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("Errors :\n")
    for _, desc := range result.Errors() {
        fmt.Printf("- %s\n", desc)
    }
}

}

Bad error message when have exclusiveMaximum or exclusiveMinimum

When have such schema:

            "x":{
                "type":"number",
                "minimum": 0,
                "exclusiveMinimum": true
            }

And then validate json like "x:0", the error message is something like "x: Must be greater than or equal to 0", the error should be like "x: Must be greater than 0".

exclusiveMaximum is the same error.

internalLog

I suggest to wrap calls to internalLog() with "if internalLogEnabled"
It not needed to call it for everyone. And some times there are parameters like "context.String()" which slows things down.

Wondering about thread safety

I would like to use a global gojsonschema.Schema object for validating schemas, i.e. the return value of gojsonschema.NewSchema. Was wondering if it was safe to use this object across different goroutines simultaneously, and not sure how to determine this.

Relative $ref does not work unless loaded from a URI

I have a project where I'm storing schema documents in a database. I need to use $ref within the schema, but when I try to load the schema, I get an error "Parent reference must be canonical". This worked fine in Python's jsonschema library, but I cannot get it to work with gojsonschema. What I expect is that hash-only $ref's would reference the current schema even though it doesn't have a canonical location.

main.go:

package main

import (
    "fmt"

    "github.com/sigu-399/gojsonschema"
)

func main() {
    schema := map[string]interface{}{
        "$ref": "#/definitions/a",
        "definitions": map[string]interface{}{
            "a": map[string]interface{}{"type": "integer"},
        },
    }
    _, err := gojsonschema.NewJsonSchemaDocument(schema)
    fmt.Println(err)
}

output:

$ go run main.go
Parent reference must be canonical

Doesn't work with json schema definitions

The official json-schema spec has definitions but definitions don't seem to work with this library.

When I try to perform a validation with the document/schema pair below I get the error:

Object has no key 'definitions'

However I know the schema is valid because the validation of the document and schema works fine here

My json document:

{
    "data": {
        "result": 0.98
    },
    "image": {
        "data": "www.image.com",
        "type": "url"
    }
}

My json schema:

{
    "properties": {
        "data": {
            "description": "My custom data schema",
            "type": "object"
        },
        "annotations": {
            "definitions": {
                "feature": {
                    "description": "A Geo JSON feature object",
                    "properties": {
                        "geometry": {
                            "oneOf": [{
                                "type": "null"
                            }, {
                                "$ref": "#/definitions/geometry"
                            }]
                        },
                        "id": {
                            "FIXME": "may be there, type not known (string? number?)"
                        },
                        "properties": {
                            "type": [
                                "object",
                                "null"
                            ]
                        },
                        "type": {
                            "enum": [
                                "Feature"
                            ]
                        }
                    },
                    "required": [
                        "geometry",
                        "properties"
                    ],
                    "title": "Feature"
                },
                "featureCollection": {
                    "description": "A Geo JSON feature collection",
                    "properties": {
                        "features": {
                            "items": {
                                "$ref": "#/definitions/feature"
                            },
                            "type": "array"
                        },
                        "type": {
                            "enum": [
                                "FeatureCollection"
                            ]
                        }
                    },
                    "required": [
                        "features"
                    ],
                    "title": "FeatureCollection"
                },
                "geometry": {
                    "definitions": {
                        "lineString": {
                            "allOf": [{
                                "$ref": "#/definitions/positionArray"
                            }, {
                                "minItems": 2
                            }],
                            "description": "An array of two or more positions"
                        },
                        "linearRing": {
                            "allOf": [{
                                "$ref": "#/definitions/positionArray"
                            }, {
                                "minItems": 4
                            }],
                            "description": "An array of four positions where the first equals the last"
                        },
                        "polygon": {
                            "description": "An array of linear rings",
                            "items": {
                                "$ref": "#/definitions/linearRing"
                            },
                            "type": "array"
                        },
                        "position": {
                            "additionalItems": false,
                            "description": "A single position",
                            "items": [{
                                "type": "number"
                            }, {
                                "type": "number"
                            }],
                            "minItems": 2,
                            "type": "array"
                        },
                        "positionArray": {
                            "description": "An array of positions",
                            "items": {
                                "$ref": "#/definitions/position"
                            },
                            "type": "array"
                        }
                    },
                    "description": "One geometry as defined by GeoJSON",
                    "oneOf": [{
                        "properties": {
                            "coordinates": {
                                "$ref": "#/definitions/position"
                            },
                            "type": {
                                "enum": [
                                    "Point"
                                ]
                            }
                        },
                        "title": "Point"
                    }, {
                        "properties": {
                            "coordinates": {
                                "$ref": "#/definitions/positionArray"
                            },
                            "type": {
                                "enum": [
                                    "MultiPoint"
                                ]
                            }
                        },
                        "title": "MultiPoint"
                    }, {
                        "properties": {
                            "coordinates": {
                                "$ref": "#/definitions/lineString"
                            },
                            "type": {
                                "enum": [
                                    "LineString"
                                ]
                            }
                        },
                        "title": "LineString"
                    }, {
                        "properties": {
                            "coordinates": {
                                "items": {
                                    "$ref": "#/definitions/lineString"
                                },
                                "type": "array"
                            },
                            "type": {
                                "enum": [
                                    "MultiLineString"
                                ]
                            }
                        },
                        "title": "MultiLineString"
                    }, {
                        "properties": {
                            "coordinates": {
                                "$ref": "#/definitions/polygon"
                            },
                            "type": {
                                "enum": [
                                    "Polygon"
                                ]
                            }
                        },
                        "title": "Polygon"
                    }, {
                        "properties": {
                            "coordinates": {
                                "items": {
                                    "$ref": "#/definitions/polygon"
                                },
                                "type": "array"
                            },
                            "type": {
                                "enum": [
                                    "MultiPolygon"
                                ]
                            }
                        },
                        "title": "MultiPolygon"
                    }],
                    "required": [
                        "type",
                        "coordinates"
                    ],
                    "title": "geometry",
                    "type": "object"
                },
                "geometryCollection": {
                    "description": "A collection of geometry objects",
                    "properties": {
                        "geometries": {
                            "items": {
                                "$ref": "#/definitions/geometry"
                            },
                            "type": "array"
                        },
                        "type": {
                            "enum": [
                                "GeometryCollection"
                            ]
                        }
                    },
                    "required": [
                        "geometries"
                    ],
                    "title": "GeometryCollection"
                }
            },
            "description": "Format of the annotations portion of the result payload",
            "oneOf": [{
                "$ref": "#/definitions/geometry"
            }, {
                "$ref": "#/definitions/geometryCollection"
            }, {
                "$ref": "#/definitions/feature"
            }, {
                "$ref": "#/definitions/featureCollection"
            }],
            "properties": {
                "bbox": {
                    "items": {
                        "type": "number"
                    },
                    "type": "array"
                },
                "crs": {
                    "type": "null"
                }
            },
            "required": [
                "type"
            ],
            "type": "object"
        },
        "image": {
            "additionalProperties": false,
            "description": "Format of the image portion of the result payload",
            "properties": {
                "data": {
                    "type": "string"
                },
                "type": {
                    "enum": [
                        "base64",
                        "url",
                        "storage"
                    ]
                }
            },
            "required": [
                "type",
                "data"
            ],
            "type": "object"
        }
    },
    "additionalProperties": false,
    "required": [
        "data",
        "image",
        "annotations"
    ]
}

Adding 'Type' to ResultError struct

I'm trying to create custom error messages for a JSON API in this format:

"error": {
   "field": "firstName",
   "code": 50001,
   "message": "First name is required"
}

The idea is I need to convert validation errors on my JSON schema to a specific error format. It could be an error code, a custom message, or anything else really.

What do you think of adding a 'Type' field to the ResultError struct? The context already has the field name, but other than having a string there is no way to know which error occurred. I put together an example here: https://github.com/cristiangraz/gojsonschema

Essentially you would have values like: enum, missing, less_than, unique, invalid_type, greater_than, less_than, etc to know which error occurred on each field. From there, it gives the developer more options to create customized error messages. Another option would be to change the locales to be these values that you could check against, but I don't see an easy way to overwrite the locale without forking and I think there is value in knowing the error type and still having a human-readable error message.

Open to other suggestions/ideas ways of accomplishing this if there is interest.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.