santhosh-tekuri / jsonschema

JSONSchema (draft 2020-12, draft 2019-09, draft-7, draft-6, draft-4) Validation using Go
Apache License 2.0
957 stars 98 forks source link

How do you load schemas from memory rather than URLs? #89

Closed insano10 closed 2 years ago

insano10 commented 2 years ago

I have an API that takes a JSON schema and a document to validate against that schema. This is so schemas can be created dynamically. In order to compile that schema into a Schema struct I've had to write a custom loader to wrap reading the bytes from the request. Is there a more elegant way to do this with this library? I don't want to be registering a new loader for each request that could have a separate schema.

// error handling removed for clarity

type ValidationRequest struct {
    Schema   interface{}
    Document interface{}
}

body := ValidationRequest{}
web.Unmarshal(request, &body)

schemaBytes, err := json.Marshal(body.Schema)

jsonschema.Loaders["memory"] = func(_ string) (io.ReadCloser, error) {
    return ioutil.NopCloser(bytes.NewReader(schemaBytes)), nil
}

schema, err := jsonschema.Compile("memory:///foo")

err = schema.Validate(body.Document)
santhosh-tekuri commented 2 years ago

since multiple requests might be coming concurrently, updating jsonschema.Loaders global variable will cause data race.

one solution is to use Compiler.AddResource:

c := jsonschema.NewCompiler()
if err := c.AddResource("schema.json", bytes.NewReader(schemaBytes)); err!=nil {
    panic(err)
}
schema, err := c.Compile("schema.json")

another solution is to use Compiler.LoadURL:

c := jsonschema.NewCompiler()
c.LoadURL = func(s string) (io.ReadCloser, error) {
    if s == "memory:///schema.json" {
        return bytes.NewReader(schemaBytes), nil
    }
    return jsonschema.LoadURL(s) // delegate to default impl
}
schema, err := c.Compile("memory:///schema.json")
insano10 commented 2 years ago

Thank you for your suggestions that is helpful